这两个 Session 主要介绍了在 Xcode 8 与 iOS 10 中,进行 App 适配更方便的地方。
现在由于设备越来越多,屏幕的尺寸也在不断增加。从现在还支持的最古老的设备 4S 开始,到屏幕尺寸最大的 iPad Pro,再加上屏幕方向的支持和 iPad 中的 splitView,各种组合大概有 300 多种,要手动管理的话十分麻烦。
不过 Apple 帮你简化了大部分工作,现在需要关心的主要是 Traits。下面这张图简要介绍了 Traits 的几个例子:
下面首先介绍 Size Classes。Size Classes 在 iOS 8 中引入,它主要用于简化以下几个方面的操作:屏幕尺寸、屏幕方向、适配性(iPad splitView)。有了 Size Classes,你需要关心的只是可用空间的大小,你可以对屏幕内容有更精确的控制。
Size Classes 主要分为两类:horizontalSizeClass 与 verticalSizeClass。每种又可细分为两类:Compact 与 Regular。
有了 Size Classes,只需要考虑上面图片中四种组合。不过一般来说主要考虑对宽度改变做出相应调整。Size Class 可以根据屏幕的情况动态调整(旋转等),并且还能方便的根据设备的情况对 View Controller 进行调整。
下面介绍了一个重要的方法,针对 Trait 改变时进行调整,主要应用在 View Controller 与 View 中。
override func traitCollectionDidChange(_ previousTraits: UITraitCollection?) {
super.traitCollectionDidChange(previousTraits)
if previousTraits?.horizontalSizeClass != traitCollection.horizontalSizeClass {
switch traitCollection.horizontalSizeClass {
case .compact:
setupConstraintsForCompactEnvironment()
case .unspecified: fallthrough
case .regular:
setupConstraintsForRegularEnvironment()
}
} }
在每个 UITraitEnvironmet
中都会调用 traitCollectionDidChange(:)
方法,你只重写需要进行更改的部分。而在 Interface Builder、Asset catalog、UIAppearance 中,系统会帮你自动处理这个方法。
总结一下:
- Trait 用于描述整个环境的变化,包括 Layout、外观、兼容性等。
- 如果需要根据 Trait 的变化进行调整,重写
traitCollectionDidChange(:)
方法。 - Size Classes 简化了你需要考虑的事。
- 系统会帮你处理大部分的工作。
下面有个 Demo 展示,主要是关于 Xcode 8 中如何方便的根据设备情况对 UI 进行定制。具体可以去官网看看 Session 222 的后半部分。
Session 233 主要做了几件事:
- 介绍了 Size 和 Size class 的基础
- 如何最有效地使用 UIKit
- 在前面介绍的基础上提升体验
Size 和 Size class 的基础
之后介绍了一些因为不同的 Size 产生的差别,如 view 与 controller、presentation(在 modal 上的不同)、split view,session 222 也已经提过。
如何最有效地使用 UIKit
- Xcode 的工具:
- Interface Builder
- Asset Catalogs
- UIKit
- Auto Layout
- UITraitCollection
- Dynamic Type
- Layout Guides
- UIAppearance
这里着重介绍了几个功能:
Asset Catalogs
Alignment Inset:可以定义一个 Inset,将图片最重要的部分展现出来。
Slicing:类似 Android 的点九,现在可以在 Asset Catalogs 里面配置需要切分的图片,将可拉伸与不可拉伸部分分隔开
,保证边缘的如圆角部分不会因图片拉伸产生变形。
Dynamic Type
可以根据 Trait Collection 的变化调整字体。
Layout Guides
根据 Margin 来进行 Layout:
自动根据合适的阅读宽度调整内容的范围以及字体的大小:
UIAppearance
下面这段代码介绍了一种方法,用于根据 Size class 来调整 UINavigationBar 的 BackgroundImage:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions:
[NSObject: AnyObject]?) -> Bool {
//获取垂直方向为 compact 时的 trait
let verticalCompactTrait = UITraitCollection(verticalSizeClass: .compact)
//通过该 trait 获取 UINavigationBar compact 时的外观
let compactAppearance = UINavigationBar.forTraitCollection(verticalCompactTrait)
//设置背景图片
compactAppearance.setBackgroundImage(nil, for: .default)
let verticalRegularTrait = UITraitCollection(verticalSizeClass: .regular)
let verticalAppearance = UINavigationBar.forTraitCollection(verticalRegularTrait)
verticalAppearance.setBackgroundImage(UIImage(), for: .default)
}
可以达到如下的效果:
虽然前面有提到 trait,但是还有一些不太理解,查了一下官方文档:
A trait collection describes the iOS interface environment for your app, including traits such as horizontal and vertical size class, display scale, and user interface idiom. To create an adaptive interface, write code to adjust your app’s layout according to changes in these traits.
简单来说,trait collection 可以表示某种环境下的 UI,比如 size class,屏幕的 scale 等等。使用 trait collection,就可以针对特定环境下的 UI 进行更改,而不影响其他环境的 UI。
于是你就可以根据这些 trait collection,根据不同的 trait 组合来调整 UI。这也是下面官方要讲的:
- 为不同设备、屏幕方向、尺寸进行设计
- 不要这么做:只判断屏幕尺寸是否完全相等
- 最好这样:设定一些条件来判断:制定规则
- 使用 Size class
- 将值与阈值相比
- 将值与其它值进行比较
- 实现这些设计
- 找出 app 的尺寸数据
- 使用规则来决定应该如何设计
- 将设计应用到 UI 中去
- 关于代码适合放置的位置:
- 这部分代码不适合放在
viewDidLoad()
中,因为这时候 superview 还没有确定,layout 也还没有起作用。只有在整个设计中不变的东西才放在init()
loadView()
viewDidLoad()
中。 - 这部分代码适合放在
viewWillLayoutSubviews()
中,这时候 view 已经在 superview 中,并且 layout 已经开始了。在这个方法里面适合调整一些 view controller 相关的东西。 - 但要小心使用:
- 尽量少放代码在这个方法中
- 找出那些上次改变了的东西
- 小心不要引发 layout 循环
- 这部分代码不适合放在
- 复用组件
- 设计一些在不同设计中通用的部件
- 每一个部件都是一个 view controller
- view 的层级与约束
- 与其它 view controller 的联系
- 与 app 中其它部分的联系
关于 layout,官方有个小 demo,演示如何实现以下效果:
即在屏幕旋转时,UIStackView
内部元素的方向也跟着旋转。
其实代码没几行:
class SimpleExampleViewController: UIViewController {
@IBOutlet var stackView : UIStackView!
override func viewWillLayoutSubviews() {
let size = view.bounds.size
let useWideDesign = size.width >= size.height
//根据屏幕的宽高比较,决定是否要修改 stactView 内容方向
if useWideDesign {
stackView.axis = .horizontal
} else {
stackView.axis = .vertical
}
}
}
关于部件的复用这一块,这个 session 中还有一个比较长的 demo,地址在这里 ,需要 Xcode 8 与 iOS 10。
总而言之,这部分提出了一种类似于 web 中响应式设计的解决方案,在 web 中已经应用了挺久了,但是在原生的 app 中还比较新,并且也有一些拓展,根据屏幕的 trait 能进行更多的定制,包括点击操作的效果等等。虽然感觉在原生 app 中做这件事比较怪异,不过也期待有更多有趣的设计~