IOS рдореЗрдВ рдПрдХ рдбрд╛рд░реНрдХ рдереАрдо рдЬреЛрдбрд╝реЗрдВ

рд╕рднреА рдХреЛ рдирдорд╕реНрдХрд╛рд░!

рдореЗрд░рд╛ рдирд╛рдо рдПрдВрдбреНрд░реА рд╣реИ, рдореИрдВ рдорд╛рдИ рдмреНрд░реЛрдХрд░ рдЯреАрдо рд╕реЗ рд╣реВрдВред рдореИрдВ рдЖрдкрдХреЛ рдмрддрд╛рдКрдВрдЧрд╛ .─╕ рдиреЗ iOS рдореЗрдВ рдПрдХ рдбрд╛рд░реНрдХ рдереАрдо рдХреЗ рд▓рд┐рдП рд╕рдкреЛрд░реНрдЯ рдЬреЛрдбрд╝рд╛ рд╣реИред

IOS 13 рдореЗрдВ Apple рдиреЗ рдкреВрд░реЗ рд╕рд┐рд╕реНрдЯрдо рдХреЗ рд▓рд┐рдП рдПрдХ рдбрд╛рд░реНрдХ рдереАрдо рдЬреЛрдбрд╝рд╛ рд╣реИ, рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛ iOS рд╕реЗрдЯрд┐рдВрдЧреНрд╕ рдкрд░ рдПрдХ рд╣рд▓реНрдХрд╛ рдпрд╛ рдЧрд╣рд░рд╛ рд░рдВрдЧ рдЪреБрди рд╕рдХрддреЗ рд╣реИрдВред рдбрд╛рд░реНрдХ рдореЛрдб рдореЗрдВ, рд╕рд┐рд╕реНрдЯрдо рд╕рднреА рд╕реНрдХреНрд░реАрди, рд╡рд┐рдЪрд╛рд░, рдореЗрдиреВ рдФрд░ рдирд┐рдпрдВрддреНрд░рдг рдХреЗ рд▓рд┐рдП рдПрдХ рдЧрд╣рд░реЗ рд░рдВрдЧ рдкреИрд▓реЗрдЯ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рддрд╛ рд╣реИред

рдЫрд╡рд┐

рдХреМрди рдкрд░рд╡рд╛рд╣ рдХрд░рддрд╛ рд╣реИ - рдмрд┐рд▓реНрд▓реА рдХреЗ рдиреАрдЪреЗ рдЬрд╛рддрд╛ рд╣реИред

рдбрд╛рд░реНрдХ рдбрд┐рдЬрд╛рдЗрди рдХрд╛ рд╕рдорд░реНрдерди


рдбрд┐рдлрд╝реЙрд▓реНрдЯ рд░реВрдк рд╕реЗ Xcode 11 рдореЗрдВ рдмрдирд╛рдпрд╛ рдЧрдпрд╛ рдПрдкреНрд▓рд┐рдХреЗрд╢рди iOS 13. рдореЗрдВ рдбрд╛рд░реНрдХ рдбрд┐рдЬрд╝рд╛рдЗрди рдХрд╛ рд╕рдорд░реНрдерди рдХрд░рддрд╛ рд╣реИ, рд▓реЗрдХрд┐рди рдбрд╛рд░реНрдХ рдореЛрдб рдХреЗ рдкреВрд░реНрдг рдХрд╛рд░реНрдпрд╛рдиреНрд╡рдпрди рдХреЗ рд▓рд┐рдП, рдЖрдкрдХреЛ рдЕрддрд┐рд░рд┐рдХреНрдд рдЕрдкрдбреЗрдЯ рдХрд░рдиреЗ рдХреА рдЖрд╡рд╢реНрдпрдХрддрд╛ рд╣реИ:

  • рд░рдВрдЧреЛрдВ рдХреЛ рд╣рд▓реНрдХреЗ рдФрд░ рдЧрд╣рд░реЗ рдбрд┐рдЬрд╛рдЗрди рдХрд╛ рд╕рдорд░реНрдерди рдХрд░рдирд╛ рдЪрд╛рд╣рд┐рдП
  • рдЫрд╡рд┐рдпреЛрдВ рдХреЛ рдкреНрд░рдХрд╛рд╢ рдФрд░ рдЕрдВрдзреЗрд░реЗ рдбрд┐рдЬрд╛рдЗрди рдХрд╛ рд╕рдорд░реНрдерди рдХрд░рдирд╛ рдЪрд╛рд╣рд┐рдП

рдРрдкреНрдкрд▓ рдиреЗ рдХрдИ рд╕рд┐рд╕реНрдЯрдо рд░рдВрдЧ рдЬреЛрдбрд╝реЗ рд╣реИрдВ рдЬреЛ рдкреНрд░рдХрд╛рд╢ рдФрд░ рдЕрдВрдзреЗрд░реЗ рдбрд┐рдЬрд╛рдЗрди рдХрд╛ рд╕рдорд░реНрдерди рдХрд░рддреЗ рд╣реИрдВред
рдЫрд╡рд┐

IOS 13 рдореЗрдВ, рдирдпрд╛ UIColor рдЗрдирд┐рд╢рд╛рдЗрдЬрд╝рд░ рдкреЗрд╢ рдХрд┐рдпрд╛ рдЧрдпрд╛ рдерд╛ :

init (dynamicProvider: @escaping (UITraitCollection) -> UIColor)

рдкреНрд░рдХрд╛рд╢ рдФрд░ рдЕрдВрдзреЗрд░реЗ рдбрд┐рдЬрд╛рдЗрди рдХреЗ рдмреАрдЪ рд╕реНрд╡рд┐рдЪ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рд╕рдорд░реНрдерди рдХреЗ рд╕рд╛рде рд░рдВрдЧ рдмрдирд╛рдиреЗ рдХреЗ рд▓рд┐рдП рдПрдХ рд╕реНрдерд┐рд░ рдлрд╝рдВрдХреНрд╢рди рдЬреЛрдбрд╝реЗрдВ:

extension UIColor {
    
    static func color(light: UIColor, dark: UIColor) -> UIColor {
        if #available(iOS 13, *) {
            return UIColor.init { traitCollection in
                return traitCollection.userInterfaceStyle == .dark ? dark : light
            }
        } else {
            return light
        }
    }
}

CGColor рдкреНрд░рдХрд╛рд╢ рдФрд░ рдЕрдВрдзреЗрд░реЗ рдХреЗ рдмреАрдЪ рд╕реНрд╡рдЪрд╛рд▓рд┐рдд рд╕реНрд╡рд┐рдЪрд┐рдВрдЧ рдХрд╛ рд╕рдорд░реНрдерди рдирд╣реАрдВ рдХрд░рддрд╛ рд╣реИред рдбрд┐рдЬрд╝рд╛рдЗрди рдмрджрд▓рдиреЗ рдХреЗ рдмрд╛рдж рдЖрдкрдХреЛ рдореИрдиреНрдпреБрдЕрд▓ рд░реВрдк рд╕реЗ CGColor рдХреЛ рдмрджрд▓рдирд╛ рд╣реЛрдЧрд╛ред

override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
    super.traitCollectionDidChange(previousTraitCollection)
        
    layer.borderColor = UIColor.Pallete.black.cgColor
}

рд╕рдВрд╕рд╛рдзрдиреЛрдВ рдореЗрдВ рдЕрдВрдзреЗрд░реЗ рд╕рдЬрд╛рд╡рдЯ рдХреЗ рд▓рд┐рдП рд░рдВрдЧ рдЬреЛрдбрд╝рдирд╛ рднреА рд╕рдВрднрд╡ рд╣реИред

рдЫрд╡рд┐

рд▓реЗрдХрд┐рди рдореИрдВ рдХреЛрдб рдореЗрдВ рд░рдВрдЧ рдЬреЛрдбрд╝рдирд╛ рдкрд╕рдВрдж рдХрд░рддрд╛ рд╣реВрдВред

UIColor.Pallete
extension UIColor {
    
    struct Pallete {

        static let white = UIColor.color(light: .white, dark: .black)
        static let black = UIColor.color(light: .black, dark: .white)

        static let background = UIColor.color(light: .white, dark: .hex("1b1b1d"))
        static let secondaryBackground = UIColor(named: "secondaryBackground") ?? .black

        static let gray = UIColor.color(light: .lightGray, dark: .hex("8e8e92"))

    }
}


рдЫрд╡рд┐рдпреЛрдВ рдХреЗ рд▓рд┐рдП, рд╕рдВрд╕рд╛рдзрдиреЛрдВ рдореЗрдВ рдПрдХ рдбрд╛рд░реНрдХ рдбрд┐рдЬрд╝рд╛рдЗрди рдХреЗ рд▓рд┐рдП рдмрд╕ рдЫрд╡рд┐ рдХрд╛ рдПрдХ рдкреНрд░рдХрд╛рд░ рдЬреЛрдбрд╝реЗрдВред

рдЫрд╡рд┐

рдПрдХ рдЙрджрд╛рд╣рд░рдг рдХреЗ рд▓рд┐рдП рдПрдХ рдЫреЛрдЯрд╛ рд╕рд╛ рдЖрд╡реЗрджрди рдХрд░рддреЗ рд╣реИрдВред


рдПрдкреНрд▓рд┐рдХреЗрд╢рди рдореЗрдВ рджреЛ рд╡рд┐рдВрдбреЛ рдФрд░ рддреАрди рд╕реНрдХреНрд░реАрди рд╣реЛрдВрдЧреЗред

рдкрд╣рд▓реА рд╡рд┐рдВрдбреЛ: рдкреНрд░рд╛рдзрд┐рдХрд░рдг рд╕реНрдХреНрд░реАрдиред

рджреВрд╕рд░реА рд╡рд┐рдВрдбреЛ: рд░рд┐рдмрди рд╕реНрдХреНрд░реАрди рдФрд░ рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛ рдкреНрд░реЛрдлрд╝рд╛рдЗрд▓ рд╕реНрдХреНрд░реАрдиред

рдкреНрд░рдХрд╛рд╢ рдФрд░ рдЕрдВрдзреЗрд░реЗ рдбрд┐рдЬрд╛рдЗрди рдореЗрдВ рд╕реНрдХреНрд░реАрдирд╢реЙрдЯ
рдЫрд╡рд┐ рдЫрд╡рд┐ рдЫрд╡рд┐
рдЫрд╡рд┐ рдЫрд╡рд┐ рдЫрд╡рд┐

рд▓рд╛рдЗрдЯ рдФрд░ рдбрд╛рд░реНрдХ рдереАрдо рд╕реНрд╡рд┐рдЪ рдХрд░реЗрдВ


рд╡рд┐рд╖рдп рдХреЗ рд▓рд┐рдП рдПрдХ рдкрд╣реЗрд▓реА рдмрдирд╛рдПрдБ:тАи

enum Theme: Int, CaseIterable {
    case light = 0
    case dark
}

рд╣рдо рдПрдкреНрд▓рд┐рдХреЗрд╢рди рдХреЛ рдкреБрдирд░рд╛рд░рдВрдн рдХрд░рдиреЗ рдХреЗ рдмрд╛рдж рдЗрд╕реЗ рдкреБрдирд░реНрд╕реНрдерд╛рдкрд┐рдд рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рд╡рд░реНрддрдорд╛рди рдереАрдо рдХреЛ рд╕рдВрдЧреНрд░рд╣реАрдд рдХрд░рдиреЗ рдХреА рдХреНрд╖рдорддрд╛ рдЬреЛрдбрд╝рддреЗ рд╣реИрдВред

extension Theme {
    
    //   UserDefaults
    @Persist(key: "app_theme", defaultValue: Theme.light.rawValue)
    private static var appTheme: Int
    
    //    UserDefaults
    func save() {
        Theme.appTheme = self.rawValue
    }
    
    //   
    static var current: Theme {
        Theme(rawValue: appTheme) ?? .light
    }
}

рджреГрдврд╝ рд░рд╣рдирд╛
@propertyWrapper
struct Persist<T> {
    let key: String
    let defaultValue: T
    
    var wrappedValue: T {
        get { UserDefaults.standard.object(forKey: key) as? T ?? defaultValue }
        set { UserDefaults.standard.set(newValue, forKey: key) }
    }
    
    init(key: String, defaultValue: T) {
        self.key = key
        self.defaultValue = defaultValue
    }
}


рдбрд┐рдЬрд╛рдЗрди рдХреЛ рдордЬрдмреВрд░ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП, рдЖрдкрдХреЛ рд╕рднреА рдПрдкреНрд▓рд┐рдХреЗрд╢рди рд╡рд┐рдВрдбреЛ рдХреА рд╢реИрд▓реА рдХреЛ рдмрджрд▓рдиреЗ рдХреА рдЖрд╡рд╢реНрдпрдХрддрд╛ рд╣реИредтАи
рд╣рдо рдПрдкреНрд▓рд┐рдХреЗрд╢рди рдореЗрдВ рдереАрдо рд╕реНрд╡рд┐рдЪрд┐рдВрдЧ рд▓рд╛рдЧреВ рдХрд░рддреЗ рд╣реИрдВредтАи

extension Theme {
    
    @available(iOS 13.0, *)
    var userInterfaceStyle: UIUserInterfaceStyle {
        switch self {
        case .light: return .light
        case .dark: return .dark
        }
    }
    
    func setActive() {
        //   
        save()
        
        guard #available(iOS 13.0, *) else { return }
        
        //       
        UIApplication.shared.windows
            .forEach { $0.overrideUserInterfaceStyle = userInterfaceStyle }
    }
}

рд╡рд┐рдВрдбреЛ рджрд┐рдЦрд╛рдиреЗ рд╕реЗ рдкрд╣рд▓реЗ рд╡рд┐рдВрдбреЛ рд╕реНрдЯрд╛рдЗрд▓ рдХреЛ рдХрд░рдВрдЯ рдереАрдо рдореЗрдВ рдмрджрд▓рдирд╛ рднреА рдЖрд╡рд╢реНрдпрдХ рд╣реИред

extension UIWindow {
    
    //     
    //     
    func initTheme() {
        guard #available(iOS 13.0, *) else { return }
        
        overrideUserInterfaceStyle = Theme.current.userInterfaceStyle
    }
}

рдПрдХ рдкреНрд░рдХрд╛рд╢ рдпрд╛ рдЕрдВрдзреЗрд░реЗ рд╡рд┐рд╖рдп рдЪреБрдирдиреЗ рдХреЗ рд╕реНрдХреНрд░реАрдирд╢реЙрдЯ
рдЫрд╡рд┐ рдЫрд╡рд┐

рд╕рд┐рд╕реНрдЯрдо рдереАрдо рдкрд░ рдПрдХ рд╕реНрд╡рд┐рдЪ рдЬреЛрдбрд╝реЗрдВ


Enum рд╡рд┐рд╖рдп рдореЗрдВ рдПрдХ рд╕рд┐рд╕реНрдЯрдо рдереАрдо рдЬреЛрдбрд╝реЗрдВред
enum Theme: Int, CaseIterable {
    case system = 0
    case light
    case dark
}

рдкреНрд░рдХрд╛рд╢ рдпрд╛ рдЕрдВрдзреЗрд░реЗ рд╡рд┐рд╖рдп рдХреА рдЬрдмрд░рди рд╕реНрдерд╛рдкрдирд╛ рдХреЗ рдмрд╛рдж, рдпрд╣ рдирд┐рд░реНрдзрд╛рд░рд┐рдд рдХрд░рдирд╛ рдЕрд╕рдВрднрд╡ рд╣реИ рдХрд┐ рд╕рд┐рд╕реНрдЯрдо рдореЗрдВ рдХреМрди рд╕рд╛ рдбрд┐рдЬрд╝рд╛рдЗрди рд╢рд╛рдорд┐рд▓ рд╣реИред рд╕рд┐рд╕реНрдЯрдо рдбрд┐рдЬрд╝рд╛рдЗрди рдХреЛ рдкрд╣рдЪрд╛рдирдиреЗ рдХреЗ рд▓рд┐рдП, рд╣рдо рдПрдкреНрд▓рд┐рдХреЗрд╢рди рдореЗрдВ рдПрдХ рд╡рд┐рдВрдбреЛ рдЬреЛрдбрд╝рддреЗ рд╣реИрдВ, рдЬрд┐рд╕рдореЗрдВ рд╣рдо рдбрд┐рдЬрд╝рд╛рдЗрди рдкрд░рд┐рд╡рд░реНрддрди рдХреЗ рд▓рд┐рдП рдмрд╛рдзреНрдп рдирд╣реАрдВ рдХрд░реЗрдВрдЧреЗред рдбрд┐рдЬрд╝рд╛рдЗрди рдХреЛ рд▓рд╛рдЧреВ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рднреА рдЖрд╡рд╢реНрдпрдХ рд╣реИ рдЬрдм рдПрдкреНрд▓рд┐рдХреЗрд╢рди рдореЗрдВ рд╕рд┐рд╕реНрдЯрдо рдереАрдо рд╕реНрдерд╛рдкрд┐рдд рд╣реЛ рдФрд░ рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛ iOS рдореЗрдВ рдбрд┐рдЬрд╝рд╛рдЗрди рдХреЛ рдмрджрд▓ рджреЗред

final class ThemeWindow: UIWindow {
    
    override public func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {

        //         iOS,     .
        // :      .
        if Theme.current == .system {
            Theme.system.setActive()
        }
    }
}

let themeWindow = ThemeWindow()

class AppDelegate: UIResponder, UIApplicationDelegate {

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        ...
        //    ,    
        //       
        themeWindow.makeKey()
        ...
        return true
    }
}

extension Theme {
    
    @available(iOS 13.0, *)
    var userInterfaceStyle: UIUserInterfaceStyle {
        switch self {
        case .light: return .light
        case .dark: return .dark
        case .system: return themeWindow.traitCollection.userInterfaceStyle
        }
    }
    
    func setActive() {
        //   
        save()
        
        guard #available(iOS 13.0, *) else { return }
        
        //       
        //        
        UIApplication.shared.windows
            .filter { $0 != themeWindow } 
            .forEach { $0.overrideUserInterfaceStyle = userInterfaceStyle }
    }
}

рдПрдХ рдкреНрд░рдгрд╛рд▓реА, рдкреНрд░рдХрд╛рд╢ рдпрд╛ рдЕрдВрдзреЗрд░реЗ рд╡рд┐рд╖рдп рдЪреБрдирдиреЗ рдХреЗ рд╕реНрдХреНрд░реАрдирд╢реЙрдЯ
рдЫрд╡рд┐ рдЫрд╡рд┐

рдкрд░рд┐рдгрд╛рдо


рдбрд╛рд░реНрдХ рдбрд┐рдЬрд╝рд╛рдЗрди рдФрд░ рд╕рд┐рд╕реНрдЯрдо, рд▓рд╛рдЗрдЯ рдФрд░ рдбрд╛рд░реНрдХ рдереАрдо рдХреЗ рдмреАрдЪ рд╕реНрд╡рд┐рдЪ рдХрд░рдиреЗ рдХрд╛ рд╕рдорд░реНрдердиред
рд╕реНрдХреНрд░реАрди рд╡реАрдбрд┐рдпреЛ


рдкреВрд░реЗ рдкреНрд░реЛрдЬреЗрдХреНрдЯ рдХрд╛ рд▓рд┐рдВрдХ

All Articles