swift change theme using delegate and protocol on

2020-08-04 10:11发布

问题:

I'm trying to use delegate and protocol first time.

I want to change the theme across many view controller.

Then on any controller which has protocol to change theme

When I go to this controller now I expect theme to be new but is old.

I do not go from theme controller to where them has changed


My code

protocol ThemeDelegate: class {
    func changeTheme(theme: UIColor)
}


class FirstController: UICollectionViewController, UICollectionViewDelegateFlowLayout, ThemeDelegate {

    var newTheme: UIColor  = .red

    func changeTheme(theme: UIColor) {
        newTheme = theme
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = newTheme
    }
}

ThemeController {

    weak var themeDelegate: ThemeDelegate?

    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {        
        let theme = .blue        
        themeDelegate?.changeTheme(theme: theme)        
    }

    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {

        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: themeCellId, for: indexPath) as! ThemeCell
        cell.themeImageView.image = UIImage(named: "theme cell image")
        return cell
    }
}

this is order

image2

回答1:

I'll try to make it simple:-

1) First, declare a protocol:-

protocol ThemeDelegate{
    func changeTheme(theme: String, fontColor: UIColor, alpha: CGFloat)
}

2) Have a variable of that protocol in your ThemeController:

open var themeDelegate: ThemeDelegate? = nil

3) Call the delegate functions through themeDelegate:- (you've done this right till this step)

func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
    themeDelegate?.changeTheme(theme: theme, fontColor: fontColor, alpha: alpha)
}

4) You need to conform your AnyController as the delegate, like yourThemeControllerInstance.delegate = self and you've done that as well.

It doesn't work because you've declared a new instance of ThemeController and have conformed AnyController to be the delegate of that new instance which supposedly has the same old theme:

override func viewDidLoad() {
    let themeController = ThemeController() // This is a new instance which you have created, so gets initialised with old theme. You have made your class to be the delegate of this instance
    themeController.themeDelegate = self
}

In order for your delegate to work as expected, you need the same instance of ThemeController where you change your theme



回答2:

You can set delegate but careful view needs a be load.

You do this also with notifications and send all viewControllers with conforms Theme

1 - Your Step

import UIKit

protocol ThemeDelegate: class {
    func changeTheme(theme: String, fontColor: UIColor, alpha: CGFloat)
}

class ThemeController: UIViewController {

    weak var themeDelegate: ThemeDelegate?

    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
        themeDelegate?.changeTheme(theme: theme, fontColor: fontColor, alpha: alpha)
    }

}

class XViewController: UIViewController {


    // Create an instance
    lazy var themeController = ThemeController()


    override func viewDidLoad() {
        super.viewDidLoad()

        // We need call loadView becase collectionView needs to be show
        themeController.loadView()
        themeController.themeDelegate = self

    }

}
extension XViewController: ThemeDelegate {
    // And we conform ThemeDelegate
    func changeTheme(theme: String, fontColor: UIColor, alpha: CGFloat) {
        // TODO UI
    }
}

2 - Observer Step

// Sometimes we need update multiple viewControllers and we should use notification and observers


// We create a model
struct Theme {
    let theme: String
    let fontColor: UIColor
    let alpha: CGFloat
}

// We need a protocol for we don't want all view controller listen theme
protocol Themeable: class {
    func listenTheme()
    func didThemeChange(theme: Theme)
}


// Global notification name
let themeableNotificationName = Notification.Name(rawValue: "ThemeableNotification")

// Our protocol extension and observer notification
extension Themeable where Self: UIViewController {
    func listenTheme() {
        NotificationCenter.default.addObserver(forName: themeableNotificationName, object: nil, queue: nil) { [weak self] notification in
            guard let theme = notification.object as? Theme else { return }
            self?.didThemeChange(theme: theme)
        }
    }

}


// Notification sender themeController
class NotifyThemeController: UIViewController {


    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
        // Create a model and post
        NotificationCenter.default.post(name: themeableNotificationName, object: Theme(theme: "Lorem", fontColor: .red, alpha: 1.0), userInfo: nil)
    }

}

// YViewController
class YViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        // We need call this method for observer
        listenTheme()
    }


}
// YViewController conforms Themeable
extension YViewController: Themeable {
    func didThemeChange(theme: Theme) {
        // TODO UI
    }

}
// ZViewController
class ZViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        // We need call this method for observer
        listenTheme()
    }


}
// ZViewController conforms Themeable
extension ZViewController: Themeable {
    func didThemeChange(theme: Theme) {
        // TODO UI
    }

}

Have Fun!