You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SwiftUI:解决Sheet在fraction档位下内容无法顶部对齐的问题

问题描述

我正在实现Apple Maps风格的Sheet交互,目前大部分功能已完成,唯独所有档位下内容均顶部对齐这一点有问题:

  • Sheet处于medium或large档位时,内容能完美顶部对齐;
  • 使用fraction(0.15)档位时,内容始终居中显示,只有展开Sheet后才会展示完整内容。

Sheet实现代码

struct DeviceMapSheet: View {

var device: Device
var locationName: String
var locationIcon: String

@State var showAlert = false

@ObservedObject var mapViewModel: MapViewModel

@Environment(\.dismiss) private var dismiss

var body: some View {
    ZStack {
        VStack {
            HStack {
                RemoteImage(device: device, size: SensioDimens.imageSize, warningSize: SensioDimens.warningSize, isDetailTwin: true, shouldCache: false)
                
                VStack(alignment: .leading, spacing: 2) {
                    DeviceTypeName(device: device)
                    
                    DeviceName(device: device)
                        .padding(.bottom, 2)
                    
                    Status(device: device)
                    
                    Battery(device: device)
                        .padding(.top, 2)
                }
            }
            .padding(.top, 24)
            
            SensioDivider()
                .padding(.vertical)
            
            HStack {
                if locationName != TranslationManager.shared.getNoAddressFound() {
                    RemoteImageView(
                        url: locationIcon,
                        placeholder: {
                            Image(SensioImages.pinIcon).resizable()
                                .scaledToFill()
                                .frame(width: 24, height: 24)
                        },
                        image: {
                            $0.resizable()
                                .scaledToFill()
                                .frame(width: 24, height: 24)
                        })
                }
                
                Text(locationName)
                    .font(SensioFonts.systemMFont)
            }
            
            Button(TranslationManager.shared.getDirectionInMaps()) {
                showAlert = true
            }
            .buttonStyle(SensioGreyButton())
            
            ActionButtons(device: device, isFromMap: true, mainViewModel: MainViewModel(), mapViewModel: mapViewModel, actionButtonsValues: SessionManager.shared.getAllowance(deviceSNNum: device.deviceSNNum).getActionButtonsValues(device: device))
                .padding(.top)
            
            Spacer()
        }
        .alert(TranslationManager.shared.getOpenMapTitle(), isPresented: $showAlert) {
            Button(TranslationManager.shared.getOpen(), role: .none) {
                self.openMap(deviceName: device.deviceName, latStr: device.posLati, lonStr: device.posLong)
                showAlert = false
            }.keyboardShortcut(.defaultAction)
            
            Button(TranslationManager.shared.getCancel(), role: .cancel) {
                showAlert = false
            }
        } message: {
            Text(TranslationManager.shared.getOpenMapText())
        }
    }
    .fullScreenCover(isPresented: $mapViewModel.showDeviceAction) {
        ZStack{
            Color.black.opacity(0.015).edgesIgnoringSafeArea(.all)
            DeviceActionDialog(deviceAction: mapViewModel.deviceAction, show: $mapViewModel.showDeviceAction, isFromMap: true, mainViewModel: MainViewModel(), mapViewModel: mapViewModel)
        }
        .modifier(OpacityModifier())
    }
}
}
#Preview("DeviceMapSheet") {
    DeviceMapSheet(device: Device(), locationName: "Test Address", locationIcon: "", mapViewModel: MapViewModel())
}

Sheet展示代码(detentBinding = PresentationDetent.fraction(0.15))

sheet(isPresented: $showMapSheet) {
            DeviceMapSheet(device: device, locationName: mapViewModel.locationName, locationIcon: mapViewModel.locationIcon,
                           mapViewModel: mapViewModel)
            .presentationDetents([.fraction(0.15), .medium, .large], selection: $detentBinding)
            .presentationDragIndicator(.visible)
            .interactiveDismissDisabled()
            .presentationBackgroundInteraction(.enabled(upThrough: .medium))
            .modifier(SheetRadiusModifier())
        }

效果说明

  • fraction档位:内容居中显示,仅露出顶部部分内容
  • medium档位:内容完全顶部对齐,展示全部内容
解决方案

问题根源是Sheet高度小于内容高度时,系统默认会将内容居中布局。要实现全档位顶部对齐,可采用以下两种修复方式:

方法1:强制内容容器顶部对齐

修改DeviceMapSheet的根VStack,添加无限高度约束并指定顶部对齐,覆盖系统默认行为:

var body: some View {
    ZStack {
        VStack {
            // 原有内容保持不变
        }
        .frame(maxHeight: .infinity, alignment: .top) // 添加该行代码
        // 原有alert、fullScreenCover逻辑不变
    }
}

方法2:自定义Detent确保最小高度

如果需要避免Sheet高度过小导致内容被过度压缩,可以自定义Detent,设置最小高度阈值:

// 自定义小档位Detent,确保最小高度足够展示顶部内容
private let customSmallDetent = PresentationDetent.custom("small") { context in
    max(context.maxDetentValue * 0.15, 120) // 0.15比例或120px取最大值
}

// 展示Sheet时使用自定义Detent
sheet(isPresented: $showMapSheet) {
    DeviceMapSheet(...)
        .presentationDetents([customSmallDetent, .medium, .large], selection: $detentBinding)
        // 其他修饰符保持不变
}

关键提示

  • 方法1是最直接的修复方案,无需额外逻辑即可实现全档位顶部对齐;
  • 方法2适合对Sheet最小高度有要求的场景,避免内容被过度截断。

内容的提问来源于stack exchange,提问作者Andrija Randjelovic

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.20 18:40:55