앱에서 다크모드 제어 가능한 스위치 만들기 (iOS 15.0, UserDefaults)

DevelopRecord·2022년 5월 6일
1

최근에 현재 만들고 있는 앱에 다크모드를 On/Off 할 수 있는 기능을 추가하고 싶어 시작했다가,

전에 만들어본 경험이 있긴 했지만 iOS 15.0부터 deprecated 되어,

꽤 삽질한 내용에 대해 잊지 않게 글을 쓰려 합니다.

먼저 구현할 내용은 간단했습니다.

  1. 테이블 뷰 헤더에 다크모드를 제어할 수 있는 UISwitch를 하나 만든다.
  2. 사용자가 스위치를 On/Off를 하여 상태가 바뀔 경우 UserDefault에 앱 상태를 저장시킨다.
  3. 앱을 종료하고 실행하면 Lifecycle에 UserDefaults값을 불러오고, 다크모드의 상태유무를 적용한다.
  4. 앱을 종료 후 실행해도 다크모드가 적용이 되어 있어야 한다.

동작 원리는,

스위치가 isOn == true 상태일 때는 다크모드, false 상태일 때는 라이트 모드가 적용이 됩니다.

시스템 설정에 따르는 옵션은 따로 넣지 않았어요.

overrideUserInterfaceStyle가 인터페이스 모드를 지정하는 변수입니다.

해당 뷰의 인터페이스 색상만 지정하는 방법인 view.overrideUserInterfaceStyle도 있습니다.

하지만 앱 전체의 인터페이스를 변경하고 싶을 때는 let window = UIApplication.shared.windows.first 를 사용해야 해요.

따라서 아래 방식으로 사용이 가능하죠.

let window = UIApplication.shared.windows.first
window.overrideUserInterfaceStyle = .dark

스위치 액션 추가

먼저 우리는 저 스위치를 isOn == true가 된 상태일 때 다크모드를 지정하고 싶으니까

스위치의 Selector 함수에 작성해야 합니다.

let userDefaults = UserDefaults.standard

func updateInterfaceStyle() {
    if let window = UIApplication.shared.connectedScenes.first as? UIWindowScene {
        if #available(iOS 15.0, *) {
            let windows = window.windows.first
                windows?.overrideUserInterfaceStyle = self.appearanceSwitch.isOn == true ? .dark : .light
                userDefaults.set(appearanceSwitch.isOn, forKey: "appearanceSwitchState") // 스위치 상태 저장하기 위해 UserDefaults에 상태 저장
            }
        } else if let window = UIApplication.shared.windows.first {
            if #available(iOS 13.0, *) {
                    window.overrideUserInterfaceStyle = self.appearanceSwitch.isOn == true ? .dark : .light
                userDefaults.set(appearanceSwitch.isOn, forKey: "appearanceSwitchState")
            } else {
                window.overrideUserInterfaceStyle = .light
            }
        }
    }
    
@objc func handleAppearanceChange(_ sender: UISwitch) {
    UIView.animate(withDuration: 0.4) {
        self.updateInterfaceStyle()
    }
}

먼저 저의 디바이스 환경은 위의 사진에서 보시다시피, iOS 15.2인 최신 버전의 상태입니다.

하지만, iOS 15.0 이상부터는 아래 방법이 deprecated 되었어요.

let window = UIApplication.shared.windows.first
window.overrideUserInterfaceStyle = .dark

그래서 위 함수에서 버전별 분기 처리를 해주었던 거구요. 대신 iOS 13.0 ~ 14.5 버전까지는 사용 가능합니다. (14.5인지는 확실하게는 모르겠어요..)

또한 스위치의 버튼 상태를 기억하고 앱을 종료하고 다시 실행했을 경우에도 다크모드를 유지해야 하기 때문에 UserDefaults에 스위치의 상태값을 저장합니다.

Lifecycle에 추가

그럼 앱이 실행했을 때에도 상태를 기억하기 위해 Lifecycle에도 위 함수를 추가해 주도록 합니다.

override func viewDidLoad() {
    super.viewDidLoad()
    appearanceSwitch.isOn = userDefaults.bool(forKey: "appearanceSwitchState")

    updateInterfaceStyle()
}

스위치의 상태값을 뷰가 로드될 때 바로 가져와야 하기 때문에 UserDefaults에 상태값을 가져옵니다.

SceneDelegate에 추가

마지막으로, 앱을 종료하고 실행해도 적용이 되어 있어야 한다고 했죠?

따라서 앱 실행 후 앱 전체에 적용해주기 위해 SceneDelegate에도 적용해줍니다.

let userDefaults = UserDefaults.standard

userDefaults.bool(forKey: "appearanceSwitchState")
AppearanceHeader.shared.updateInterfaceStyle()

마무리

기존에 사용했던 방식이 deprecated 되어 찾아보느라 30분 정도 더 소요된 것 같습니다.

참고 레퍼런스

0개의 댓글