How do I create a singleton view used in more than

2019-08-31 02:15发布

问题:

I have a picker (UIPickerView) that I think should be a singleton that will be displayed in more than one view. I am using a tabbed UI and 2 of the views use the same picker. How I can create one instance of the picker so that I can call reloadAllComponents on it when the data changes, and have both views show the same selection? EDIT: only one view is visible/active at a time.

In addition to the picker, there will be other controls in that common header. I have created a view from an xib that has the picker and also a button. I can place that on the 2 views in the storyboard.

// This is the view that is placed on each of the viewcontrollers
class RaceSelView: UIView {

    @IBOutlet var view: UIView!
    @IBOutlet weak var racePicker: RacePicker!
// racePicker is UIPickerView with delegates to supply the data

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
// I assume the racePicker is instantiated in UINib, but how do I override it so that I can return the singleton racePicker?
        UINib(nibName: "RaceSelView", bundle: nil).instantiate(withOwner: self, options: nil)

        addSubview(view) // I really don't understand this. Aren't I setting this view to be a subview of itself?

        racePicker.setDelegates() // RacePicker supplies its own delegates and gets data from globals

        globals.racePicker = racePicker // globals needs a reference so that it can call racePicker.reloadAllComponents() when the data changes, but right now there are 2 instances and therefore only one gets notified

        racePicker.setCurRace() // this sets the picker to the current value, but this is rather useless because each instance only hits this code once, not every time the view becomes active. If the picker is changed in one view, the other view shows the wrong value
        view.bounds = self.bounds
    }

    @IBAction func onReload(_ sender: Any) {
        getRaces()
    }
}

It draws and there are no exception, but there are 2 instances of that picker and therefore they are not coordinated. When one changes, the other should change. My instinct is to make it a singleton, but I don't now how to override its creation to return the singleton.

EDIT: I managed to do this by creating a UIPickerView singleton, then in the view controller for each of the 2 tabbed views:

override func viewDidLoad() {
    super.viewDidLoad()

    addSubView(globals.theRacePicker)
}

This seemed to work just fine. My understanding is that the view cannot be in 2x places at once, so addSubView removes it from the other before assigning it to the new. The problem is that I wanted a more complex header view that is shared between the 2 and wanted to use the GUI to define it. I will see if I can add the picker at runtime to the subview of the header....

回答1:

An instance of a UIView can't be displayed in more than 1 place at the same time since it has a single parent. It's ok to have one view for each part of the app you're going to display it in.

If you want to have two views in sync that can be handled by using the same model for both. You have plenty of options, you could go with MVC, MVVM, MVP and others to achieve this, but explaining how those work is beyond the scope of this question.

The gist of it is that you make sure that when the user inputs something in your view the model is updated, and if the model is updated by something other than user input the view gets updated.



回答2:

to make a class singleton , just use this static let shared = YourClassName() now when using this class just use it like this

let myView = YourClassName.shared


回答3:

I thought I would post the answer to my own question. I created a header view (RaceSelView) that loads from xib that has several controls in it. Make a UIView in both views where you want the singleton picker to appear. The header view is placed at that location. Whenever the tab is changed, or the controllerviews are loaded, find that view and add the singleton picker as a subview.

protocol HasHeader {
    func getHeaderLoc() -> UIView
    func reload()
}
class BaseXCView: UIViewController,HasHeader {

    override func viewDidAppear(_ animated: Bool) {
        setRacePicker() // after layout has happened so that the headerLoc will be positioned correctly
        reload()
    }
    func setRacePicker() {
        var hv = globals.raceSelView
        if hv == nil {
            hv = RaceSelView(frame: getHeaderLoc().frame)
            globals.raceSelView = hv
        }
        view.addSubview(hv!)
    }
    func reload() {
        fatalError("Must Override")
    }
    func getHeaderLoc() -> UIView {
        fatalError("Must Override")
    }
}

class XCTabBarController: UITabBarController, UITabBarControllerDelegate {
    override func viewDidLoad() {
        self.delegate = self
    }
    func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController) {
// this is called whenever the tab changes
        if let vc = viewController as? BaseXCView {
            vc.setRacePicker()
        }
    }
}

This seems better than having separate instances of the picker. The alternative would involve copying the state to/from each instance when they become active, and keeping track of which is active in order to know which one to tell to reload/redraw when the underlying data changes.

I am not certain this is the best approach, comments are welcome.