Executing a function when a view appears - Swift - ios

I'm building an app for iOS using the Swift language. I start with a table view controller as my root view controller, and then I have a secondary view controller in which a variable (passData) is defined. This all works fine, and it passes the data correctly (I think) from the secondary view controller back to the primary view controller. However, when the user returns back to the primary view controller, I need a function to execute which will then add the 'addTitle' value to an array. I know how to add it to the array, but...
I don't know how to initiate the function when the view is returned to. What I mean is, after the user is finished on the secondary view controller AND the variable "passData" is defined, they will then push the back button on the navigation bar. I then need the primary view controller to recognise that it is once again being displayed to the user, and then execute the following code:
tableData += [passData]
tableSubtitle += [passDescription]
I have tried the following:
override func viewDidAppear() {
tableData += [passData]
tableSubtitle += [passDescription]
}
But this gives the error as Method does not override any method from its superclass.
Essentially, I just need to know how to start a function when the view displays. How can i achieve this?

you need to call super.viewDidAppear(animated) and the method signature takes a Bool so you should say:
override func viewDidAppear(animated: Bool)
ProTip: If you want to override a method you can just start typing the method name you want to overload and Xcode will auto suggest the method name and fill in the override declarative. So on a new line start typing viewDid and you should see the viewDidAppear method in the autocompletion drop down. Pressing enter will complete the method signature for you.

Related

When calling a function from another file the app crashes

I am new to Swift so this will hopefully be something simple but nothing that I have found has helped.
I am using SWRevealViewController to add a side menu onto my app, it basically creates a table view controller onto the side of the regular view controller. When the user taps on one of the cells I want to be able to call a function that is in the regular view controller.
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
ViewController().testFunction()
}
This then calls the function:
func testFunction() {
button1.setTitle("TEST", for: .normal)
}
Now, the app calls this function correctly but I am left with the error
fatal error: unexpectedly found nil while unwrapping an Optional value
If I ask the function to Print("test") instead of changing the button title, that works absolutely fine. When I try calling the function from the same regular view controller there are no errors and the button changes text.
The cause for crash is this line:
ViewController().testFunction()
What you do is you create a brand new instance of ViewController and call a method that does something with one of its properties. Now, we can safely assume that your button1 is a button you created in Interface Builder - therefore it doesn't exist by the time you are trying to access it.
What you should be doing is call this method on some already existing instance of ViewController, not creating a brand new instance of it (which doesn't make much sense anyway - it's not in the navigation stack, it just exists locally in your didSelectRowAt)

Add analytics event to log each ViewController's name in the project

I have a project in which I need to log an analytics event whenever any View Controller (log the name of the View Controller) comes on screen.
I was trying to avoid littering all of my existing View Controller classes with call to the analytics SDK.
I tried making an AnalyticsViewController and all my View Controllers would subclass this View Controller, and then I add analytics event in AnalyticsViewController class's viewDidLoad method. But the problem with this approach is that AnalyticsViewController does not which child View Controller is the call coming from.
I am using Swift 3.0. I believe that Swift with its powerful language features should be able provide me with an abstraction of some sorts.
Is there any way through this problem without littering all the View Controllers?
You were on the right track. Making a UIViewController parent class is a good idea.
In viewDidLoad method you can just add this:
let className = NSStringFromClass(self.classForCoder)
It will give you the name of current loaded view controller and then you can use that name in your event to specify which view controller was actually loaded.
Edit: added example.
So your parent's viewDidLoad would look something like this:
override func viewDidLoad() {
super.viewDidLoad()
let className = NSStringFromClass(self.classForCoder)
sendEvent(withViewControllerName: className)
}
The answer given by #JPetric is an amazing starting point. I just had to do a little modification to get it to work.
I've put this in my AnalyticsViewController to retrieve the name of the current subclass.
private func currentClassName() -> String? {
return NSStringFromClass(self.classForCoder).components(separatedBy: ".").last
}

UIKeyCommands don't work when intermediary viewController contains two viewControllers

I believe this is a non-trivial problem related to UIKeyCommands, hierarchy of ViewControllers and/or responders.
In my iOS 9.2 app I have a class named NiceViewController that defines UIKeyCommand that results in printing something to the console.
Here's NiceViewController:
class NiceViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let command = UIKeyCommand(input: "1", modifierFlags:UIKeyModifierFlags(),
action: #selector(keyPressed), discoverabilityTitle: "nice")
addKeyCommand(command)
}
func keyPressed() {
print("works")
}
}
When I add that NiceViewController as the only child to my main view controller all works correctly - pressing button "1" on external keyboard (physical keyboard when used in simulator) works like a charm. However when I add a second view controller to my main view controller the UIKeyCommands defined in NiceViewController stop working.
I'd love to understand why does it happen and how to ensure that having multiple child view controllers to my main view controller doesn't stop those child view controllers from handling UIKeyCommands.
Here is my main view controller:
class MainViewController: UIViewController {
let niceViewController = NiceViewController()
let normalViewController = UIViewController()
override func viewDidLoad() {
super.viewDidLoad()
self.view.addSubview(niceViewController.view)
self.addChildViewController(niceViewController)
self.view.addSubview(normalViewController.view)
// removing below line makes niceViewController accept key commands - why and how to fix it?
self.addChildViewController(normalViewController)
}
}
I do not believe this is a problem with UIKeyCommands
In iOS, only one View Controller at a time may manage key commands. So with your setup, you have a container view controller with a couple child view controllers. You should tell iOS that you would like NiceViewController to have control of key commands.
Defining First Responders
At a high level, in order to support key commands, you not only must create a UIKeyCommand and add it to the view controller, but you must also enable your view controller to become a first responder so that it is able to respond to the key commands.
First, in any view controller that you would like to use key commands for, you should let iOS know that that controller is able to become a first responder:
override func canBecomeFirstResponder() -> Bool {
// some conditional logic if you wish
return true
}
Next, you need to make sure the VC actually does become the first responder. If any VCs contain some sort of text fields that become responders (or something similar), that VC will probably become the first responder on its own, but you can always call becomeFirstResponder() on NiceViewController to make it become the first responder and, among other things, respond to key commands.
Please see the docs for UIKeyCommand:
The system always has the first opportunity to handle key commands. Key commands that map to known system events (such as cut, copy and paste) are automatically routed to the appropriate responder methods. For other key commands, UIKit looks for an object in the responder chain with a key command object that matches the pressed keys. If it finds such an object, it then walks the responder chain looking for the first object that implements the corresponding action method and calls the first one it finds.
Note: While someone is interacting with the other VC and it is the first responder, NiceViewController cannot be the first responder at the same time, so you might want some key commands on the other VC as well.
Why this isn't always necessary
When only one VC is presented, iOS appears to assume that it will be the first responder, but when you have a container VC, iOS seems to treat the container as the first responder unless there is a child that says it is able to become the first responder.
Following #Matthew explanation solution is adding becomeFirstResponder() request; in viewDidAppear instead of viewDidLoad resolve my similar problem.
Swift4
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
becomeFirstResponder()
print("becomeFirstResponder?: \(isFirstResponder)")
}
I found while experimenting with this that if you manually call becomesFirstResponder() on the child view controllers, it allows you to have multiple first responders and all key commands show up when hitting command.
I'm not sure why this works exactly as surely you're only supposed to have a single firstResponder at any one time.

How to run a function when an app first loads

I'm trying to write a simple to-do list in Swift that will store the list as an array of Strings and then call it back from memory when the app loads.
I've got the following code:
var itemList = [String]()
func loadData() -> [String] {
var arr = [String]()
if NSUserDefaults.standardUserDefaults().objectForKey("storedData") != nil {
arr = NSUserDefaults.standardUserDefaults().objectForKey("storedData")! as! [String]
}
else {
arr = ["Nothing to do..."]
}
return arr
}
func saveData(arr: [String]) {
NSUserDefaults.standardUserDefaults().setObject(arr, forKey: "storedData")
}
Where I'm getting stuck is in where to place the call to loadData(). This is an app that has two view controllers (one for the list, one for an add item setup), so if I place the loadData() call in viewDidLoad() for the main view controller, the array is called back in from memory (and over-written) every time I switch back to the main view controller.
Where is the best place to call this so that it will load once only, upon the app starting up?
the array is called back in from memory (and over-written) every time I switch back to the main view controller.
No. viewDidLoad only loads once, when the app starts. Only viewWillApprear and viewDidAppear get called everytime the viewcontroller changes.
Also you could make your code a bit more compact by using if let:
if let storedData = NSUserDefaults.standardUserDefaults().objectForKey("storedData") as! [String]{
arr = storedData
}
But if you want to make sure to load this only once, you can put it in your AppDelegate file in your applicationDidFinishWithOptions method.
But you'd have to make a variable in your AppDelegate file which you can access from your viewController.
viewDidLoad() only happens when the View Controller is first instantiated. If it is your root view controller you can have it in viewDidLoad().
The other goes, viewDidLoad > viewWillAppear > viewDidAppear. After the view is first loaded only the latter 2 methods are called whenever you navigate.
you can also always register for a NSApplicationDidFinishLaunchingNotification notification at the notification center
check it out here:
https://developer.apple.com/library/mac/documentation/Cocoa/Reference/ApplicationKit/Classes/NSApplication_Class/index.html#//apple_ref/c/data/NSApplicationDidFinishLaunchingNotification
Use or overwrite respectively
application(_:didFinishLaunchingWithOptions:)
of your application delegate.
That is called only once upon application launch.
See
https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIApplicationDelegate_Protocol/#//apple_ref/occ/intfm/UIApplicationDelegate/application:didFinishLaunchingWithOptions:
viewDidLoad is called after the view for a single controller is first loaded. It shouldn't be called more than once for the life-cycle of a single viewController. Maybe it is possible that, if you are not calling "super.viewDidLoad()" in your own viewDidLoad method, then it may be called again? While you can generally assume that the rootViewController for an application is only created once, I think it's theoretically possible that it might be cleared out of memory by the app if required and then recreated again - so I would never assume it's only called once.
One thing you could do is just set a boolean (default false) to true whenever you load the data and then not call it again if the flag is already set to true.
Alternatively, it's a good idea to separate the data management from your viewControllers. A relatively simple solution would be to have a class called "AppData" say, which might be a singleton (so you can only ever have one instance of it) or a member of your AppDelegate. Then, in your app delegate's "applicationDidFinishLaunchingWithOptions method, you could create the one instance of the AppData class and call the loadData method on it. This class would then live independently of whichever view is currently showing, and the current view could call methods on this object to load/save/update data as required.

Having issues with nib from ios_google_places_autocomplete

Thank you for viewing this page.
I have downloaded the following from GH: https://github.com/watsonbox/ios_google_places_autocomplete
It uses a nib file to initiate a autocomplete feature within a ViewController (in the Main Storyboard.
Issues
The following issues are hindering my progress;
I am unable to close the nib view using the X (or Stop button). The
nib loads via ViewDidLoad, therefore every time it dismisses itself,
it will be shown again. I have attempted to do the following but it
does not work.
When any cell is selected, I am unable to go back to the ViewController I originally navigated from. (same as point 1,
however should happen once I select any of the cells).
extension LocoSearch: GooglePlacesAutocompleteDelegate {
func placeSelected(place: Place) {
println(place.description)
println(place.id)
var locoResult = PFUser.currentUser()
locoResult["placeDesc"] = place.description
locoResult["placeId"] = place.id
locoResult.pin()
self.performSegueWithIdentifier("locoDone", sender: self)
}
func placeViewClosed() {
dismissViewControllerAnimated(true, completion: {
self.performSegueWithIdentifier("locoDone", sender: self)
})
To stop the autocomplete controller from loading every time the view loads, either remove it from viewDidLoad (and put it in a button click handler, for example), or if that's really where it belongs then perhaps use a variable stored property to store the currently selected Place and only show the autocomplete controller if none exists.
placeSelected is the correct callback for handling selections. Perhaps you should dismiss the autocomplete view before performing the segue as you do in your close handler? Please link a Github project with this issue if you really can't get it working.

Resources