Skip to content

Latest commit

Β 

History

History
312 lines (191 loc) Β· 13.7 KB

Unleash-the-UIKit-trait-system.md

File metadata and controls

312 lines (191 loc) Β· 13.7 KB

Unleash the UIKit trait system

πŸ—‚WWDC23 | Category : UI Frameworks

πŸ”—Β https://developer.apple.com/videos/play/wwdc2023/10057/

πŸ”– Discover powerful enhancements to the trait system in UIKit. Learn how you can define custom traits to add your own data to UITraitCollection, modify the data propagated to view controllers and views with trait override APIs, and adopt APIs to improve flexibility and performance. We'll also show you how to bridge UIKit traits with SwiftUI environment keys to seamlessly access data from both UIKit and SwiftUI components in your app.


Understanding traits

Traits

Traits are independent pieces of data that the system automatically propagates to every view controller and view in your app

UIKit provides many built-in system traits,

Untitled

iOS 17 λΆ€ν„° custom traits μ •μ˜ κ°€λŠ₯

Trait collections

Untitled

trait collections 은 traits 와 κ΄€λ ¨ λ³€μˆ˜λ“€μ„ 포함

Untitled

iOS 17 λΆ€ν„° trait collections 에 new API μΆ”κ°€

  1. ν΄λ‘œμ €λ₯Ό μ·¨ν•˜λŠ” μƒˆλ‘œμš΄ initializer

    • ν΄λ‘œμ € λ‚΄λΆ€μ—μ„œ mutableTraits λ₯Ό λ°›λŠ”λ‹€
    • mutable container λŠ” μƒˆλ‘œμš΄ ν”„λ‘œν† μ½œ UIMutableTraits λ₯Ό conform
    • initializer κ°€ immutable UITraitCollection μΈμŠ€ν„΄μŠ€λ₯Ό 리턴
  2. κΈ°μ‘΄ trait collection 의 값듀을 μˆ˜μ •ν•˜μ—¬ μƒˆλ‘œμš΄ μΈμŠ€ν„΄μŠ€λ₯Ό μƒμ„±ν•΄μ£ΌλŠ” modifyingTraits λ©”μ†Œλ“œ

직접 train collections λ₯Ό μ΄λ ‡κ²Œ μƒμ„±ν•΄μ„œ μ‚¬μš© ν•  μˆ˜λ„ μžˆμ§€λ§Œ λŒ€λΆ€λΆ„ μš°λ¦¬λŠ” trait environment μ—μ„œ trait collections 을 μ–»μ–΄μ„œ μ‚¬μš©ν•œλ‹€.

Trait environments

Untitled

trait environments μ—λŠ” UIWindowScnene, UIWindow, UIPresentationController, UIViewController, UIView κ°€ 있고 각각 고유의 trait collection 을 κ°–λŠ”λ‹€.

Trait hierarchy

Untitled

  • trait environment 은 앱을 톡해 flow νƒ€λŠ” trait hierarchy 둜 μ—°κ²°λ˜μ–΄ μžˆλ‹€
  • 각각의 trait environment λŠ” parent environment 의 trait 값듀을 상속 λ°›λŠ”λ‹€

View controller and view trait hierarchy

Untitled

iOS 17 이전

  • ViewController λŠ” Parent View Controller 의 trait 을 직접 상속
  • View Controller 에 μ†ν•œ View 듀은 View Controller 둜 λΆ€ν„° trait 상속
  • View Controller 에 μ†ν•˜μ§€ μ•Šμ€ View 듀은 superView 둜 λΆ€ν„° trait 상속

Untitled

β†’ View hierarchy 에 μžˆλŠ” trait 의 ν”Œλ‘œμš°λŠ” View Controller 에 μ†ν•œ View μ—μ„œ λŠκΈ΄λ‹€

Untitled

iOS17 μ—μ„œλŠ” trait hierarchy λ₯Ό 톡합

  • View Controller λŠ” View 의 superview μ—μ„œ trait 상속

β†’ View Controller κ°€ 이제 view hierarchy μ—μ„œ traits λ₯Ό 상속 λ°›κΈ° λ•Œλ¬Έμ—, ViewController κ°€ μ—…λ°μ΄νŠΈλœ trait 을 λ°›κΈ° μœ„ν•΄μ„œ ViewController 의 View λŠ” λ°˜λ“œμ‹œ hierarchy 에 μžˆμ–΄μ•Ό 함

β†’ View κ°€ hierarchy 에 μΆ”κ°€ 되기 전에 ViewController 의 trait collection 에 μ ‘κ·Όν•˜λ©΄, ViewController λŠ” trait 의 μ΅œμ‹ κ°’μ„ κ°€μ Έμ˜¬ 수 μ—†λ‹€

View controller trait updates

Untitled

viewWillAppear λŠ” View κ°€ hierarchy 에 μΆ”κ°€ 되기 전에 항상 호좜됨

viewIsAppearing

  • viewIsAppearing is called after viewWillAppear
  • View κ°€ hierarchy 에 μΆ”κ°€λ˜λ©΄, View Controller, View λͺ¨λ‘ μ΅œμ‹  triat collection 을 κ°–κ²Œλ¨
  • viewIsAppearing is a drop-in replacement for nearly all cases where you’re using viewWillAppear
  • this new method back-deploys all the way to iOS 13.

View trait updates

Untitled

  • iOS 17 μ—μ„œλŠ” view trait μ—…λ°μ΄νŠΈμ˜ 일관성과 μ„±λŠ₯ ν–₯상
  • View λŠ” hierarchy μ•ˆμ— μžˆμ„ λ•Œλ§Œ trait collection 을 μ—†λ°μ΄νŠΈν•¨
  • 일단 hierarchy μ•ˆμ— 있으면, 각각의 view λŠ” layout 이 μˆ˜ν–‰λ˜κΈ° 전에 μ¦‰μ‹œ μžμ‹ μ˜ trait collection 만 μ—…λ°μ΄νŠΈ
  • κ°€μž₯ best practice λŠ” layout λ™μ•ˆ trait 을 사 용
    • View λŠ” layoutSubViews() λ©”μ†Œλ“œ μ•ˆμ—μ„œ traitCollection 을 μ‚¬μš©
    • layoutSubViews() λŠ” View μ—μ„œ setNeedsLayout() 이 호좜 λ λ•Œλ§ˆλ‹€ λ™μž‘ ν•˜κΈ° λ•Œλ¬Έμ—, μ—¬λŸ¬λ²ˆ 호좜 ν•  λ•Œ 반볡된 μž‘μ—…μ„ 피해야함

Defining custom traits


Uses for custom triats

When to define a new custom trait

  • μ—¬λŸ¬ children μ—κ²Œ 데이터λ₯Ό 전달 ν•΄μ•Ό ν• λ•Œ
    • ParentViewController β†’ Muliple ChildViewControllers
    • SuperView β†’ all of its subviews
  • λ‹€λ₯Έ μ»΄ν¬λ„ŒνŠΈμ— 데이터λ₯Ό 전달 ν• λ•Œ
    • λ§Žμ€ λ ˆμ΄μ–΄λ“€μ΄ nested 된 경우, direct connection 은 μ—†μ„λ•Œ
  • ν¬ν•¨ν•˜κ³  μžˆλŠ” 뷰컨 같은 environment 정보듀을 λ„˜κ²¨μ€„λ•Œ

trait system 이 κ°•λ ₯ν•˜κΈ΄ ν•˜μ§€λ§Œ, 데이터 μ „λ‹¬λ‘œμ„œ μ‚¬μš©ν•˜λŠ”κ±΄ 쒋지 μ•ŠμŒ

μ„±λŠ₯을 μœ„ν•΄μ„œλŠ” 직접 데이터λ₯Ό μ „λ‹¬ν•˜λŠ”κ²ƒμ΄ μ•„λ‹Œ, 값을 μΆ”κ°€ν• λ•Œ μ‚¬μš©ν•΄μ•Ό ν•œλ‹€

Custom trait

Untitled

trait 을 μ •μ˜ ν•˜λ©΄, λ°”λ‘œ UITraitCollection, UIMutableTraits 의 μƒˆλ‘œμš΄ API μ‚¬μš© κ°€λŠ₯

struct 자체λ₯Ό ν‚€κ°’μ²˜λŸΌ μ‚¬μš©

Untitled

Custom trait μ •μ˜ ν• λ•Œ extension 도 써주어야 함

Untitled

색상에 영ν–₯을 μ£ΌλŠ” trait 은 λΉ„μš©μ΄ λΉ„μ‹ΈκΈ° λ•Œλ¬Έμ—, λ“œλ¬Όκ²Œ λ³€ν™”ν•˜λŠ” trait μ—λ§Œ μ‚¬μš©

name β†’ 디버거에 ν‘œμ‹œλ˜λŠ” 이름

identifier

  • 인코딩 같은 μΆ”κ°€ κΈ°λŠ₯에 μ ν•©ν•œ trait 으둜 λ§Œλ“€μ–΄μ€Œ
  • reverse-DNS 포멧 μ‚¬μš©
  • μ•±λ‚΄μ—μ„œ μœ λ‹ˆν¬ ν•˜λ„λ‘

Untitled

ν…Œλ§ˆμ— λ”°λ₯Έ μ»€μŠ€ν…€ λ‹€μ΄λ‚˜λ―Ή 컬러 μ •μ˜

Untitled

Custom trait best practices

Untitled

  • κ°„λ‹¨ν•œ struct λ‚˜ enum 같은 value types μ‚¬μš© Class 기반의 trait λŠ” ν”Όν•˜κΈ°
  • κ°€μž₯ 효율적인 데이터 νƒ€μž…μ€ Bool, Int, Double, enum with Int raw value enum with Int raw value 이 κ°€μž₯ μœ μš©ν•œ 데이터 νƒ€μž…
  • μ»€μŠ€ν„°λ¨Έ νƒ€μž…μ€ Equatable ν”„λ‘œν† μ½œμ„ κ΅¬ν˜„ν•΄μ•Όν•¨

Custom triats in Swift and Objective-C

Untitled

Obj-C 도 μ‚¬μš© κ°€λŠ₯!

but… μ˜΅μ”¨, Swift 에 각각 cutom trait μ •μ˜ν•΄μ£Όμ–΄ν– ν•œλ‹€

Applying overrides


Trait overrides

Untitled

Trait overrdies λŠ” trait hierachy λ‚΄λΆ€μ˜ 데이터λ₯Ό μˆ˜μ •ν•˜κΈ° μœ„ν•œ λ§€μ»€λ‹ˆμ¦˜μ΄λ‹€

iOS 17 λΆ€ν„°λŠ” trait overrides λ₯Ό 더 μ μš©ν•˜κΈ° μ‰¬μ›Œμ§

각각의 trait envirionment classesdp traitOverrides ν”„λ‘œνΌν‹°κ°€ 생김

Untitled

trait overrides λŠ” 트리 ꡬ쑰 μ•ˆμ—μ„œ μ–΄λ–€ μœ„μΉ˜μ—μ„œλ“  trait 값을 λ°”κΎΌλ‹€

이 쀑 ν•œ ν™˜κ²½μ—μ„œ 적용되면 λͺ¨λ“  ν•˜μœ„ 계측에 λ‹€ 적용

Untitled

parent μ—μ„œ 적용된 traitCollection 은 상속받고 μžˆλŠ” child 에도 적용

Untitled

μ•„λ§ˆ trait override κ°€ μ¦‰μ‹œ 반영 λ˜μ§€ μ•Šμ„ 수 μžˆλ‹€

  • View λŠ” layout 직전에 trait collection 을 μ—…λ°μ΄νŠΈ ν•˜κΈ° λ•Œλ¬Έμ— view 의 trait override μˆ˜μ •μ€ layoutSubviews μ „κΉŒμ§€λŠ” λ°”λ‘œ λ°˜μ˜λ˜μ§€ μ•ŠλŠ”λ‹€
  • traitOverrides ν”„λ‘œνΌν‹°λŠ” override κ°€ μ μš©λ˜μ—ˆλŠ”μ§€, μ œκ±°λ˜μ—ˆλŠ”μ§€λ„ ν™•μΈν•΄μ€Œ

Untitled

  • contains λ©”μ†Œλ“œλ‘œ 이미 μžˆλŠ” 거라면 제거, μ—†μœΌλ©΄ 적용

  • traitOverrides λŠ” 값을 μ„ΈνŒ…ν•˜κΈ° μœ„ν•œ input mechanism 이닀

    β†’ Trait value λ₯Ό read ν•˜κΈ° μœ„ν•΄μ„œλŠ” traitCollection ν”„λ‘œνΌν‹°λ₯Ό μ‚¬μš©ν•΄μ•Ό 함

  • override κ°€ μ„ΈνŒ…λœκ²ƒμ΄ 없을 λ•Œ, traitOverrides μ—μ„œ 읽으면 exception λ°œμƒ


Maximize performance

Untitled

  • 각각의 traitOverride λŠ” λΉ„μš©μ΄ λ“€κΈ° λ•Œλ¬Έμ— κΌ­ ν•„μš”ν•œ κ²½μš°μ—λ§Œ μ‚¬μš©
  • 값을 μˆ˜μ •ν•˜λŠ” 경우, μ‹œμŠ€ν…œμ€ ν•˜μœ„ trait collection 을 λͺ¨λ‘ μ—…λ°μ΄νŠΈ ν•΄μ•Όν•˜κΈ° λ•Œλ¬Έμ—, traitOverride λ₯Ό μˆ˜μ •ν•˜λŠ” λΉˆλ„μˆ˜λ₯Ό μ΅œμ†Œν™” 해야함
  • ν•„μš”ν•œ 계측에 κ°€μž₯ κ°€κΉŒμš΄ 곳에 μˆ˜μ •ν•˜μž

Handling changes

Untitled

βœ” **traitCollectionDidChange(_:) is deprecated in iOS17**

  • μ–΄λ–€ traitCollection 을 care ν•˜λŠ”μ§€ μ‹œμŠ€ν…œμ€ μ•Œμ§€ λͺ»ν•˜κΈ° λ•Œλ¬Έμ— λͺ¨λ“  traitCollection 이 λ³€ν• λ•Œλ§ˆλ‹€ 계속 λ©”μ†Œλ“œλ₯Ό μ½œν•΄μ•Όν•¨
  • 각각의 trait 에 λŒ€ν•œ change λ₯Ό λ“±λ‘ν•˜λŠ” new API
    • target action 방식 λ˜λŠ” clouse
    • subclass μ—μ„œ λ©”μ†Œλ“œλ₯Ό override ν• ν•„μš” μ—†κΈ° λ•Œλ¬Έμ— μ–΄λ””μ„œλ“  changes λ₯Ό μ˜΅μ €λΉ™ κ°€λŠ₯

Untitled

iOS 17 이전 버전을 λŒ€μ‘ν•΄μ„œ traitCollectionDidChange λ₯Ό 계속 μ‚¬μš©ν•΄μ•Ό ν•œλ‹€λ©΄,

μ–΄λ–€ νŠΉμ • trait 에 λŒ€ν•œ change λ₯Ό 바라보고 μžˆλŠ”μ§€μ— λŒ€ν•œ κ΅¬ν˜„μ„ 좔가해야함

⬇️ iOS 17 ⬇️

Closure

Untitled

  • UITraitHorizontalSizeClass 같은 λͺ¨λ“  μ‹œμŠ€ν…œ trait 에 λŒ€ν•œ 심볼이 μƒˆλ‘œ 생김

  • λͺ¨λ“  trait 에 λŒ€ν•œ 변화에 λŒ€ν•΄ ν˜ΈμΆœλ˜μ§€ μ•ŠκΈ° λ•Œλ¬Έμ— 더이상 old, new trait 값듀을 비ꡐ할 ν•„μš”κ°€ μ—†μŒ

  • trait 에 λ³€ν™”κ°€ 생긴 객체가 첫번째 νŒŒλΌλ―Έν„°λ‘œ μ „λ‹¬λ˜κΈ° λ•Œλ¬Έμ—, weak 레퍼런슀 캑쳐 ν•  ν•„μš” 없이 λ°”λ‘œ μ‚¬μš© ν•  수 있음 (self: Self 둜 μ‚¬μš©ν•΄μ•Όν•¨)

  • λ‹€λ₯Έ trait environment 에 λŒ€ν•œ trait changes 도 μ˜΅μ €λΈŒ κ°€λŠ₯

  • λ‹€λ₯Έ λ·°μ—μ„œ μ‹œμŠ€ν…œ trait, μ»€μŠ€ν…€ trait λͺ¨λ‘ λ³€ν•˜λ©΄ ν΄λ‘œμ € μ‹€ν–‰

  • register ν•œ 뷰의 νƒ€μž…μ„ 첫번째 νŒŒλΌλ―Έν„°λ‘œ μ μ–΄μ€Œ

target-action

Untitled

  • target νŒŒλΌλ―Έν„° μƒλž΅ κ°€λŠ₯ (μƒλž΅ν•˜λ©΄ registerForTraitChange ν˜ΈμΆœλ˜λŠ” 같은 객체)
  • view, previousTraitCollection νŒŒλΌλ―Έν„°

### Semantic system trait sets

Untitled

  • μ‹œμŠ€ν…œ λ‹€μ΄λ‚˜λ―Ή μ»¬λŸ¬μ— 영ν–₯을 μ£ΌλŠ” system trait 리턴
  • UIImage(named:) μ‚¬μš©ν•΄μ„œ 이미지 λ‘œλ“œμ‹œ system traits 의 subset 리턴

Unresgistration

Untitled

Maximize performance

  • μ‹€μ œλ‘œ μ˜μ‘΄ν•˜λŠ” trait 만 register 등둝
  • 변화에 λŒ€ν•΄ μ¦‰μ‹œ λ°˜μ‘ν•˜μ§€ μ•Šλ„λ‘ 해라 layoutSubviews λ©”μ†Œλ“œ λ‚΄λΆ€μ—μ„œ trait 을 μ‚¬μš©ν•œλ‹€λ©΄, setNeedsLayout 을 ν˜ΈμΆœν•΄μ„œ trait change λ₯Ό invalidate 해라 ? β†’ View κ°€ layoutSubviews λ₯Ό λ°›κ³ , μ¦‰μ‹œ μ—…λ°μ΄νŠΈλ₯Ό ν•˜μ§€ μ•Šλ„λ‘

SwiftUI bridging

UIKit β†’ SwiftUI 둜 데이터λ₯Ό μ „λ‹¬ν•˜λŠ” λŠκΉ€ μ—†λŠ” μƒˆλ‘œμš΄ 방법이 μƒκΈ°κ²Œ 됨!

Intergrating UIKit and SwiftUI

Untitled

UIKit 의 Custom trait 은 SwiftUI 의 environment keys 와 맀우 μœ μ‚¬

Untitled

λ‘˜μ„ λΈŒλ¦Ώμ§€ ν•˜κΈ° μœ„ν•΄μ„œλŠ” UITraitBridgedEnvironmentKey ν”„λ‘œν† μ½œλ§Œ 채택해주면 됨

UIKit β†’ SwiftUI

Untitled

SwiftUI β†’ UIKit

Untitled

Next steps

Untitled