- 다크모드를 위해 고려해야 할 것
- 색상
- iOS는 전통적으로 색상을 하드코드 해왔음
- 다크 모드를 지원하기 위해서는 모든 색을 바꿔야 한다
- 따라서 semantic color라는 개념을 통해서, 모드에 따른 색상 변경을 지원하도록 한다.
- 각 semantic color는 다크모드일 때 단순히 light mode의 색을 뒤집는 것이 아니다
- 이 Semantic Color는 현재 시스템 모드에 따라 색을 동적으로 선택한다.
- material
- view&control
- 시스템이 제공하는 걸 써도 되지만, 커스텀도 가능하다.
- semantic color만들기
- assetCatalogue에서 appearance값을 바꿔주면 된다.
- 색상 뿐 아니라 이미지도 같은 방식으로 바꿔줄 수 있다.
- 시스템은 base Color와 elevatedColor를 구분할 수 있는 능력이 있다. 다크모드 일때는 두 색이 차이가 생긴다.
- base : 뷰 전체를 채우는 것
- elevated: 뷰의 일부를 채우는 것
- material 적용
- UIVisualEffectView에 적용
- UIBlurEffect = .systemMaterial
- material위에는 VibrantColor를 적용 가능
- Under the hood
- 어떻게 알고 색을 전환하지?
- UITraitCollection
- 실행 환경
- 시스템 라이트/다크 모드 설정
- base/elevation 여부
- 코드로 dynamic Color구현하는 법
let dynamicColor = UIColor { (traitCollection: UITraitCollection) -> UIColor in
if traitCollection.userInterfaceStyle == .dark {
return .black
} else {
return .white
}
}
- 위 클로저에 공급되는 traitCollection은 self.traitCollection이고, UIKit은 draw직전에 self.traitCollection에 UITraitCollection.current값을 설정한다.
- 모드 설정이 바뀌면, UIKit이 draw 메소드가 오버라이드 된 것을 알아차리고, setNeedsDisplay를 자동으로 호출해서 다시 draw가 되게 한다.
class BackgroundView: UIView {
override func draw(_ rect: CGRect) {
// 현재 그래픽 컨텍스트에 채울 색을 선택한다.
UIColor.systemBackground.setFill()
// 현재 선택한 색으로, rect를 채운다.
UIRectFill(rect)
}
}
- UIKit은 draw뿐 아니라, 레이아웃 과정에서도 traitCollection을 Current로 설정해준다.
- 하지만 해당 메소드들 바깥에서는 traitCollection이 current임이 보장되지 않는다.
- dynamic을 쓸수 있는 것은 UI 레벨에서만이다. 로우 레벨에서는 이 과정을 직접해야 한다. 여기에도 몇가지 방법이 있다.
let layer = CALayer()
let traitCollection = view.traitCollection
// 첫번째 옵션
let resolvedColor = UIColor.label.resolvedColor(with: traitCollection)
layer.borderColor = resolvedColor.cgColor
// 두번째 옵션
traitCollection.performAsCurrent {
layer.borderColor = UIColor.label.cgColor
}
// 세번쩨 옵션
// 스레드 안전하고, 해당 스레드에서만 영향을 미친다.
// 그래도 이후 로직의 사이드 이펙트를 없애기 위해서, 원래 값을 저장해 놓고 쓰는 게 좋다.
UITraitCollection.current = traitCollection
layer.borderColor = UIColor.label.cgColor
- 모든 traitCollection의 변화가 색의 변화를 일으키는 것은 아니다.
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
super.traitCollectionDidChange(previousTraitCollection)
if traitCollection.hasDifferentColorApperance(comparedTo: previousTraitCollection) {
// Resolve dynamic colors again
}
}
- image도 비슷하게 처리할 수 있다. 다만 UIImage는 이러한 능력이 없고, image에서 찾을 수 있는 imageAsset이 traitCollection을 받아서 이미지를 리졸브한다. -> 이 과정은 이미지뷰에서 일어난다.
- TraitCollection이 전달되는 과정
- UI 계층을 따라서 전파된다.
- iOS 13에서 동작이 달라졌다
- 뷰계층을 예측해서, 해당 계층의 traitCollection값을 일단 가진다.
- traitCollectionDidChange는 이 초깃값이 바뀐 경우에만 호출되게 된다. -> 이것이 이전 버전과 달라진 것.
- traitCollection은 layout단계에서 사용하는 것이 가장 좋다.
- 만약 일부 계층에서만 다크모드를 적용하고 싶다면, 이것을 오버라이드 하면 된다 -> overrideUserInterfaceStyle
- UIView, UIViewController에 존재
- traitCollection도 override할 수 있다.
- UIViewController, UIPresentationController에 존재
- status bar, indicatorView에 변화가 생겼다.
- attributedString을 그릴 때 다크 모드를 적용하려면, foregroundColor에 dynamic color를 설정할 것
- 웹 컨텐츠도 다크모드를 적용해야 한다.
- color-scheme에 지원하는 컬러 스킴을 명시하라
- prefers-color-scheme 미디어 쿼리로 커스텀 컬러와 이미지를 설정하자 -> 관련 영상이 있다.
- tvOS에서도 거의 대부분 지원된다.