UITextField.text value won't change on GooglePlaceAutocomplete - swift

I am working on a form app on iOS 11 using Swift 4. I'd like to put the return of Google's selector (PlaceAutocomplete) in the UITextField of one of the cells of my UITableView. The issue is that despite assigning new values, the text fields remain blank. After debugging for a while it seems that something is discarding the content of my UITextField when the location is being selected.
These are the GooglePlaceAutocomplete callbacks with the result value assignment :
extension MyUITableViewController, GMSAutocompleteViewControllerDelegate {
func viewController(_ viewController: GMSAutocompleteViewController, didAutocompleteWith place: GMSPlace) {
print("Place name: \(place.name)")
let indexPath4 = IndexPath(row: 4, section: 0)
let cell4 = tableView.cellForRow(at: indexPath4) as! InputPlacePicker
cell4.inputTextField.text = place.name
dismiss(animated: true, completion: nil)
}
func viewController(_ viewController: GMSAutocompleteViewController, didFailAutocompleteWithError error: Error) {
print("Error: ", error.localizedDescription)
}
func wasCancelled(_ viewController: GMSAutocompleteViewController) {
dismiss(animated: true, completion: nil)
}
}
And this is the call to GooglePlaceAutocomplete
#objc func pickPlace() {
let autocompleteController = GMSAutocompleteViewController()
autocompleteController.delegate = self
present(autocompleteController, animated: true, completion: nil)
}

Changing the cells directly is probably not a good idea, since everytime the UITableView is reloaded, cells get recycled and depending on your tableView(_:cellForRowAt:) implementation, your UITextFieldmight get overwritten or end up somewhere else.
It is a better practice to have some kind of model or state in your view controller, which holds the state for each cell in the different sections.
Change this state (for instance a list of place names) in your GooglePlacesAutocomplete delegate and call reloadData on your tableView to get the new data from the changed state.
In your tableView(_:cellForRowAt:) implementation, you set inputTextField.text = place.name before you return it, this way all cells should end up with the right place name from your internal state.

Okay! I understood what you said Kie and you're right. I'll implement something to save my field values.
But in my case, it is the GMSAutocompleteViewController which reloads my UITableView because it was called with present(). I changed that to add it to my navigationController and now my field persists.
Thanks

Related

How to i fix the "Cannot find 'present' and 'dismiss' in scope" error in swift

I am trying to allow a user to pick an image and set it as there banner on there profile page but when i write my code i keep getting the error "Cannot find 'present' in scope" when trying to present the imagePicker and i also get "Cannot find 'dismiss' in scope" when trying to dismiss the imagePicker
here is my code for the above:
//this function gets called when the user tapes on the banner to change the image
class ProfileHeader: UICollectionViewCell, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
#objc func handleBannerTapped() {
let imagePicker = UIImagePickerController()
imagePicker.delegate = self
imagePicker.allowsEditing = true
present(imagePicker, animated: true, completion: nil)
}
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
guard let bannerPicture = info[UIImagePickerController.InfoKey.editedImage] as? UIImage else {
imageSelected = true
return
}
imageSelected = true
profileImage.layer.masksToBounds = true
profileBanner.setImage(bannerPicture.withRenderingMode(.alwaysOriginal), for: .normal)
dismiss(animated: true, completion: nil)
}
}
I have used the same function in my sign up page where the user chooses a profile picture and it is working fine so i am confused as to why it doesn't work now for the banner
You can only present and dismiss on a view controller. But ProfileHeader is not a view controller. It's a cell.
So where's the view controller? It's up the responder chain from the cell. So walk up the responder chain until you come to the view controller, and present and dismiss on that.
Here's a utility method that will help you:
extension UIResponder {
func next<T:UIResponder>(ofType: T.Type) -> T? {
let r = self.next
if let r = r as? T ?? r?.next(ofType: T.self) {
return r
} else {
return nil
}
}
}
So now you can say:
if let vc = self.next(ofType: UIViewController.self) {
vc.present(imagePicker, animated: true, completion: nil)
}
And so on.
However, although that will cause your code to compile, and perhaps even appear to work correctly, I think your approach here is completely wrong-headed:
You should not be doing this work in the cell in the first place.
You should not be having a cell telling a view controller what to present / dismiss.
You should not be wantonly modifying the content of the cell.
A cell is a transient reusable object, so you cannot even really guarantee that the cell will still be there (or will still occupy the same row of the table) when the image has been chosen.
Instead, the cell should just tell the view controller, hey, the user wants to pick a photo, and stand back and let the view controller do the work. The work should consist of modifying the data model (and then reloading the cell).

How to call a function from another controller in swift

I set the Show Charts button on the DetailView Controller which triggers the getChartData function and shows me the values in display view in charts, now I want to call that function in the didselectrow on the main Viewcontroller so that the chart is loaded automatically, but it fails.
When I tried to call that function in didselectrow (DVC.getChartsData) I got the error "Thread 1: Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value"
DVC.getChartsData
Thread 1: Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value
ViewController:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let Storyboard = UIStoryboard(name: "Main", bundle: nil)
let DVC = Storyboard.instantiateViewController(withIdentifier: "DetailViewController") as! DetailViewController
DVC.getDetailName = coin[indexPath.row].name
let formatedRoundingPrice = (coin[indexPath.row].price as NSString).floatValue * currencymodel.indexValue
let formatedPrice = String (format: "%.3f", formatedRoundingPrice)
DVC.getDetailPrice = formatedPrice
self.navigationController?.pushViewController(DVC, animated: true)
let percentage = String ((coin[indexPath.row].percent as NSString).floatValue)
DVC.getDetailPercent = percentage
tableView.deselectRow(at: indexPath, animated: true)
//DVC.getChartData()
}
DetailViewController:
#IBAction func tapLineChart(_ sender: Any) {
getChartData()
}
func getChartData () {
let chart = HITLineChartView(frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: displayView.bounds.height))
displayView.addSubview(chart)
let max = String((priceResult.max() ?? 0.0).rounded(.up))
let min = String((priceResult.min() ?? 0.0).rounded(.down))
let maxChange = abs((listOfChanges.max()) ?? 0.0).rounded(.up)
let minChange = abs((listOfChanges.min()) ?? 0.0).rounded(.up)
absMaxPercentage = Int(maxChange > minChange ? maxChange : minChange)
titles = ["\(getDetailName) closing price is \(getDetailPrice)"]
print(data)
chart.draw(absMaxPercentage,
values: listOfChanges,
label: (max: max, center: "", min: min),
dates: namesArray,
titles: titles)
addCloseEvent(chart)
finalURL = baseURL + "bitcoin" + "/market_chart?vs_currency=usd&days=5"
print(finalURL)
getBitcoinData(url: finalURL)
}
How to load my charts tap on a specific tableview cell instead of tapping on tapLineChart.
https://imgur.com/fg2502P
https://imgur.com/C4AzaRY
https://imgur.com/jOrwujy
if you want to call a function on viewControllerB that you declare from viewController A.
just create the object of the class file you want to use the function from
var obj mainVC = MainViewController()
class MainViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
func commonMethod() {
print("From the main class")
}
}
Using that object, call the function in another file where you mean to use it
class OtherViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
mainVC.commonMethod()
// Do any additional setup after loading the view, typically from a nib.
}
}
Additionally, You can also create a new swift file, name it Global.swift, create all your functions that you want to use throughout the application here. They become "global functions"
You will want to use delegates or observers to pass data between view controllers.
I'm new to tutorials, but I wrote a bit about this here: https://www.eankrenzin.com/swift-blog/pass-data-throughout-your-app-with-observers-and-notifications-xcode-11-amp-swift-5
You should use optional binding to unwrap your VC let DVC = Storyboard.instantiateViewController(withIdentifier: "DetailViewController") as! DetailViewController
Your code is crashing because of that line. Check your interface builder to make sure the identifier is correct. Edit: this line was not causing a crash, but it is still better to use optional binding.The line is: https://imgur.com/CVP1x6H
NOTE: It is terrible practice to litter your app with instances when delegates and observers could work. Also do NOT have globals. Globals are disastrous for debugging and create tech debt.

Run a function N time anywhere in the app in Swift

I trying to make a calling app for my project and I want to add a function that keeps checking if someone if calling. My app uses Firebase where I have a key for each users to check if he made a call or not.
There's two problem I am facing here, the first one is, as I said, that I want my function to keep checking anywhere in the app for an incoming call. The other problem is that i have a viewcontroller that I want to pop up when someone is calling. I have found this code on github but it uses navigationcontroller which I am not using in my app :
extension UIViewController{
func presentViewControllerFromVisibleViewController(viewControllerToPresent: UIViewController, animated flag: Bool, completion: (() -> Void)? = nil) {
if let navigationController = self as? UINavigationController, let topViewController = navigationController.topViewController {
topViewController.presentViewControllerFromVisibleViewController(viewControllerToPresent: viewControllerToPresent, animated: true, completion: completion)
} else if (presentedViewController != nil) {
presentedViewController!.presentViewControllerFromVisibleViewController(viewControllerToPresent: viewControllerToPresent, animated: true, completion: completion)
} else {
present(viewControllerToPresent, animated: true, completion: completion)
}
}
}
For your question on monitoring when incoming calls occur and to be called as a result, see this answer. It's probably what you need (I've never tried it, however). The example shows creating a CXCallObserver and setting your AppDelegate as delegate.
For your second question, I'd first try this answer which leverages the window.rootViewController so you can do this from your AppDelegate. Generally, the root VC is your friend when trying to do UI your AppDelegate. :)
A better answer based on Alex's added comments:
I'd first look at how to set up an observer to your Firebase model so that you can get a callback. If you don't have a way to do that, I'd use KVO on the Firebase model property. But to do exactly as you're requesting, and to do so lazily from AppDelegate (rather than from a singleton), see this code:
// In AppDelegate
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey : Any]? = nil) -> Bool
{
self.timerToCheckForCalls = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(timerFired), userInfo: nil, repeats: true)
}
func timerFired()
{
let didCall = // TODO: query your Firebase model...
guard didCall == true else
{
return
}
self.displayCallerView()
}
func displayCallerView()
{
// See below link.
}
See this answer for how to present your view controller, even when your app might be showing an action sheet, alert, etc... which I think you'd especially value since you need to display the caller regardless of what your app is doing.
Note while user is scrolling a UITextView, the timer won't fire yet. There may be other situations where the timer could be delayed too. So it really would be best to observe your Firebase model or receive a KVO callback than to use a timer.
If you want to make a function that can be called from anywhere, use a singleton pattern. You can also use that to store your special view controller.
Bear in mind that this code SHOULD NOT considered fully functioning code and will need to be customized by you to suit your needs.
class MyClass {
let shared = MyClass()
var viewController: SpecialViewController?
func checkForCall() {
// do function stuff
}
func getSpecialViewController() {
let storyBoard = UIStoryboard.init(name: "main", bundle: nil)
// keep it so we don't have to instantiate it every time
if viewController == nil {
viewController = storyBoard.instantiateViewController(withIdentifier: "SomeViewController")
}
return viewController
}
}
// Make an extension for UIViewController so that they can all
// use this function
extension UIViewController {
func presentSpecialViewController() {
let vc = MyClass.shared.getSpecialViewController()
present(vc, animated: false, completion: nil)
}
}
Somewhere in your code:
// in some function
MyClass.shared.checkForCall()
Somewhere else in code:
presentSpecialViewController()

Update label in UIPageView

Relatively new to Xcode/Swift, but have some experience in other languages.
I am at the infancy stages of an application and am trying to achieve the following functionality:
In a UIPageView environment, when the user swipes forward, the label (whose value is an integer) increases by 1. When the user swipes back, the label decreases by 1.
I have a single UIViewController as the content page with storyboard ID PageContentViewController.
Essentially, my thoughts are to declare an integer variable initially set to 0 and then create a swipedBack() and swipedRight() function which updates the value of the variable and then sets the text of the label on the PageContentViewController to the value of that variable. The functions then return the new PageContentViewController.
I am getting some peculiar behaviour. Namely, the app is building and running fine.
The initial screen loads with a label of 0. Then I swipe forward, and it updates to 1. BUT THEN... if i swipe forward again, the label stays as one. BUT THEN... if I keep going forward, it then acts as desired - goes to 2, then 3. Then if i reverse direction, it becomes even more peculiar.
If I'm at a value of four, then swipe back, It will decrease to 3. But then swiping back again it will go back to 4, then continuing swiping, it will behave as expected (4,3,2,1...). I obviously have a conceptual misunderstanding and would appreciate some help.
Here's my code:
import UIKit
class ViewController: UIPageViewController, UIPageViewControllerDataSource {
var arrPageTitle: NSArray = NSArray()
var t: Int = 0
override func viewDidLoad() {
super.viewDidLoad()
self.dataSource = self
self.setViewControllers([getViewControllerAtIndex()] as [UIViewController], direction: UIPageViewControllerNavigationDirection.forward, animated: false, completion: nil)
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
return swipedBack()
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
return swipedForward()
}
func getViewControllerAtIndex() -> PageContentViewController {
let pageContentViewController = self.storyboard?.instantiateViewController(withIdentifier: "PageContentViewController") as! PageContentViewController
pageContentViewController.strTitle = "\(t)"
return pageContentViewController
}
func swipedForward() -> PageContentViewController {
let pageContentViewController = self.storyboard?.instantiateViewController(withIdentifier: "PageContentViewController") as! PageContentViewController
t = t + 1
pageContentViewController.strTitle = "\(t)"
return pageContentViewController
}
func swipedBack() -> PageContentViewController {
let pageContentViewController = self.storyboard?.instantiateViewController(withIdentifier: "PageContentViewController") as! PageContentViewController
t = t - 1
pageContentViewController.strTitle = "\(t)"
return pageContentViewController
}
}
I think this is probably because once you change directions, the most recent UIViewController is still in memory, and thus the delegate function is not called.
I would recommend using the pageViewControllerWillTransitionTo function instead. This should be called every time you swipe. Similarly you could use pageViewControllerDidFinishAnimating if you want the call to happen after you swipe.
In order to detect which direction you swiped, you could try and use a UISwipeGestureRecognizer at the same time, and call functions that will increment/decrement the variable value. Actually, now that I think about it, you may just be able to use this gesture recognizer without the delegate function. Up to you.

Storing Individual UISwitch States in UITableView

I have a UITableView that has Two ProtoType Cells both with separate TableViewCell subclassess. In one of the prototype cells I have multiple switches. The user can select the item they want in the table and turn the switch on that corresponds to the item. I would like to be able to store the UISwitchstate so if the user navigates away and comes back they will see what they have selected previously.
I'm trying to store the UISwitchstate in a dictionary and then call the state back when the table gets reloaded.
Here is the code I have so far:
#IBAction func switchState(sender: AnyObject) {
if mySwitch.on{
savedItems = NSMutableDictionary(object: mySwitch.on, forKey: "switchKey")
standardDefaults.setObject("On", forKey: "switchKey")
}
else{
standardDefaults.setObject("Off", forKey: "switchKey")
and then this is in the awakeNib section:
override func awakeFromNib() {
super.awakeFromNib()
self.mySwitch.on = NSUserDefaults.standardUserDefaults().boolForKey("switchKey")
NSUserDefaults.standardUserDefaults().registerDefaults(["switchKey" : true])
}
thanks for the help.
What you need to do is:
Having a dictionary (or any other data type) that is from the exact same size as your data source.
And in your
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
you need to that dictionary for the row indexPath.row and then mainpulate your cell
Note, your dictionary should be static
Putting my answer here instead of a comment so I can hopefully explain things better. You need to have some sort of system to keep up with which dictionary item corresponds with which UISwitch. Your best bet would probably be to have a dictionary var uiDictionary = [String : Bool]() where your keys are a string that you know corresponds to a specific switch. Then, in your cellForRowAtIndexPath: you would try to access each dictionary item, check if it's the one for the switch you're trying to set, and then set it. Don't know your exact setup, but it would look something like this...
func cellForRowAtIndexPath() {
//other stuff here
//now set your switches
for (key, value) in uiDictionary {
switch(key) {
case "Switch1":
if value == true {
cell?.switch1.setOn(true, animated: true)
} else {
cell?.switch1.setOn(false, animated: true)
}
break
case "Switch2":
if value == true {
cell?.switch1.setOn(true, animated: true)
} else {
cell?.switch1.setOn(false, animated: true)
}
}
}
}