You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

SwiftUI适配不同iPhone屏幕尺寸的实现方法咨询

SwiftUI多机型适配指南:解决iPhone 7/8/Xs/11等屏幕的响应式布局问题

嘿,我看到你在SwiftUI的多机型适配上面碰壁了——放心,SwiftUI天生就支持适配iPhone 7/8/Xs/11这类不同尺寸的机型,而且有很多比硬编码屏幕尺寸更优雅的响应式方案。咱们先看看你当前代码里的小问题,再一步步优化到真正的自适应布局。

先说说你当前代码的问题

你现在用UIScreen.main.bounds.width来判断屏幕大小,这种方式有两个明显的问题:

  • 首先,你代码里let h = UIScreen.main.bounds.width明显是笔误,应该是bounds.height,这会导致你所有的高度判断完全错误,肯定会让布局在不同机型上乱掉;
  • 其次,直接依赖全局屏幕尺寸的方式不够灵活,比如在iPad分屏模式、或者未来的新设备上,这种硬判断很容易失效。

多机型适配的核心方案

下面是几个SwiftUI里常用的响应式布局技巧,结合你的代码来改造:

1. 用GeometryReader获取容器尺寸(而非全局屏幕)

GeometryReader可以获取它所在父容器的尺寸,而不是整个屏幕的尺寸,这样在嵌套布局或者分屏场景下更准确。你可以把全局的wh去掉,在需要的地方嵌入GeometryReader来获取动态尺寸。

比如修改你的PrayTimeView的顶部区域:

struct PrayTimeView: View {
    let prayTimes = Bundle.main.decode("time1.json")
    var manager = LocalNotificationManager()
    // 用环境变量获取尺寸类,替代硬编码屏幕宽度
    @Environment(\.horizontalSizeClass) var horizontalSizeClass
    
    var body: some View {
        GeometryReader { fullScreenProxy in
            VStack {
                VStack(spacing:0) {
                    Image("Prayer")
                        .resizable()
                        .overlay(ImageOverlay(items: prayTimes))
                        // 用屏幕高度的比例来设置图片高度,适配不同机型
                        .frame(height: fullScreenProxy.size.height * 0.28)
                    HStack{
                        Rectangle().fill(Color("Bg"))
                            .overlay(
                                VStack{
                                    Text(Date().DDMMYYYY)
                                        // 根据尺寸类调整字体,替代宽度判断
                                        .font(horizontalSizeClass == .compact ? .headline : .title)
                                        .foregroundColor(.white)
                                    Text(Date().DDMMYYYYIslamic)
                                        .font(horizontalSizeClass == .compact ? .headline : .title)
                                        .foregroundColor(.white)
                                }
                            )
                    }
                    // 用相对高度设置日期栏
                    .frame(height: fullScreenProxy.size.height * 0.08)
                }
                Spacer()
                VStack{
                    timeView(self.prayTimes)
                }
            }
        }
        .modifier(CustomMudifier())
        .navigationBarTitle("Gebet")
    }
}

2. 利用尺寸类(Size Classes)做自适应判断

SwiftUI提供了horizontalSizeClassverticalSizeClass环境变量,用来判断设备的尺寸类型:

  • 紧凑宽度(.compact):大部分iPhone竖屏状态
  • 常规宽度(.regular):iPad、iPhone横屏状态
  • 紧凑高度(.compact):iPhone横屏
  • 常规高度(.regular):iPhone竖屏、iPad

你可以用这个来调整字体、布局方向、控件大小,比硬判断屏幕宽度更可靠。比如在你的CountDownView里:

struct CountDownView: View {
    @EnvironmentObject var share: Share
    var items:[PrayTime]
    var manager = LocalNotificationManager()
    @State var interval : Int = 0
    let prayers = ["Fajr","Dhuhr","Asr","Maghrib","Isha"]
    @State var currentPrayerIndex = 0
    @Environment(\.horizontalSizeClass) var horizontalSizeClass
    
    var timerText : String {
        let dateForm = DateComponentsFormatter()
        dateForm.zeroFormattingBehavior = .pad
        dateForm.unitsStyle = .positional
        dateForm.allowedUnits = [.hour, .minute, .second ]
        return dateForm.string(from: TimeInterval(interval)) ?? "00:00"
    }
    
    var body: some View {
        HStack{
            Text("(-\(timerText))").font(Font.title.monospacedDigit()).bold()
                .foregroundColor(.white)
            Text(prayers[currentPrayerIndex])
                // 根据尺寸类调整字体大小
                .font(horizontalSizeClass == .compact ? .title : .largeTitle).bold()
                .foregroundColor(.white)
        }
        // ... 剩余代码保持不变
    }
}

3. 优化List布局:避免硬编码行高

你当前的ourView里用了多个固定高度的Rectangle来模拟List行,这不仅代码冗余,还很难适配不同屏幕。咱们可以用ForEach来遍历祈祷时间,利用List的原生自适应行高:

struct ourView: View {
    @EnvironmentObject var share : Share
    @State var onN = false{
        didSet{
            share.isOn = onN
            if onN{
                print("ON")
                let manager = LocalNotificationManager()
                manager.scheduleForAll(items: items)
            } else{
                print("off")
                UNUserNotificationCenter.current().removeAllPendingNotificationRequests()
            }
            UserDefaults.standard.set(onN, forKey: "on")
        }
    }
    var items: [PrayTime]
    var item: PrayTime
    @Environment(\.horizontalSizeClass) var horizontalSizeClass
    
    // 定义祈祷时间和对应图标的映射
    private let prayerDetails: [(name: String, timeKey: String, icon: String, index: Int)] = [
        ("Fajr", "fajr", "speaker", 0),
        ("Sunrise", "sunrise", "circle", -1),
        ("Dhuhr", "dhuhr", "speaker", 1),
        ("Asr", "asr", "speaker", 2),
        ("Maghrib", "maghrib", "speaker", 3),
        ("Isha", "isha", "speaker", 4)
    ]
    
    var body: some View {
        let bonN = Binding<Bool>(get: {self.onN}, set:{self.onN = $0})
        VStack {
            Toggle(" Notifications On", isOn: bonN)
                .frame(height:20)
            List{
                ForEach(prayerDetails, id: \.name) { detail in
                    HStack{
                        Text(detail.name)
                            .fontWeight(detail.index == share.currentPrayerIndex ? .heavy : .regular)
                        Spacer()
                        // 通过KeyPath获取对应时间
                        Text(item[keyPath: \.[detail.timeKey]])
                        Image(systemName: detail.icon)
                            .padding(.horizontal)
                    }
                    // 根据尺寸类调整行高
                    .frame(height: horizontalSizeClass == .compact ? 40 : 60)
                }
            }
            .disabled(horizontalSizeClass == .regular ? true : false)
            .foregroundColor(Color.black)
            .listStyle(GroupedListStyle())
        }
        .onAppear{
            self.onN = UserDefaults.standard.bool(forKey: "on")
        }
    }
}

4. 修复代码中的笔误

别忘了把你代码里的这个错误改掉:

// 原错误代码
let h = UIScreen.main.bounds.width
// 改成
let h = UIScreen.main.bounds.height

总结

SwiftUI的核心就是响应式布局,尽量避免硬编码固定尺寸,多依赖以下工具:

  • GeometryReader获取容器动态尺寸
  • 尺寸类(Size Classes)判断设备类型
  • Spacer、.frame(maxWidth: .infinity)等填充空间的方式
  • 原生控件的自适应特性(比如List的自动行高)

这样你的布局就能自动适配从iPhone 7到最新款iPhone的所有屏幕尺寸,甚至未来的新设备也能兼容。

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

火山引擎 最新活动