【问题标题】:Trying to reload view controller to update the current theme尝试重新加载视图控制器以更新当前主题
【发布时间】:2023-03-07 06:02:02
【问题描述】:

我在寻找什么

我正在尝试重新加载视图控制器中的所有视图,以在主题之间进行更改(类似于 TwitterApple Maps 所做的)。


我如何设置不同的主题

我有这样的主题视图设置:

@IBDesignable
extension UIView {

    @IBInspectable
    var lightBackgroundColor: UIColor? {
        set {
            switch GEUserSettings.theme {
            case .light:    backgroundColor = newValue
            case .dark:     break
            }
        }
        get {
            return self.lightBackgroundColor
        }
    }

    @IBInspectable
    var darkBackgroundColor: UIColor? {
        set {
            switch GEUserSettings.theme {
            case .light:    break
            case .dark:     backgroundColor = newValue
            }
        }
        get {
            return self.darkBackgroundColor
        }
    }
}

这允许我在我的Main.storyboard 中设置lightdark 主题背景颜色,具体取决于当前主题。我的背景模糊效果不包括在内,因为我找不到更新代码中style的方法,所以它是在viewDidLoad中创建的。


通过摇动设备触发主题

但是,当我想更改主题时,我不知道该怎么做。我想通过摇动设备来触发它,如下所示:

override func motionBegan(_ motion: UIEvent.EventSubtype, with event: UIEvent?) {
    print("Shaken!")
    let oppositeTheme: GEUserSettings.Theme = {
        switch GEUserSettings.theme {
        case .light:    return .dark
        case .dark:     return .light
        }
    }()

    GEUserSettings.theme = oppositeTheme

    // My attempt to update the view controller to
    // update the theme, which doesn't do anything.
    dismiss(animated: true) {
        UIApplication.shared.keyWindow?.rootViewController?.present(self, animated: true, completion: nil)
        // Yes, the presenting is working, but the views don't change.
    }
}

有哪些可能的解决方案?

如果应用程序退出并重新启动,设置将生效。我可以强制退出应用程序(不使用exit(0) 或任何算作崩溃的东西),或者在使用应用程序时重新加载它。

我尝试关闭然后重新加载视图控制器,如上面的代码所示。我正在重新加载的那个显示在基本视图控制器的顶部。

由于我正在使用情节提要,我怎样才能完成这项工作?

编辑 - 添加了我的明暗模式的图像以使我的问题更清晰:

【问题讨论】:

  • 在你的 baseController 中创建主题更新函数并在everyoController 中覆盖该方法将 UI 更新逻辑放入该函数中,当设备摇晃时调用覆盖的方法......我已经这样做了
  • 正如 Abu UI Hassan 所说,在每个控制器中的基本控制器覆盖中创建方法,将逻辑放在那里以进行主题更新。您必须使用 NotificationCenter 将消息广播到控制器必须更改主题的每个控制器。
  • 这是一个很好的例子,展示了如何使用复合设计模式将主题应用于任何层次的视图。它也可以扩展到视图控制器refactoring.guru/design-patterns/composite/swift/…

标签: ios swift uiviewcontroller xcode-storyboard


【解决方案1】:

如果您要在应用程序中使用主题,Apple 提供了UIApperance 协议,可帮助您同时更改某种类型的控件属性,使用此协议,您的 UI 将具有统一的外观。使用方法真的很简单,把UILabel的背景颜色全部改成这样:

UILabel.apperance().backgroundColor = .lightGray

如果您想像在示例代码中那样在一个地方管理所有内容,您可以创建一个包含 UI 特征的结构,请检查此结构(我使用了与您相同的名称):

import UIKit

struct GEUserSettings {
    enum Theme { case light, dark }

    static public var theme: Theme = .light {
        didSet {
            guard theme != oldValue else { return }
            apply()
        }
    }
    static weak var window: UIWindow?

    static public func toggleTheme() {
        self.theme = theme == .light ? .dark : .light
    }

    static private func apply() {
        setColors()
        if let window = window {
            window.subviews.forEach({ (view: UIView) in
                view.removeFromSuperview()
                window.addSubview(view)
            })
        }
    }

    static public func setColors() {
        switch theme {
        case .light:
            UILabel.appearance().textColor = .black
            UISegmentedControl.appearance().tintColor = .blue
            UILabel.appearance(whenContainedInInstancesOf:    [UISegmentedControl.self]).backgroundColor = .clear
            UITableViewHeaderFooterView.appearance().backgroundColor = .lightGray
            UITableView.appearance().backgroundColor = .white
        case .dark:
            UILabel.appearance().textColor = .red
            UISegmentedControl.appearance().tintColor = .purple
            UILabel.appearance(whenContainedInInstancesOf: [UISegmentedControl.self]).backgroundColor = .clear
            UITableViewHeaderFooterView.appearance().backgroundColor = .black        
            UITableView.appearance().backgroundColor = .darkGray
        }
    }
}

在 AppDelegate 中,或尽快将 UIWindow 引用传递给主题管理器结构。我在AppDelegatedidFinishLaunchingWithOptions 中做到了。这对于立即进行颜色更改是必要的。

定义此结构后,您可以根据需要自定义任何 UI 控件。例如,您可以为UILabel 定义某种背景颜色,如果它包含在UISegmentedControl 中,则使用不同的背景颜色。

您定义的摇动事件可以像这样在主题之间切换:

override func motionBegan(_ motion: UIEvent.EventSubtype, with event: UIEvent?) {
    GEUserSettings.toggleTheme()
}

如果您摇动设备,屏幕将在这两者之间切换(我只更改了一些属性):

如果您想使用示例项目,请访问Github

希望我能帮上忙!

【讨论】:

  • 当我只希望某些视图改变颜色时怎么办?我可以这样做吗?例如,我有多个UIViews。它们在亮模式下是不同的颜色,在暗模式下也是不同的颜色。如何做到这一点?
  • 在这种情况下并不容易,但是您可以将这些视图放在某个类的容器中,或者将这些视图专门化到一个从基类继承的新空类中。例如:您有三个不同的UILabels。创建一些继承自UILabel 的空类,例如UIRedLabelUIGreenLabelUIBlueLabel。然后在主题管理器中为这些类设置不同的属性,例如:UIRedLabel.apperance().textColor = .redUIGreenLabel.apperance().textColor = .green 等。
  • 我通过手机添加了最后一条评论,但没有看到您的解决方案。好一个!今晚回家后我会试试这种模糊效果。
【解决方案2】:

我终于弄明白了,使用NotificationCenter

GE用户设置

GEUserSettings 现在如下所示:

enum GEUserSettings {

    enum Theme: String {
        case light
        case dark
    }
    /// The current theme for the user.
    static var theme: Theme = .dark
    #warning("Store theme in UserDefaults")

    /// Toggles the theme.
    static func toggleTheme() {
        switch GEUserSettings.theme {
        case .light:    theme = .dark
        case .dark:     theme = .light
        }

        NotificationCenter.default.post(name: Notification.Name("UpdateThemeNotification"), object: nil)
    }
}

GE视图

GEViewUIView 的自定义子类。这是替换而不是我对UIView 的扩展。现在看起来类似于:

/// UIView subclass to allow creating corners, shadows, and borders in storyboards.
@IBDesignable
final class GEView: UIView {

    // MARK: - Initializers
    override init(frame: CGRect) {
        super.init(frame: frame)
    }
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)

        // Triggers when the theme is changed
        NotificationCenter.default.addObserver(self, selector: #selector(updateBackgroundColorNotification), name: Notification.Name("UpdateThemeNotification"), object: nil)
    }
    @objc
    private func updateBackgroundColorNotification() {
        updateBackgroundColor()
    }


    /* ... */


    // MARK: - Background
    @IBInspectable
    var lightBackgroundColor: UIColor? {
        didSet {
            updateBackgroundColor()
        }
    }
    @IBInspectable
    var darkBackgroundColor: UIColor? {
        didSet {
            updateBackgroundColor()
        }
    }

    /// Updates the background color depending on the theme.
    private func updateBackgroundColor() {
        switch GEUserSettings.theme {
        case .light:    backgroundColor = self.lightBackgroundColor
        case .dark:     backgroundColor = self.darkBackgroundColor
        }
    }
}

通过motionBegan(_:with:)更新

override func motionBegan(_ motion: UIEvent.EventSubtype, with event: UIEvent?) {
    super.motionBegan(motion, with: event)

    // Toggles the theme and update the views
    GEUserSettings.toggleTheme()
    drawerViewModel.updateBlurEffect(drawerView: drawerView)
}

然后模糊被移除并重新创建,如下所示:

/// Creates the blur effect behind the collection view.
func updateBlurEffect(drawerView: GEView) {
    if let blurView = drawerView.subviews[0] as? UIVisualEffectView {
        blurView.removeFromSuperview()
    }

    let blurEffect: UIBlurEffect = {
        switch GEUserSettings.theme {
        case .light:    return UIBlurEffect(style: .light)
        case .dark:     return UIBlurEffect(style: .dark)
        }
    }()
    let blurView = UIVisualEffectView(effect: blurEffect)

    drawerView.addSubview(blurView)
    drawerView.sendSubviewToBack(blurView)
    GEConstraints.fillView(with: blurView, for: drawerView)
}

这甚至不需要退出应用程序或重新加载视图控制器,它会立即发生!

额外(动画)

如果您愿意,还可以通过更改 updateBackgroundColor() 函数来为颜色变化设置动画:

/// Updates the background color depending on the theme.
private func updateBackgroundColor() {
    UIView.animate(withDuration: 0.25) {
        switch GEUserSettings.theme {
        case .light:    self.backgroundColor = self.lightBackgroundColor
        case .dark:     self.backgroundColor = self.darkBackgroundColor
        }
    }
}

您也可以为模糊设置动画:

/// Creates the blur effect behind the collection view.
func updateBlurEffect(drawerView: GEView) {
    if let blurView = drawerView.subviews[0] as? UIVisualEffectView {
        UIView.animate(withDuration: 0.25, animations: {
            blurView.alpha = 0
        }, completion: { _ in
            blurView.removeFromSuperview()
        })
    }

    let blurEffect: UIBlurEffect = {
        switch GEUserSettings.theme {
        case .light:    return UIBlurEffect(style: .light)
        case .dark:     return UIBlurEffect(style: .dark)
        }
    }()
    let blurView = UIVisualEffectView(effect: blurEffect)
    blurView.alpha = 0

    drawerView.addSubview(blurView)
    drawerView.sendSubviewToBack(blurView)
    GEConstraints.fillView(with: blurView, for: drawerView)

    UIView.animate(withDuration: 0.25, animations: {
        blurView.alpha = 1
    })
}

【讨论】:

    【解决方案3】:
        typealias Style = StyleManager
    //MARK: - Style
    final class StyleManager {
        static func selectedThem()->Int?
        {
            return AppUtility?.getObject(forKey: "selectedTheme") as? Int       // 1 for dark Theme ...... 2 for light Theme
        }
        static func BoldFont()->UIFont {
            return UIFont(name: FontType.bold.fontName, size: FontType.bold.fontSize)!
        }
        // MARK: - Style
        static func setUpTheme() {
            Chameleon.setGlobalThemeUsingPrimaryColor(primaryTheme(), withSecondaryColor: theme(), usingFontName: font(), andContentStyle: content())
        }
        // MARK: - Theme
        static func SetPagerViewsColor()->UIColor
        {
           return secondarythemeColor
        }
        static func primaryTheme() -> UIColor {
            setCheckMarkBackground()
            if selectedThem() == 1
            {
                return UIColor.white
            }
            else
            {
                return OddRowColorlight
            }
        }
        static func theme() -> UIColor {
            if selectedThem() == 1
            {
                EvenRowColor = EvenRowColordark
                OddRowColor = OddRowColorlight
                primaryThemeColor=EvenRowColor
                secondarythemeColor=OddRowColor
                return darkGrayThemeColor
            }
            else
            {
                EvenRowColor = lightWhiteThemeColor!
                OddRowColor = UIColor.white
                primaryThemeColor=EvenRowColor
                secondarythemeColor=OddRowColor
                return lightWhiteThemeColor!
            }
            // return FlatWhite()
        }
        static func toolBarTheme() -> UIColor {
            if selectedThem() == 1
            {
                return UIColor.white
            }
            else
            {
                return FlatBlack()
            }
        }
        static func tintTheme() -> UIColor {
            if selectedThem() == 1
            {
                return UIColor.white
            }
            else
            {
                return FlatBlack()
            }
        }
        static func titleTextTheme() -> UIColor {
            if selectedThem() == 1
            {
                return UIColor.white
            }
            else
            {
                return UIColor.white
            }
        }
        static func titleTheme() -> UIColor {
            if selectedThem() == 1
            {
                return darkGrayThemeColor
            }
            else
            {
                return FlatWhite()
            }
        }
        static func textTheme() -> UIColor {
            if selectedThem() == 1
            {
                return UIColor.white
            }
            else
            {
                return FlatBlack()
            }
            //return FlatMint()
        }
        static func backgroudTheme() -> UIColor {
            if selectedThem() == 1
            {
    
                return .darkGray
            }
            else
            { 
                return .white
            }
        }
    }
    

    现在在全局范围内创建一些变量

    var primaryThemeColor:UIColor!
    var secondarythemeColor:UIColor!
    var themeColor:UIColor!
    var toolBarThemeColor:UIColor!
    var tintThemeColor:UIColor!
    var titleTextThemeColor:UIColor!
    var titleThemeColor:UIColor!
    var textThemeColor:UIColor!
    var backgroundThemeColor:UIColor!
    var positiveThemeColor:UIColor!
    var negativeThemeColor:UIColor!
    var clearThemeColor:UIColor!
    var setCheckMarkBackgroundColor:UIColor!
    var menuSectioColor:UIColor!
    var menuCellColor:UIColor!
    var menuBackgroundColor:UIColor!
    var menuTextTHeme:UIColor!
    var themeName:String!
    var btnIconColor:UIColor!
    

    现在在 AppDelegate 中创建下面的函数并在 didFinish Launch 中调用这个函数

    func setCurrentThemeColors()
    {
        themeColor = Style.theme()
        toolBarThemeColor = Style.toolBarTheme()
        tintThemeColor = Style.tintTheme()
        titleTextThemeColor = Style.titleTextTheme()
        titleThemeColor = Style.titleTheme()
        textThemeColor = Style.textTheme()
        backgroundThemeColor = Style.backgroudTheme()
    }
     func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
        self.setCurrentThemeColors()
        return true
    }
    

    现在你已经设置好了你的主题,你只需要在你的 baseController 中创建主题更新函数并在每个 ViewController 中覆盖该方法将 UI 更新逻辑放在该函数中,当设备摇晃时调用如下所示的覆盖方法

    override func motionBegan(_ motion: UIEvent.EventSubtype, with event: UIEvent?) {
    print("Shaken!")
    updateTheme()
    appDelegate.setCurrentThemeColors()
    }
    
      func setLightTheme(){
        AppUtility?.saveObject(obj: 0 as AnyObject, forKey: "selectedTheme")
    }
    func setDarkTheme(){
        AppUtility?.saveObject(obj: 1 as AnyObject, forKey: "selectedTheme")
    }
    func updateTheme()
    {
        let theme = AppUtility?.getObject(forKey: "selectedTheme") as? Int
        if theme != nil
        {
            _ = theme == 1 ? setLightTheme() : setDarkTheme()
        }
        else
        {
            setDarkTheme()
        }
        appDelegate.setCurrentThemeColors()
        ConfigureView()
    
    }
     func ConfigureView(){
        btnDownLoadPdf.backgroundColor = .clear
        btnRightSide.backgroundColor = .clear
        btnRefreshPage.backgroundColor = .clear
        self.View.backgroundColor = secondarythemeColor
        PeriodicePastDatesPickerView.backgroundColor = secondarythemeColor
        customDatePicker.backgroundColor = secondarythemeColor
        UnitPicker.backgroundColor = secondarythemeColor
        currencyPicker.backgroundColor = secondarythemeColor
    }
    

    注意: 您必须根据需要更新颜色,其中包含一些在您的情况下不可用的颜色

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2015-09-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-06-08
      • 2015-09-10
      • 1970-01-01
      相关资源
      最近更新 更多