iOS) 다크모드 대응
내용
- 2가지 방법으로 다크 모드를 대응해보자
- Color Assets, UIColor extension
- 라이트 / 다크 모드를 제한해 보자
먼저 Xcode 와 시뮬레이터에서 라이트 / 다크 모드를 확인할 수 있는 방법을 알아보자.
- 다음과 같이 Xcode 에서 Appearance 를 조절하면 런타임 중에도 확인 가능하다.
- 시뮬레이터에서 [설정] → [개발자] → [Appearacne] 에서 토글로 모드를 바꿀 수 있다.
자 이제 대응하는 2가지 방법을 알아보자!
😎 1.Color Assets 으로 대응하기
- color assets 를 위한 새로운 파일을 추가하고 싶다면 Asset Catalog 를 추가해서 Color Set 를 추가하거나
- 기존의 Asset 에서 + 버튼을 눌러서 Color Set 을 추가해도 된다.
- Appearance 에서 None 이 아닌 Any, Dark 를 선택해준다. (Any, Light, Dark 를 골라도 가능하다 대신 색을 3개를 지정해주어야 한다. 편의상 Any, Dark 를 선택해도 상관없다.)
- 다음과 같이 다크모드일 경우 컬러도 등록해준다.
backgroundColorAsset
로 이름을 지정해주었다.
- 이제부터 스토리보드에서 사용가능하다! (스토리보드에서 Appearance 를 조절하면 그에 맞는 색으로 바뀐다.)
backgroundColorAsset
이 라이트모드에서는 하얀색, 다크모드에서는 검은색을 보여준다. (우리가 아는systemRed, systemBackground 등 System Color 는 모드에 따라 색이 서로 다르다)
- System Background 로 배경색을 설정해두면 Appearance 를 변경해주면 다음과 같이 바뀐다. 또한 Appearance 를 선택해서 모드에 따른 뷰의 변화를 편리하게 볼 수 있다.
Color Assets programmatically 하게 사용하기
이렇게 추가한 Color Assets 은 Image Assets 와 동일하게 programmatically 하게 사용가능하다.
extension 으로 UIColor 상수를 만들어주었다.
extension UIColor {
static let backgroundColorAsset = UIColor(named: "backgroundColorAsset")
}
// 사용
// view.backgroundColor = .backgroundColorAsset
그렇다면 이미지에 대해서도 다크모드 적용이 가능할까?
Appearances 를 Any, Dark 로 설정해서 이미지를 추가할 수 있다.
if let image = UIImage(named: "profileImage") {
profileImage.image = image
}
😎 2.UIColor extension 으로 대응하기
먼저, UITraitEnvironment 에 대해서 알아보자.
iOS interface environment 을 앱에서 사용할 수 있도록 하는 메서드의 세트. 프로토콜이다.
iOS 인터페이스 환경에는 다음과 같은 trait 가 포함된다고 한다.
- horizontal / vertical size class
- display scale
- user interface idiom
- user interface style
우리는 여기서 user interface style 즉 라이트 / 다크 모드가 앱에서 필요하다. trait environment 에 접근하기 위해서는 UITraitCollection 클래스의 traitCollections 프로퍼티를 사용해야 한다.
출처
iOS ) UITraitEnvironment와 traitCollectionDidChange
extension UIColor {
static var defaultLabelColor: UIColor {
if #available(iOS 13, *) {
return UIColor { (traitCollection: UITraitCollection) -> UIColor in
// ✅ UITraitCollection 의 userInterfaceStyle : 라이트인지 다크인지 알려준다.
if traitCollection.userInterfaceStyle == .light {
return .black
} else {
return .white
}
}
} else {
return .black
}
}
}
// 사용
// textLabel.textColor = .defaultLabelColor
위와 같이 커스텀하게 설정할 수 있다.
또한 아래와 같이 system color 를 사용할 수도 있다. 하지만 iOS 13 부터 적용가능하기 때문에 이전 버전들의 경우도 대응해주어야 한다.
System color 로 대응하기
static var backgroundColor: UIColor {
if #available(iOS 13.0, *) {
return .systemBackground
} else {
return .white
}
}
다양한 시스템 컬러에 대해서 알아보려면 아래의 출처를 참고하자.
Color - Visual Design - iOS - Human Interface Guidelines - Apple Developer
⁉️문제
라이트 모드에서는 검은색 그림자가 다크 모드에서는 하얀색 그림자를 만들고 싶었다.
하지만 shadow 의 색이 대응이 되지 않았다..! (라이트에서 다크로 변경했기 때문에 초기에 설정된 그림자만 적용됨.)
⁉️ 해결
UIView 에 윤곽선 혹은 그림자를 적용할 때 CALayer 에 색상을 적용한다. 이 때는 UIColor 가 아닌 CGColor 객체를 shadowColor 의 객체로 사용하게 된다.
CGColor, NSTextAttachment 는 디바이스의 Appearances 값이 바뀔때 자동으로 바뀌지 않는다. 그래서 직접 바꿔 줘야 한다. 그림자의 색은 CGColor 와 관련있기에 다음과 같이 진행했다.
// traitCollection 이 변경될 때 호출되는 메서드
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
// 반드시 호출해야함. view 계층 구조에서 더 높은 인터페이스 요소가 먼저 레이아웃을 조정할 수 있도록 하기 위함.
super.traitCollectionDidChange(previousTraitCollection)
guard #available(iOS 13, *) else { return }
// ✅ 지정된 특성 컬렉션(previousTraitCollection)과 현재 특성 컬렉션 간의 변경이 색상 값에 영향을 미치는지 여부를 묻는다.
guard traitCollection.hasDifferentColorAppearance(comparedTo: previousTraitCollection) else { return }
// ✅ 이전 traitCollection 의 userInterfaceStyle 와 비교.
guard traitCollection.userInterfaceStyle != previousTraitCollection?.userInterfaceStyle else { return }
qrcodeBackView.layer.shadowColor = UIColor.shadowColor.cgColor
}
// UI 를 초기화하는 과정에서도
// qrcodeBackView.layer.shadowColor = UIColor.shadowColor.cgColor
// 추가해줘야 한다. 왜냐면 traitCollectionDidChange(_:) 메서드는 traitCollection 이 변경될 때 호출되기 때문이다.
이외에도 특정한 상황에서 특별한 동작을 부여하고 싶을 때 위의 메서드를 오버라이드 해서 사용한다고 한다.
✅ 로 표시된 주석 두개 중 아무거나 사용해도 괜찮다. 같은 의미다.
그림자가 잘 안보여서 굵기를 키웠다. 라이트모드에서는 검은색 그림자가 다크모드에서는 빨강 그림자가 나오게했다.
📌 라이트 / 다크 모드 제한시키기
여건에 따라서는 특정 모드로 제한해야하는 경우도 있다. 그렇다면 라이트/다크 모드로 제한하는 2가지 방법도 알아보자.
😎 1.info.plist 설정
라이트모드로만 제한해보자.
Appearance
혹은 UIUserInterfaceStyle
key 에 고정시키고 싶은 모드 value 를 넣어주면 된다.
(라이트모드만 가능 → Light / 다크모드만 가능 → Dark)
😎 2.programmatically 하게 설정
- AppDelegate, View Controller, UIView 에서 설정
AppDelegate 설정(Window 에서 설정)
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// ✅ iOS 13 부터 다크모드가 적용되므로 다음과 같은 조건문 성립.
if #available(iOS 13.0, *) {
// ✅ iOS 13 부터는 다크모드로만 제한.
self.window?.overrideUserInterfaceStyle = UIUserInterfaceStyle.dark
} else {
// Fallback on earlier versions
}
}
View Controller 에서 설정
override func viewDidLoad() {
super.viewDidLoad()
if #available(iOS 13.0, *) {
overrideUserInterfaceStyle = .dark
} else {
// Fallback on earlier versions
}
}
UIView 에서 설정
let view = UIView()
if #available(iOS 13.0, *) {
view.overrideUserInterfaceStyle = .dark
} else {
// Fallback on earlier versions
}