Jim Jim - 1 year ago 85
Swift Question

Where in view lifecycle to update controller after modal UIViewController dismissed

I have a

with a
that needs to display either "lbs" or "kg". My app has a settings screen (another
) that is presented modally over the first view controller and the user can select either of the two units and save their preference. If the units are changed and the modal settings screen is dismissed, I of course want the label on the first view controller to be updated with the new units value (but without refreshing the whole view). I thought I knew how to make it work, but evidently I don't.

On my modal settings screen, I have a
to allow the user to select units. Anytime it's changed, this function updates

func saveUnitsSelection() {
if unitsControl.selectedSegmentIndex == 0 {
UserDefaultsManager.sharedInstance.preferredUnits = Units.pounds.rawValue
} else {
UserDefaultsManager.sharedInstance.preferredUnits = Units.kilograms.rawValue

Then they would likely dismiss the settings screen. So, I added this to
in my first view controller:

override func viewDidLoad() {

let preferredUnits = UserDefaultsManager.sharedInstance.preferredUnits
units.text = preferredUnits

That didn't work, so I moved it to
and that didn't work either. I did some research and some caveman debugging and found out that neither of those functions is called after the view has been loaded/presented the first time. It seems that
will be called a second time if I'm working within a hierarchy of
s managed by a
, but isn't called when I dismiss my modal
to reveal the
underneath it.

Edit 1:
Here's the view hierarchy I'm working with:
enter image description here

I'm kinda stuck at this point and not sure what to try next.

Edit 2:
The user can tap a 'Done' button in the navigation bar and when they do, the
function dismisses the Settings view:

class SettingsViewController: UITableViewController {

let preferredUnits = UserDefaultsManager.sharedInstance.preferredUnits
// some other variables set here

override func viewDidLoad() {

self.navigationController?.navigationBar.topItem?.title = "Settings"

navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Done", style: .Plain, target: self, action: #selector(self.dismissSettings(_:)))

if preferredUnits == Units.pounds.rawValue {
unitsControl.selectedSegmentIndex = 0
} else {
unitsControl.selectedSegmentIndex = 1

func dismissSettings(sender: AnyObject?) {
navigationController?.dismissViewControllerAnimated(true, completion: nil)


Answer Source


You misspelled viewWillAppear. You called it:

func viewWillAppear()

As far as Cocoa Touch is concerned, this is a random irrelevant function that hooks into nothing. You meant:

override func viewWillAppear(animated: Bool)

The full name of the first function is: "viewWillAppear"

The full name of the second function is: "viewWillAppear:animated"

Once you get used to this, the extreme method "overloading" that Cocoa Touch uses gets easier.

This is very different in other languages where you might at least get a warning.

The other lesson that everyone needs to learn when posting a question is: Include All Related Code!

Useful logging function I use instead of print or NSLog, to help find these things:

class Util {
    static func log(message: String, sourceAbsolutePath: String = #file, line: Int = #line, function: String = #function, category: String = "General") {
        let threadType = NSThread.currentThread().isMainThread ? "main" : "other"
        let baseName = (NSURL(fileURLWithPath: sourceAbsolutePath).lastPathComponent! as NSString).stringByDeletingPathExtension ?? "UNKNOWN_FILE"
        print("\(NSDate()) \(threadType) \(baseName) \(function)[\(line)]: \(message)")

[Remaining previous discussion removed as it was incorrect guesses]