Having issues with nib from ios_google_places_autocomplete - ios

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.

Related

Swift: Edit value of Label when pressing a button

I'm trying to learn Swift and I'm playing around with the basics of variables. I made an app where when you press a button, the value of a label changes, and while the button that adds 1 works, the one that subtracts one doesn't. If I change the first button (plusone) to subtract one, then it works, so I assume it is something to do with how the code is parsed? Would you be kind enough to explain how this works? Thanks in advance.
The code is here:
import UIKit
class ViewController: UIViewController {
var value = 0
#IBOutlet var number: UILabel!
#IBAction func plusone(_ sender: Any) {
value = value+1
number.text = "\(value)"
}
#IBAction func plustwo(_ sender: Any) {
value = value-1
number.text = "\(value)"
}
}
And the app looks like this if that matters:
Screenshot
The second button is named 'plustwo' because I haven't figured out where else I need to rename it, if I just rename it here the app displays a blank screen and nothing else.
From the comments:
I changed plustwo to -2 and it works as intended. Pressing Add One
adds one and pressing Substract One substracts one. I am not sure why.
The behavior you describe tells me that you have your Subtract One button wired to both the plusone and plustwo functions. You can check this by control-clicking on your button in Interface Builder and checking the connections. If you see TouchUpInside wired to two functions, remove one by clicking on the x to delete the connection.
Another way you can verify the behavior is to add logging print statements to your functions such as print("in plusone") and print("in plustwo"). When you press Subtract One, if you see both messages in the console then you know you have your button connected to both #IBActions.
How did this happen? This frequently happens when you copy a button in Interface Builder that already has a connection to an #IBAction. Connecting it to a new #IBAction doesn't replace the first one, it just connects to both of them and calls both when the button is pressed.
The second button is named 'plustwo' because I haven't figured out
where else I need to rename it, if I just rename it here the app
displays a blank screen and nothing else.
To rename the function, you need to change the name of the function and remove and reconnect the connection in Interface Builder. As described above, control-click on the button in Interface Builder and remove the connection to the old function name, then connect the button to the new function.

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.

Checking the current view state after block/closure completion

Within a asynchronously executed block/closure, I want to get a check on my current state before I executed anything within that block.
A common example of where this presents itself is segueing to the next View Controller after a NSURLsession request.
Here's an example:
#IBAction func tappedButton(sender: UIButton) {
//This closure named fetchHistorical goes to the internet and fetches an array
//The response is then sent to the next view controller along with a segue
Order.fetchHistorical(orderID, completionHandler: { (resultEnum) -> () in
switch resultEnum {
case .Success(let result):
let orderItemsArray = result.orderItems!.allObjects
self.performSegueWithIdentifier("showExchanges", sender: orderItemsArray)
default:
let _ = errorModal(title: "Error", message: "Failed!")
}
})
}
Assume that the user has been impatient and tapped this button 3 times.
That would mean this function will be called three times and each time it would attempt to segue to the next view controller (iOS nicely blocks this issue with "Warning: Attempt to present on whose view is not in the window hierarchy!")
I wanted to know how do you folks tackle this problem? Is it something like ... within the closure, check if you are still in the present viewcontroller ... if you are, then segueing is valid. If not, you probably have already segued and don't execute the segue again.
***More generally, how are you checking the current state within the closure because the closure is executed asynchronously?
Since the closure isn't executing on the main thread the state would be in accurate if you check it here (as you stated). You can use GCD to go to the main thread and check the state there. There are a couple of ways you can keep this code from running multiple times. If it will take some time to perform the calculations you can use an acitivity indicator to let the user know the app is busy at the moment. If you want the user to still have the option of pressing the button you can put a tag like:
var buttonWasTapped:Bool = false //class property
#IBAction func tappedButton(sender: UIButton) {
if !self.buttonWasTapped{
self.buttonWasTapped = true
}
}
Then change it back to false on viewDidAppear so they can press once every time that page is shown.
When starting some task that will take some time to complete I would do two things;
Show some sort of activity indicator so that the user knows something is happening
Disable the button so that there is further indication that the request has been received and to prevent the user from tapping multiple times.
It is important that you consider not only the correct operation of your app but also providing a good user experience.

delegate equals to nil

I have a problem with my iconMenu it doesn't work, I can only slide to open my menu, I have this error only I change page from my first page ("Accueil") instead of using my slide out menu. Yes because I have a slide out menu and links on my first page, like that:
So when I use links of my first view, my iconMenu is like disabled, it's because here:
#IBAction func menuNosOffresTapped(sender: AnyObject) {
delegate?.toggleLeftPanel!()
}
My app can't go at the function toggleLeftPanel here: Call a function of an another class (protocol) someone told me that the problem could came from my delegate.
I did some test, I lost my delegate when I use first page links and when I use my Slide out menu.
App is here: https://github.com/Vkt0r/SlideOutSideBarTest
So I think to get access to toggleLeftPanel, I have to get my delegate.
the problem is your NosOffresViewController is missing the menuTapped action.

presenting a modal in viewdidappear with swift

I'm new to swift and ios programming in general. I'm trying to display a modal view when my app first loads which it does. The problem I'm running into is that my modal keeps appearing over and over and over. Not sure where I'm going wrong.
BONUS QUESTION: Ultimately I'd like this to only happen the first time the user opens the app.
class ViewController: UIViewController {
var introModalDidDisplay = false
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
showIntroModal()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func showIntroModal() {
if (!introModalDidDisplay) {
println(introModalDidDisplay)
introModalDidDisplay = true
let intro = self.storyboard?.instantiateViewControllerWithIdentifier("introModal") as IntroModalViewController
intro.modalPresentationStyle = UIModalPresentationStyle.FormSheet
self.presentViewController(intro, animated: true, completion: nil)
}
}
}
Found it. My "intro" class was extending ViewController rather than UIViewController...apparently that's bad. Thanks for the help! Sorry for the wild goose chase.
When you close the modal view you show your ViewController view again, firing viewDidAppear once more and entering an infinite loop of showing your modal view, since the first view is always "appearing"
I'd suggest doing this in viewDidLoad, as the view is supposed to load only once. Try and experiment with these events and see when they are fired.
As for firing only once I'd suggest setting a flag in localStorage (plist) indicating whether it's the first time the user opens the app or not. For example set a flag in the first view's viewDidLoad and if that flag is false show your modal view and set the flag to true.
Here's a question about writing in plists in Swift: Save Data to .plist File in Swift
A couple of observations:
Are you saying that you're seeing this appear again and again while you're using the app? That would suggest that you have multiple instances of this view controller instantiated. For example, you might be doing a segue back to this view controller (which will create new instance) rather than unwinding/popping/dismissing back to it (which will return to the previous instance).
I'd suggest you have a breakpoint or logging statement in viewDidLoad and confirm that you see this once and only once. If you're seeing it multiple times, that means that you have some circular reference amongst your storyboard scenes (and, BTW, you are abandoning memory, a type of leak).
To handle this only presenting itself once between uses of the app, you need to save this introModalDidDisplay in some form of persistent storage. Often NSUserDefaults is used for this. For example, define introModalDidDisplay to look up the status in the standard user defaults:
var introModalDidDisplay = NSUserDefaults.standardUserDefaults().boolForKey("introModalDidDisplay")
Then your showIntroModal can update this setting in the user defaults:
func showIntroModal() {
if !introModalDidDisplay {
introModalDidDisplay = true
NSUserDefaults.standardUserDefaults().setBool(true, forKey: "introModalDidDisplay")
NSUserDefaults.standardUserDefaults().synchronize()
let intro = self.storyboard?.instantiateViewControllerWithIdentifier("introModal") as IntroModalViewController
intro.modalPresentationStyle = UIModalPresentationStyle.FormSheet
self.presentViewController(intro, animated: true, completion: nil)
}
}
Clearly, you can use whatever persistent storage technique you want (plist, archive, user defaults, Core Data, SQLite), but the idea is the same: Retrieve the status from persistent storage and once the intro screen has been presented, update that persistent storage accordingly.
By the way, by looking this up in persistent storage, we also fix the symptom of the problem I discussed in point #1. But you really want to fix the root cause of that first point, too, because otherwise you'll be leaking memory (if, of course, you're really instantiating multiple copies of the ViewController class).
By the way, looking ahead to the future, rather than storing just a boolean, I might suggest storing a version number identifier, too. That way, when you release version 2.0 of the app, you'll be able to decide whether the v1.0 users might see the updated intro screen again (or perhaps a custom one that focuses on what's new).

Resources