SwiftUI横竖屏适配:解决单元格与通知徽章布局异常问题
问题:横屏状态下SwiftUI单元格拉伸、通知徽章位置偏移
支持iOS 15,已实现包含图片、文字的垂直单元格,有通知时显示红色圆形徽章。但横屏状态下单元格过度拉伸,通知徽章位置异常:iPhone 11竖屏显示正常,横屏时徽章稍远;iPad 10代横屏时徽章距离极远。已找到临时解决方案,但不清楚原理。
原代码
import SwiftUI struct FavouritesWebAppsMainView: View { @EnvironmentObject var classFromEntryPoint: ClassFromEntryPoint //code for presenting sheet. @State private var selectedItem: WebAppModel? //related to customized wkwebViews @State private var errorMessage: String = "none" @State private var showAlert = false let columns = [ GridItem(.flexible()), GridItem(.flexible()), GridItem(.flexible()), ] var body: some View { VStack { headerView mainBodyWithGrid .sheet(item: $selectedItem) { webApp in let test = webApp.appUrl if let url = URL(string: test) { CustomWebView(url: url, messageErrorFromWebView: $errorMessage) HStack { Spacer() Button(action: { selectedItem = nil //Used to dismiss the sheet }) { Label("", systemImage: "xmark") } } .padding() .background(Color.gray.opacity(0.2)) } else { //never called let _ = self.errorMessage = "Not valid URL" let _ = self.showAlert = true let _ = Logger.error("failed url: \(webApp.appUrl)") } } } //most external view .backgroundImage() .onChange(of: errorMessage, perform: { newValue in self.showAlert = true }) .alert("Errore", isPresented: $showAlert) { Button("ok", role: .cancel) {} } message: { Text(self.errorMessage) } } //MARK: single views private var headerView: some View { VStack { HStack { Text("Welvome") .font(.largeTitle) .foregroundColor(.blue) .lineLimit(1) .minimumScaleFactor(0.5) .padding() Spacer() } Text("Le tue app") .padding(.bottom) } } private var mainBodyWithGrid: some View { ZStack(alignment: .bottom) { ScrollView { if classFromEntryPoint.preferredAppList.isEmpty { Text("Add App to the store") } else { LazyVGrid(columns: columns, spacing: 30) { ForEach(classFromEntryPoint.preferredAppList) { item in VerticalCellView(item: item) .onTapGesture { self.selectedItem = item } } } .padding(.horizontal) } } logoutButton } } private var logoutButton: some View { HStack { Button("Logout") { Logger.debug("logout") classFromEntryPoint.performLogout() } .padding() .background(.red) .foregroundStyle(.white) .clipShape(Capsule()) .padding() Spacer() } } } //MARK: - cell struct VerticalCellView: View { var item: WebAppModel var body: some View { ZStack(alignment: .topTrailing) { GeometryReader { geometry in VStack { ImageDownloadedFromWeb(url: item.logoUrl ?? "N/D") .frame(width: min(geometry.size.width, 100), height: min(geometry.size.width, 100)) Text(item.name) .font(.headline) .frame(maxWidth: .infinity) // Text(item.description) // .font(.subheadline) // .lineLimit(2) // .truncationMode(.tail) } .frame(width: geometry.size.width) } let notificationCount = (Int.random(in: 0...20)) Text(notificationCount > 10 ? "99+" : "\(notificationCount)") .font(.system(size: 12)) .frame(width: 25, height: 25) .background(.red) .foregroundColor(.white) .clipShape(Circle()) } .frame(height: 150) } }
临时解决方案(已验证有效)
struct VerticalCellView: View { var item: WebAppModel var body: some View { VStack { ImageDownloadedFromWeb(url: item.logoUrl ?? "N/D") .frame(width: 100, height: 100) .overlay( notificationBadge, alignment: .topTrailing ) Text(item.name) .font(.headline) // Text(item.description) // .font(.subheadline) // .lineLimit(2) // .truncationMode(.tail) } .frame(height: 150) } private var notificationBadge: some View { let notificationCount = (Int.random(in: 0...20)) return Text(notificationCount > 10 ? "99+" : "\(notificationCount)") .font(.system(size: 12)) .frame(width: 25, height: 25) .background(.red) .foregroundColor(.white) .clipShape(Circle()) .offset(x: 10, y: -10) } }
解决方案原理解析
原代码问题根源
GeometryReader会占据父视图的全部可用宽度,而LazyVGrid的GridItem设为.flexible(),横屏时单元格宽度会大幅拉伸。徽章直接放在ZStack的.topTrailing位置,会对齐整个拉伸后单元格的右上角,导致和图片位置脱节。- 图片用
min(geometry.size.width, 100)限制尺寸,在宽单元格中会居中显示,而徽章对齐单元格边缘,进一步加剧了位置偏差。
为什么新方案有效
- 移除
GeometryReader,直接给图片固定100x100的尺寸,VStack会根据内容自动适配宽度,避免单元格无意义拉伸。 - 用
.overlay将徽章绑定到图片上,对齐图片的.topTrailing,再通过.offset(x:10, y:-10)让徽章部分超出图片范围,确保无论单元格整体尺寸如何变化,徽章始终和图片保持相对固定的位置。 - 保持
VStack高度固定为150,保证不同设备上单元格高度统一,避免布局混乱。
内容的提问来源于stack exchange,提问作者biggreentree
相关产品推荐
相关产品推荐

