ํ์ ๊ณผ ๊ด๋ จ๋ ์ฝ๋๋ฅผ ๋ณด๋ค๋ณด๋, compact, regular์ ๊ฐ์ ์ฉ์ด๋ค์ด ๋ณด์๋ค. UITraitCollection์ ๋ฌด์์ผ๊น?
Size Classes
๋จผ์ , Apple์ด ๋ค์ํ ๋๋ฐ์ด์ค๋ค์ ์คํฌ๋ฆฐ ๋ชจ์์ ์ด๋ป๊ฒ ๋ ผ๋ฆฌ์ ์ผ๋ก ๊ด๋ฆฌํ๋ ์ง ๋ถํฐ ์์์ผ ํ๋ค. ๊ฒฐ๊ณผ์ ์ผ๋ก ๋งํ๋ฉด, 2๊ฐ์ Class๋ฅผ ๊ฐ์ง๊ณ ๊ด๋ฆฌํ๋ค. (์ฌ๊ธฐ์ Class๋ Programming์์์ Class๊ฐ ์๋๊ณ ๋ถ๋ฅ์ ์๋ฏธ์ด๋ค)
- Regular
- Compact
Regular๋ ์๋ฌด๋๋ ์ผ๋ฐ์ ์ธ ์ํฉ์ ๋งํ๋ ๊ฒ ๊ฐ๊ณ , Compact๋ ์ข ๋นก๋นกํ๋ค๋ผ๋ ๋๋์ ์ฃผ๋ ๊ฒ ๊ฐ๋ค. ํด๋น Size Class์ ๊ฒฝ์ฐ, HIG์ ๊ฐ๋ฉด ์ฐพ์๋ณผ ์ ์๋ค. Code๋ก๋ UIUserInterfaceSizeClass
๋ก ๊ตฌํ๋์ด ์๋ค.
UITraitCollection
horizontal, vertical size, display scale๊ณผ ๊ฐ์ iOS interface ํ๊ฒฝ์ ๋ด๊ณ ์๋ Class
์ด์ ๊ธ์์ Dark mode๋ฅผ ๋ณด๋ฉด์ elevated level์ ๋ํด ๋ฐฐ์ ๋ค. ๋ณด๋ฉด์ ๊ถ๊ธํ๋ ์ ์, ์ด๋ป๊ฒ device๊ฐ ์ด๋ฌํ ์ ๋ณด๋ค์ ์๊ณ ํ๊ฒฝ์ ๋ง๊ฒ ์์ ๋ณํ์ํค๋๋ ๊ฒ์ด์๋ค. ๊ทธ ํด๋ต์ด ๋ฐ๋ก UITraitCollection
์ด๋ค. ์์์ ๋ณด์๋ Size Classes ๋ค๋ ์ด UITraitCollection
์์ ๊ด๋ฆฌํ๋ ๊ฐ์ค ํ๋์ด๋ค.
- userInterfaceIdiom: device(iPhone, iPad, CarPlay)
- userInterfaceStyle: appearance
- userInterfaceLevel: VC์ level
UITraitCollection์ App ์คํ์ 1๊ฐ์ ๊ฐ๋ง ์กด์ฌํ๋ ๊ฒ์ด ์๋๋ค. ๊ฐ๊ฐ์ view, viewController๋ง๋ค ์กด์ฌํ๋ค. UITraitCollection ๊ฐ์ System์ผ๋ก๋ถํฐ UIScreen์ผ๋ก ์ ๋ฌ๋๊ณ , View ๊ณ์ธต ๊ตฌ์กฐ๋ฅผ ๋ฐ๋ผ ์ตํ์์ View๊น์ง ์ ๋ฌ๋๋ค.
๋ง์ฝ ์๋ก์ด View๊ฐ ์์ฑ๋๋ฉด, ๋ถ๋ชจ์ TraitCollection์ view์ ๋ฐ์ด๋ฃ์ด์ค๋ค.
Size
๊ฐ์ฅ ์์ฃผ ์ ํ๊ฒ ๋ ๊ฒ์, ํ์ ์ ๋ฐ๋ผ ์ด๋ป๊ฒ ์ฒ๋ฆฌ๋ ๊ฒ์ด๋๋ฅผ ๊ฒฐ์ ํ๋ ๊ฒ์ด๋ค. view.traitCollection.verticalSize
์ ๊ฐ์ ํ์์ผ๋ก ํ์ฌ device์ size class๋ฅผ ํ์ธํ ์ ์๋ค. ์ถ๊ฐ์ ์ผ๋ก iOS interface์ ๋ณํ๊ฐ ์ผ์ด๋ ๊ฒฝ์ฐ ๋์ํ๊ณ ์ถ๋ค๋ฉด, traitCollectionDidChange(_:)
๋ฅผ overrideํ์ฌ ์ฒ๋ฆฌํ ์ ์๋ค.
๋ง์ฝ interface ํ๊ฒฝ์ด ๋ณํํจ์ ๋ฐ๋ผ ๋ฐ์ํ๋ animation์ customizingํ๊ณ ์ถ๋ค๋ฉด willTransition(to:with:)
๋ฉ์๋๋ฅผ overrideํ๋ฉด ๊ฐ๋ฅํ๋ค.
Color
UITraitCollection
๊ณผ Dynamic Color๋ฅผ ํตํด dark Mode๋ก ๋ณ๊ฒฝ๋์์ ์ ์๋์ผ๋ก ๋ฐ์๋๋ค. ํ์ฌ view์์ ์ด๋ค Color๋ฅผ ์ฌ์ฉํ๋์ง ๋ณด๊ณ ์ถ๋ค๋ฉด, resolvedColor(with:)
๋ฅผ ์ฌ์ฉํ๋ฉด ๋๋ค.
let dynamicColor = UIColor.systemBackground
let traitCollection = view.traitCollection
let resolvedColor = dynamicColor.resolvedColor(with: traitCollection)
๋ง์ฝ dynamic color๊ฐ ์๋ ๊ฒ์ ๋ถ๋ฌ์ฌ ๊ฒฝ์ฐ ์ผ๋ฐ์ ์ผ๋ก ์ฌ์ฉํ๋ Color๊ฐ ๋ฆฌํด๋๋ค.
์ฝ๋๋ก Dynamic Color๋ฅผ ๋ง๋ค ์๋ ์๋ค.
let dynamicColor = UIColor { (traitCollection: UITraitCollection) -> UIColor in
if traitCollection.userInterfaceStyle == .dark {
return .black
} else {
return .white
}
}
์ด๋ฏธ์ง์ ๊ฒฝ์ฐ๋ ์๋ฆฌ๋ ๋น์ทํ์ฌ ์๋ตํ๋ค.
UITraitCollection.current
๊ทธ๋ผ UIView๋ ์ด๋ ์์ ์ ์ด ๊ฐ์ ์ฝ์ด์ ์ฒ๋ฆฌํ๋ ๊ฑธ๊น? ์ผ๋จ ํ์ฌ trait ๊ฐ์ ์ฝ์ด์์ผ ํ ๊ฒ์ด๋ค. ๊ทธ๋์ apple์ iOS 13์ ํ์ฌ์ traitCollection์ ์๋ ค์ฃผ๋ static ๋ณ์์ธ UITraitCollection.current
๋ฅผ ์ถ๊ฐํ๋ค.
class BackgroundView: UIView {
override func draw(_ rect: CGRect) {
// UIKit sets UITraitCollection.current to self.traitCollection
UIColor.systemBackground.setFill()
UIRectFill(rect)
}
}
UIView์ ๊ฒฝ์ฐ, draw method๊ฐ ํธ์ถ๋๊ธฐ ์ง์ ์ UIKit์ด UITraitCollection.current
๋ฅผ ์ค์ ํ๋ค. ๊ทธ๋ ๊ธฐ ๋๋ฌธ์ ํ์ ๋ผ์ธ์ด ๋์ํ๋ฉด์ dark mode๋ฅผ ๋ฐ์ํ ์ ์๊ฒ ๋๋ค.
์ด๋ ๊ฒ UITraitCollection.current
๊ฐ ์
๋ฐ์ดํธ ๋๋ ๊ฑด view๋ง์ด ์๋๋ค. ๋๋ค๋ฅธ ์ค์ ์์ ์ layoutSubviews
๊ฐ ํธ์ถ๋๊ธฐ ์ ์ด๋ค. ๊ทธ๋ ๊ธฐ ๋๋ฌธ์, ๋ง์ฝ VC๋ฅผ ๊ธฐ์ค์ผ๋ก traitCollection ๊ธฐ๋ฐ ๊ฐ์ ๋ณ๊ฒฝํด์ผ ํ๋ค๋ฉด, viewDidLoad
๊ฐ ์๋ viewWillLayoutSubviews()
ํน์ viewdidLayoutSubviews()
์์ ์ฒ๋ฆฌํด์ฃผ์ด์ผ ํ๋ค. UIPresentationController๋ ๋ญ์ง ๋ชจ๋ฅด๊ฒ ๋๋ฐ, ๋ค์ ๊ธ์์ ์์๋ณด์.
layoutSubviews()
์์ UI ๊ฐ์ ๋ณ๊ฒฝํด์ฃผ์๋ค๋ฉด, setNeedsLayout
์ด ํธ์ถ๋๊ณ , ๋ค์ view update cycle์ ๋ฐ์๋์ด dark modeํ๋ฉด์ด ๋ณด์ด๊ฒ ๋๋ค. VC๋ฅผ ๊ธฐ์ค์ผ๋ก ๋ค์๊ณผ ๊ฐ์ ์์๊ฐ ์๊ฒ ๋ค.
class ViewController: UIViewController {
override func viewWillLayoutSubviews() {
// Updated TraitCollection (UITraitCollection.current)
super.viewWillLayoutSubviews()
self.updateTitle()
}
private func updateTitle() {
if #available(iOS 13.0, *) {
guard traitCollection.userInterfaceStyle == .dark else {
self.title = "Light Mode"
return
}
self.title = "Dark Mode"
} else {
self.title = "Light Mode"
}
}
}
์ถ๊ฐ์ ์ผ๋ก trait์ด ๋ณ๊ฒฝ๋์์ ๋, ์๋ ค์ฃผ๋ callBack์ด ์๋ค. ๋ณ๊ฒฝ ์ฌํญ์ด ๋ฐ์ํ ์์ ์ ๋ฐ๋ก Color๋ฅผ ์ ์ฉํ๊ฑฐ๋ ํ ๋ ์ฉ์ดํ๊ฒ ์ฌ์ฉํ ์ ์๋ค.
ํ์ง๋ง.. ํด๋น Method๊ฐ ํธ์ถ๋ ์์ ์์์ UITraitCollection.current
๊ณผ ์ด method ์ธ๋ถ์์ TraitCollection์ ๊ฐ์ ๋ค๋ฅผ ์ ์๋ค. ์ด๋ ๊ทธ๋ด ์ ์๋ ๊ฒ์ด, traitCollection์ ๊ฐ์ ๊ฐ๊ฐ์ view๊ฐ ๊ฐ์ง๊ณ ์๊ธฐ ๋๋ฌธ์ด๋ค. ํ์ view๊น์ง ์
๋ฐ์ดํธ๋ TraitCollection์ด ์ ์ฉ๋์ง ์์ ์์ ์ Callback์ด ํธ์ถ๋ ์๋ ์๊ธฐ ๋๋ฌธ์ด๋ค. ๊ทธ๋ ๋ค๊ณ ๊ฐ์ ์ฃผ์ ๊ฐ์ ์๋ ๋
์์ ๊ฐ์ ๋ฐ๊พธ๋ฉด ๋์์ฑ ๋ฌธ์ ๊ฐ ๋ฐ์ํ๋ค. ์ฌ๋ฌ๋ชจ๋ก ๊ณจ์น์ํ ์ํฉ์ด๋ค.
์ฆ, ์ฐ๋ฆฌ๊ฐ ํ๊ณ ์ถ์ ๊ฒ์ traitCollection์ด ๋ณ๊ฒฝ๋ ์์ ์ ๊ฐ์ฅ ๋ฐ๋๋ฐ๋ํ, ์ฆ ๊ฐ์ฅ ์ ์ ํ ๋ ์์ ๊ธฐ๋ฐ์ผ๋ก Color๋ฅผ ๋ฐ๊พธ๊ณ ์ถ์ ๊ฒ์ด๋ค. ์ด๋ฅผ ํ๊ธฐ ์ํด์ Apple์ ์ธ๊ฐ์ง ๋ฐฉ๋ฒ์ ์ ์ํ๋ค.
let layer = CALayer()
let traitCollection = view.traitCollection
// Option 1 - resolvedColor๋ฅผ ํตํด traitCollection ๋ฐ์
let resolvedColor = UIColor.label.resolvedColor(with: traitCollection)
layer.borderColor = resolvedColor.cgColor
// Option 2 - performAsCurrent ํด๋ก์ ํ์ฉ
traitCollection.performAsCurrent {
layer.borderColor = UIColor.label.cgColor
}
// Option 3 - ์ง์ current TraitCollection ์
๋ฐ์ดํธ
// ์ด ๊ฒฝ์ฐ UITraitCollection์ ๋์ํ๋ Thread์์๋ง ์ ์ฉ๋์ด ๋ค๋ฅธ Thread์ ์ํฅ์ ์ฃผ์ง ์๋๋ค.
// ์ด ๋ฐฉ์์ performAsCurrent์ ๋ด๋ถ ๋์๊ณผ ๋์ผ
let savedTraitCollection = UITraitCollection.current
UITraitCollection.current = traitCollection
layer.borderColor = UIColor.label.cgColor
UITraitCollection.current = savedTraitCollection
traitCollectionDidChange(_:)
์ ๊ฐ์ ๋ฉ์๋๋ ๋น์ฐํ๊ฒ๋ Color ๋ณํ์ ๋ฐ๋ผ์๋ง ํธ์ถ๋๋ ๊ฒ์ด ์๋๋ค. ๊ทธ๋์ userInterfaceStyle ๋ณ๊ฒฝ์ ํ์ธํ ์ ์๋ ์ถ๊ฐ์ ์ธ API๋ ์ ๊ณตํ๋ค.
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
super.traitCollectionDidChange(previousTraitCollection)
if traitCollection.hasDifferentColorAppearance(comparedTo: previousTraitCollection) {
// Resolve dynamic colors again
}
}
TraitCollection์ ํ์ฉํ Style ๊ฐ์ ์ค์
๊ฐ๊ฐ์ view์ traitCollection์ ๊ฐ์ง๊ณ ์๋ค๋ฉด, ๋ถ๋ถ์ ์ผ๋ก ์ํ๋ mode๋ฅผ ์ ์ฉํ๋ ๊ฒ๋ ๊ฐ๋ฅํ ๊ฒ์ด๋ค.
class UIViewController {
var overrideUserInterfaceStyle: UIUserInterfaceStyle
}
class UIView {
var overrideUserInterfaceStyle: UIUserInterfaceStyle
}
overrideUserInterfaceStyle
์ด๋ผ๋ property๊ฐ iOS13๋ถํฐ ์๋ก ์๊ฒผ๋๋ฐ, ์ด ๊ฐ์ ๋ํด .light
, .dark
์ ๊ฐ์ด ์ง์ ํ๋ฉด ํ์ Subview๊น์ง ์คํ์ผ์ด overriding๋๋ค.
ํน์ ์ ์ฒด ์ฑ์ ๋ํด dark mode๋ฅผ ๊ฐ์ ํ๊ณ ์ถ์ ๊ฒฝ์ฐ, Info.plist
์ UIUserInterfaceStyle
๊ฐ์ .light
, .dark
์ ๊ฐ์ด ์ค์ ํ๋ฉด ๋๋ค.
TraitCollection Debug
Debug๋ฅผ ์ํ option์ด ์ถ๊ฐ๋์๋ค.
๋ง๋ฌด๋ฆฌ
- TraitCollection์ ๋ค์ํ trait(device, style, size)๋ฅผ ๊ฐ์ง๊ณ ์๋ ๊ฐ์ฒด์ด๋ค.
- ์ด๊ธฐํ์ parent view๋ก๋ถํฐ view์ ํ ๋น๋๋ค.
- UIScreen์ผ๋ก ๋ถํฐ view ๊ณ์ธต์ ๋ฐ๋ผ ์ ๋ฐ์ดํธ ๋๋ค.
- ์์ ๋ฌธ์ ๋๋ฌธ์
traitCollectionDidChange(_:)
์์์ ๊ฐ๊ณผ view๊ฐ ๊ฐ์ง๊ณ ์๋ ๊ฐ์ด ๋ค๋ฅผ ์ ์๋ค. - size์ธ์ ์๋ง ๋ฐ์ํ๊ธฐ ์ํ method(
traitCollection.hasDifferentColorAppearance(comparedTo:)
)๊ฐ ์๋ค. - layout์ด ๋ณํ๋๋ ์์ (
layoutSubview()
)๊ฐ trait์ ์ฌ์ฉํ๊ธฐ ๊ฐ์ฅ ์ข์ ์์ ์ด๋ค.
Dark mode์ TraitCollection์ด ์ฎ์ด๋ ๋ฐ๋์ ๊ตฌ๋ถํ์ฌ ์ ๋ฆฌํ๊ธฐ๊ฐ ์ฝ์ง ์์๋ค. ๋!