有时候,我们需要根据不同尺寸的设备适配最合适的视图布局。
这在SwiftUI项目中如何操作呢?
如上图所示,我们在SwiftUI中自定义了一个多项列表视图,不过它在不同尺寸的设备中显示的并不完美。在大屏设备尾部留出了太多空间,而在小屏设备中最后一列又显示不全,真是让人伤脑筋啊!
不过别急!
在本篇博文中,我们将介绍一些在不同设备中适配SwiftUI视图的小技巧,相信小伙伴们看完之后一定能妥妥的搞定适配问题了。
那还等什么呢?
Let’s go! 😉
要想适配不同尺寸的设备,首先我们要做的第一件是就是能够快速预览不同设备的界面。
我们可以通过为SwiftUI视图添加特定的修改器,在Xcode预览界面中快速浏览其在对应设备中的外观:
struct TestView_Previews: PreviewProvider {
static var body: some View {
NavigationView {
// 实际测试视图的布局描述
}
}
static var previews: some View {
Group {
body
.previewDevice(.init(rawValue: "iPhone 13 Pro Max"))
body
.previewDevice(.init(rawValue: "iPhone 13 Pro"))
body
.previewDevice(.init(rawValue: "iPhone 12 mini"))
body
.previewDevice(.init(rawValue: "iPod touch (7th generation)"))
}
}
}
在以上预览视图代码中,我们分别将测试视图放到 iPhone 13 Pro Max、iPhone 13 Pro、iPhone 12 mini以及iPod touch等四种不同尺寸设备中去预览显示。
在不同设备中预览我们的视图只是第一步,接着我们需要在运行中确定到底当前在何种设备中运行。
或者换句话说:当前运行的设备屏幕尺寸是多少?
为了获取当前运行设备的名称,我们只需一行代码:
UIDevice.current.name
同样,为了获取当前运行设备屏幕的尺寸,即逻辑分辨率,同样只需一行代码:
UIScreen.main.bounds
这里,我们只需要设备屏幕的尺寸。
能够在运行中确定设备的尺寸之后,我们就可以根据不同设备的尺寸大小,来动态调整我们视图的界面了。
首先,写一个对应的布局尺寸结构:
fileprivate enum LayoutSize {
case tiny, small, middle, large
// 根据当前设备计算实际的布局尺寸
static func calc() -> Self {
let width = UIScreen.main.bounds.width
if width <= 320 {
return .tiny
}else if width <= 360 {
return .small
}else if width <= 390 {
return .middle
}else{
return .large
}
}
// 列表中单个列的宽度
var unitWidth: CGFloat {
let w: CGFloat
switch self {
case .tiny, .small, .middle:
w = 30
case .large:
w = 38
}
return w
}
}
在以上代码中,我们根据前面4种典型iPhone设备的宽度,创建了4种对应的尺寸枚举类型。
我们希望在 large 和 middle 尺寸类型的设备中,最大化每列的宽度;而在较小尺寸 small 和 tiny 等设备中,紧凑的显示每列,并按需合并若干列,达到最优利用空间的目的:
if layoutSize == .large || layoutSize == .middle {
Text("\(trace.totalGainedScore)")
.fontWeight(.heavy)
.font(.body)
.foregroundColor(scoreColor)
.opacity(1.0)
.frame(minWidth: layoutSize.unitWidth)
baseScoreView
.underline(true, color: stateColor)
.fontWeight(.bold)
.font(.callout)
.foregroundColor(stateColor)
.frame(minWidth: layoutSize.unitWidth)
}else{
HStack(alignment: .top, spacing: 5) {
Text("\(trace.totalGainedScore)")
.fontWeight(.heavy)
.font(.body)
.foregroundColor(scoreColor)
.opacity(1.0)
baseScoreView
.underline(true, color: stateColor)
.fontWeight(.bold)
.font(.callout)
.foregroundColor(stateColor)
}.frame(minWidth: 50)
}
不过,即使采用上面的优化方法,在最小的 tiny 设备界面中,最后一列的显示仍然捉襟见肘。
这时,我们需要更进一步,大幅度更改列表的布局,将第二列的内容放到第一列最底部显示,继续压榨显示空间:
if layoutSize == .tiny {
VStack(spacing: 2) {
Text(Model.shortDateFt.string(from: trace.date ?? Date.distantPast))
.fontWeight(.light)
.font(.footnote)
.foregroundColor(.slateGray)
CommonUI.hiveScoringTracesView(trace, model: model)
// tiny设备中,需要将第二列显示的状态内容放到第一列的底部
stateView
.foregroundColor(stateColor)
.frame(minWidth: layoutSize.unitWidth)
}.frame(minWidth: 65.0)
}else{
VStack {
Text(Model.shortDateFt.string(from: trace.date ?? Date.distantPast))
.fontWeight(.light)
.font(.footnote)
.foregroundColor(.slateGray)
CommonUI.hiveScoringTracesView(trace, model: model)
}.frame(minWidth: 65.0)
stateView
.foregroundColor(stateColor)
.frame(minWidth: layoutSize.unitWidth)
}
现在,运行代码看一下布局适配的效果:
如上图所示,现在不同设备中的显示效果是不是比一开始要好很多呢?
我们最大化的利用了空间,做到了视图适配的收放自如,棒棒哒!🚀
在本篇博文中,我们利用SwiftUI代码,在Xcode预览中快速查看了不同设备的界面,并据此动态调整视图的布局,达到了最优空间之利用的目的。
最后,感谢各位观赏,我们下次再会!😎