Swift Cancelling the ActivityViewController causes fatal error - ios

Okay so I got my ActivityViewController to work fine when it is sharing like normal however whenever a user hits cancel after they invoked the ActivityViewController , I get the annoying fatal error.
fatal error: unexpectedly found nil while unwrapping an Optional value
So it looks like I did not unwrap my optionals well. Here's the share method :
#IBAction func share(sender: UIBarButtonItem) {
var memeedimage = generateMemedImage()
let activityViewController = UIActivityViewController(activityItems:[memeedimage] , applicationActivities: nil)
presentViewController(activityViewController, animated: true, completion: nil)
activityViewController.completionWithItemsHandler = {
(activity, success, returneditems, error) in
println("Activity: \(activity) Success: \(success) Items: \(returneditems) Error: \(error)")
self.save()
activityViewController.dismissViewControllerAnimated(true, completion:{
let memevc:MemeTableViewController = MemeTableViewController()
self.presentViewController(memevc, animated: true, completion: nil)
})
}
and the share method calls the save function which generates the object called meme using an implicitly unwrapped optional which causes the error :
func save(){
var meme : MemeObject!
meme = MemeObject(textFieldtop : texfieldtop.text! ,textFieldbottom : textfieldbottom.text! ,image : imagePickerView.image! , memedImage : generateMemedImage())
(UIApplication.sharedApplication().delegate as! AppDelegate).memes.append(meme!)
}
So I decided to safely unwrap the meme optional value but that invoked another problem
func save(){
var meme : MemeObject?
if let memez = meme{
meme = MemeObject(textFieldtop : texfieldtop.text! ,textFieldbottom : textfieldbottom.text! ,image : imagePickerView.image! , memedImage : generateMemedImage())
(UIApplication.sharedApplication().delegate as! AppDelegate).memes.append(meme!)
}
else{
println("Optionals man")
}
}
Now when the the object is not nil , "Optionals man" is printed which shouldn't happen and the completionwithitemshandler property in the share method didn't present the table view controller which should happen directly after the user shares the object.
Code for MemeTableViewController :
import UIKit
class MemeTableViewController : UIViewController,UITableViewDelegate,UITableViewDataSource
{
var memesz: [MemeObject]!
#IBOutlet var tableView: UITableView!
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
memesz = appDelegate.memes
//tableView.reloadData()
}
override func viewDidLoad() {
super.viewDidLoad()
navigationController?.hidesBarsOnSwipe = true
navigationController?.hidesBarsOnTap = true
}
//reserves the number of rows needed to display the image
func tableView(tableView : UITableView, numberOfRowsInSection section : Int)->Int
{
return memesz.count
}
//Reserves the row to be dequeued for display
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell:TableViewzCell = tableView.dequeueReusableCellWithIdentifier("MemesCell", forIndexPath: indexPath) as! TableViewzCell
let memezrow = memesz[indexPath.row]
cell.label1.text = memezrow.textFieldtop
cell.label2.text = memezrow.textFieldbottom
cell.imageview.image = memezrow.memedImage
return cell
}
//Method to do something when the row is selected
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let detailController = self.storyboard!.instantiateViewControllerWithIdentifier("FullScreenMeme") as! FullScreenMeme
detailController.meme = memesz[indexPath.row]
self.navigationController!.pushViewController(detailController, animated: true)
}
}
Any help please?

After looking your code, it seems like you haven't initialised the variable 'meme'.
func save(){
var meme : MemeObject? = MemeObject(textFieldtop : texfieldtop.text! ,textFieldbottom : textfieldbottom.text! ,image : imagePickerView.image! , memedImage : generateMemedImage())
if let memez = meme{
(UIApplication.sharedApplication().delegate as! AppDelegate).memes.append(meme!)
}
else{
println("Optionals man")
}
}
}
#IBAction func share(sender: UIBarButtonItem) {
var memeedimage = generateMemedImage()
let activityViewController = UIActivityViewController(activityItems:[memeedimage] , applicationActivities: nil)
presentViewController(activityViewController, animated: true, completion: nil)
activityViewController.completionWithItemsHandler = {
(activity, success, returneditems, error) in
println("Activity: \(activity) Success: \(success) Items: \(returneditems) Error: \(error)")
self.save()
let memevc = self.storyboard!.instantiateViewControllerWithIdentifier("MemeTableViewConroller") as! MemeTableViewController
self.presentViewController(memevc, animated: true, completion: nil)
}
}

Related

Swift: TableView with initializer returns nil. Trying to dataPass via protocol

rookie here. I created a two tableviews and one gets its data from the previous one. So it has init objects. And I have a view controller where I have a scrollview to display some scrollable images like a catalog. It is just like a menu with categories eventually leading to brands and you tap on the brand so its content comes after.
I do the API call in didSelectRow (for a reason) and use the protocol func. to get images from API to an array then into the scrollview and its subviews. But I can't properly initialize the VC where that tableview that holds the data exists. How can I properly initialize categoriesListVC then use its delegate func?
First VC where I pass data firstly.
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
let category = data[indexPath.row]
let vc = CategoriesList(brand: category.brand, url: category.url, brandName: category.brandSlugName)
vc?.title = category.category
navigationController?.pushViewController(vc ?? self, animated: true)
}
categoriesList VC with the TableView with init
init?(brand: [String], url: [String], brandName: [String]) {
self.brand = brand
self.url = url
self.brandName = brandName
super.init(nibName: nil, bundle: nil)
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
selectedRow2 = indexPath.row
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let vc = storyboard.instantiateViewController(withIdentifier: "CatalogView2") as! ScrollVC2
navigationController?.pushViewController(vc, animated: true)
dataCatalog() //API call - this works I checked. }
And this is how I tried to use its data on the final VC. Would this work? I printed categoriesList here and it gives nil. I probably don't understand something.
var categoriesList: CategoriesList?
override func viewDidLoad() {
super.viewDidLoad()
categoriesList?.delegate = self }
I am trying to use a delegate method in that final VC to use its object to pass data:
func catalogViewModelFromCategories(catalogViewModel: CatalogViewModel) {}
I didn't write the whole code to make it as short as possible. But delegate functions and other things are all in the real code.
In dataCatalog func there is only url session and decoder:
if let url = URL(string: urlString) {
let session = URLSession(configuration: .default)
let task = session.dataTask(with: url) {(data, response, error) in
if error == nil {
let decoder = JSONDecoder()
do {
let decodedData = try decoder.decode(CatalogViewData.self, from: data!)
let catalogFromCategories = CatalogViewModel(images: decodedData.catalogueimages, title: decodedData.cataloguename!)
let catalogViewModel = catalogFromCategories
DispatchQueue.main.async {
self.delegate?.catalogViewModelFromCategories(catalogViewModel: catalogViewModel)
}
} catch {
print(error)
}
return
}
}
task.resume()
}

how to keep a user login even if they close the app? I'm using swift 3, and Firebase

I'm new to coding and to Stack overflow, I'm trying to have a user stay logged in even after they close the app. I also don't want them to always see the login in screen. how do I do i go about keeping the user Login even if they close the app and re-open the app. I'm using Swift 3.0, Xcode 8, and Firebase.
import UIKit
import Firebase
import SwiftKeychainWrapper
class LoginViewController: UIViewController {
#IBOutlet weak var emailField: UITextField!
#IBOutlet weak var pwField: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
let tap: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(LoginViewController.dismissKeyboard))
//Uncomment the line below if you want the tap not not interfere and cancel other interactions.
//tap.cancelsTouchesInView = false
view.addGestureRecognizer(tap)
// Do any additional setup after loading the view.
}
func dismissKeyboard() {
//Causes the view (or one of its embedded text fields) to resign the first responder status.
view.endEditing(true)
}
#IBAction func loginPressed(_ sender: Any) {
guard emailField.text != "", pwField.text != "" else {return}
FIRAuth.auth()?.signIn(withEmail: emailField.text!, password: pwField.text!, completion: { (user, error) in
if let error = error {
print(error.localizedDescription)
}
if user != nil {
let vc = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "TabBarViewController")
self.present(vc, animated: true, completion: nil)
}
})
}
}
Below is my UsersViewController code it has the log-out button
import UIKit
import Firebase
class UsersViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var tableview: UITableView!
var user = [User]()
override func viewDidLoad() {
super.viewDidLoad()
retrieveUsers()
}
func retrieveUsers() {
let ref = FIRDatabase.database().reference()
ref.child("users").queryOrderedByKey().observeSingleEvent(of: .value, with: { snapshot in
let users = snapshot.value as! [String : AnyObject]
self.user.removeAll()
for (_, value) in users {
if let uid = value["uid"] as? String {
if uid != FIRAuth.auth()!.currentUser!.uid {
let userToShow = User()
if let fullName = value["full name"] as? String, let imagePath = value["urlToImage"] as? String {
userToShow.fullName = fullName
userToShow.imagePath = imagePath
userToShow.userID = uid
self.user.append(userToShow)
}
}
}
}
self.tableview.reloadData()
})
ref.removeAllObservers()
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableview.dequeueReusableCell(withIdentifier: "userCell", for: indexPath) as! UserCell
cell.nameLabel.text = self.user[indexPath.row].fullName
cell.userID = self.user[indexPath.row].userID
cell.userImage.downloadImage(from: self.user[indexPath.row].imagePath!)
checkFollowing(indexPath: indexPath)
return cell
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return user.count
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let uid = FIRAuth.auth()!.currentUser!.uid
let ref = FIRDatabase.database().reference()
let key = ref.child("users").childByAutoId().key
var isFollower = false
ref.child("users").child(uid).child("following").queryOrderedByKey().observeSingleEvent(of: .value, with: { snapshot in
if let following = snapshot.value as? [String : AnyObject] {
for (ke, value) in following {
if value as! String == self.user[indexPath.row].userID {
isFollower = true
ref.child("users").child(uid).child("following/\(ke)").removeValue()
ref.child("users").child(self.user[indexPath.row].userID).child("followers/\(ke)").removeValue()
self.tableview.cellForRow(at: indexPath)?.accessoryType = .none
}
}
}
if !isFollower {
let following = ["following/\(key)" : self.user[indexPath.row].userID]
let followers = ["followers/\(key)" : uid]
ref.child("users").child(uid).updateChildValues(following as Any as! [AnyHashable : Any])
ref.child("users").child(self.user[indexPath.row].userID).updateChildValues(followers)
self.tableview.cellForRow(at: indexPath)?.accessoryType = .checkmark
}
})
ref.removeAllObservers()
}
func checkFollowing(indexPath: IndexPath) {
let uid = FIRAuth.auth()!.currentUser!.uid
let ref = FIRDatabase.database().reference()
ref.child("users").child(uid).child("following").queryOrderedByKey().observeSingleEvent(of: .value, with: { snapshot in
if let following = snapshot.value as? [String : AnyObject] {
for (_, value) in following {
if value as! String == self.user[indexPath.row].userID {
self.tableview.cellForRow(at: indexPath)?.accessoryType = .checkmark
}
}
}
})
ref.removeAllObservers()
}
#IBAction func logOutPressed(_ sender: Any) {
do {
try FIRAuth.auth()?.signOut()
if FIRAuth.auth()?.currentUser == nil {
// Remove User Session from device
UserDefaults.standard.removeObject(forKey: "uid")
UserDefaults.standard.synchronize()
let vc = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "LoginVC") as! LoginViewController
}
} catch let signOutError as NSError {
// handle logout error
}
}
}
extension UIImageView {
func downloadImage(from imgURL: String!) {
let url = URLRequest(url: URL(string: imgURL)!)
let task = URLSession.shared.dataTask(with: url) {
(data, response, error) in
if error != nil {
print(error!)
return
}
DispatchQueue.main.async {
self.image = UIImage(data: data!)
}
}
task.resume()
}
}
Firebase Auth can handle this for you. Like with Firebase Database, Auth works by setting up listeners. You can listen for an existing user in your App Delegate like so:
final class AppDelegate: UIResponder, UIApplicationDelegate {
private let auth = FIRAuth.auth()
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
auth?.addStateDidChangeListener { [weak self] (_, user) in
if let user = user {
// user is already logged in
} else {
// user is not logged in
}
}
}
}
When the user is successfully logged in, put their UID in UserDefaults to store the session like so:
UserDefaults.standard.set(FIRAuth.auth()!.currentUser!.uid, forKey: "user_uid_key")
UserDefaults.standard.synchronize()
Then, whatever is the first View Controller that your apps loads, check for that key in the viewDidAppear() like this:
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
// Check if the user is logged in
if UserDefaults.standard.object(forKey: "user_uid_key") != nil {
// send them to a new view controller or do whatever you want
}
}
Put the UserDefaults in the success block of the IBAction/Function where you register/login your user like shown below:
FIRAuth.auth()?.signIn(withEmail: email, password: password, completion: { (user, error) in
if user != nil {
// Login success
// Saves User UID to UserDefaults
UserDefaults.standard.set(FIRAuth.auth()!.currentUser!.uid, forKey: "USER_KEY_UID")
UserDefaults.standard.synchronize()
}
else {
// login error
})
Remove the UserDefault when the user logs out:
#IBAction func logoutButtonPressed(sender: UIButton!) {
do {
try FIRAuth.auth()?.signOut()
if FIRAuth.auth()?.currentUser == nil {
// Remove User Session from device
UserDefaults.standard.removeObject(forKey: "USER_KEY_UID")
UserDefaults.standard.synchronize()
let loginVC = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "LoginVC") as! LoginVC
present("TheVCYouWantToSendTheUserTo", animated: true, completion: nil)
}
} catch let signOutError as NSError {
// handle logout error
}
}
UPDATED:
You forgot to include UserDefaults in your Login func. This is why it gives you an error. Add this to you login IBAction.
#IBAction func loginPressed(_ sender: Any) {
guard emailField.text != "", pwField.text != "" else {return}
FIRAuth.auth()?.signIn(withEmail: emailField.text!, password: pwField.text!, completion: { (user, error) in
if let error = error {
print(error.localizedDescription)
// You forgot to save User UID to UserDefaults here...
UserDefaults.standard.set(FIRAuth.auth()!.currentUser!.uid, forKey: "uid")
UserDefaults.standard.synchronize()
}
if user != nil {
let vc = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "TabBarViewController")
self.present(vc, animated: true, completion: nil)
}
})
}

Update Table View in iOS (Swift)

I am making a cocktail iOS application.
I am adding strings to a tableview (an ingredient to the "cabinet"). The user enters an ingredient and then presses the button ADD. It successfully adds it to the Core Data but it does not appear right away. What am I doing wrong?
Below is my code, thanks!
ViewController:
import UIKit
import CoreData
class CabinetViewController: UIViewController, UITextFieldDelegate, UITableViewDataSource, UITableViewDelegate {
var ingredientArray = [String]()
var display = [String]()
var dbIngredients = [String]()
let ingredientFetch = NSFetchRequest(entityName: "Cabinet")
var fetchedIngredient = [Cabinet]()
#IBOutlet weak var TextUI: UITextField!
#IBOutlet weak var Button: UIButton!
#IBOutlet weak var TableView: UITableView!
let moc = DataController().managedObjectContext
override func viewDidLoad() {
super.viewDidLoad()
TextUI.delegate = self
TextUI.addTarget(self, action: "textFieldDidChange:", forControlEvents: UIControlEvents.EditingChanged)
TableView.delegate = self
TableView.dataSource = self
TableView.registerClass(UITableViewCell.self,
forCellReuseIdentifier: "Cell")
// fetch Core Data
do{
fetchedIngredient = try moc.executeFetchRequest(ingredientFetch) as! [Cabinet]
} catch {
fatalError()
}
let postEndpoint: String = "http://www.thecocktaildb.com/api/json/v1/1/list.php?i=list"
guard let url = NSURL(string: postEndpoint) else {
print("Error: cannot create URL")
return
}
let urlRequest = NSURLRequest(URL: url)
let config = NSURLSessionConfiguration.defaultSessionConfiguration()
let session = NSURLSession(configuration: config)
let task = session.dataTaskWithRequest(urlRequest, completionHandler: { (data, response, error) in
guard let responseData = data else {
print("Error: did not receive data")
return
}
guard error == nil else {
print("error calling GET on www.thecocktaildb.com")
print(error)
return
}
let post: NSDictionary
do {
post = try NSJSONSerialization.JSONObjectWithData(responseData,
options: []) as! NSDictionary
} catch {
print("error trying to convert data to JSON")
return
}
var count = 1
if let drinks = post["drinks"] as? [NSDictionary] {
for drink in drinks {
if let strIngredient = drink["strIngredient1"] as? String {
print(String(count) + ". " + strIngredient)
self.dbIngredients.append(strIngredient)
count++
}
}
}
})
task.resume()
TableView.reloadData()
}
func textFieldDidChange(textField: UITextField) {
search(self.TextUI.text!)
}
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
Button.addTarget(self, action: "buttonPressed:", forControlEvents: .TouchUpInside)
return true
}
func buttonPressed(sender: UIButton!) {
//ingredientArray.append(TextUI.text!)
let entity = NSEntityDescription.insertNewObjectForEntityForName("Cabinet", inManagedObjectContext: moc) as! Cabinet
entity.setValue(TextUI.text!, forKey: "ingredient")
do{
try moc.save()
}catch {
fatalError("failure to save context: \(error)")
}
showAlertButtonTapped(Button)
// dispatch_async(dispatch_get_main_queue(), { () -> Void in
// self.TableView.reloadData()
// })
}
#IBAction func showAlertButtonTapped(sender: UIButton) {
// create the alert
let alert = UIAlertController(title: "Added!", message: "You've added " + TextUI.text! + " to your cabinet", preferredStyle: UIAlertControllerStyle.Alert)
// add an action (button)
alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.Default, handler: nil))
// show the alert
self.presentViewController(alert, animated: true, completion: nil)
}
func search(str:String) {
display.removeAll(keepCapacity: false)
for s in dbIngredients{
if s.hasPrefix(str){
display.append(s)
print(s)
}
}
}
func tableView(tableView: UITableView,
numberOfRowsInSection section: Int) -> Int {
return fetchedIngredient.capacity
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath)
do{
let fetchedIngredient = try moc.executeFetchRequest(ingredientFetch) as! [Cabinet]
cell.textLabel?.text = fetchedIngredient[indexPath.row].ingredient
} catch {
fatalError("bad things happened: \(error)")
}
return cell
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
tableView.deselectRowAtIndexPath(indexPath, animated: true)
let alert = UIAlertController(title: "Remove " + fetchedIngredient[indexPath.row].ingredient!,
message: "No more " + fetchedIngredient[indexPath.row].ingredient! + " in your cabinet?",
preferredStyle: .Alert)
let deleteAction = UIAlertAction(title: "Remove",
style: .Default,
handler: { (action:UIAlertAction) -> Void in
self.fetchedIngredient.removeAtIndex(indexPath.row)
do{
let fetchedResults = try self.moc.executeFetchRequest(self.ingredientFetch)
if let result = fetchedResults[indexPath.row] as? NSManagedObject {
self.moc.deleteObject(result)
try self.moc.save()
}
}catch{
fatalError()
}
})
let cancelAction = UIAlertAction(title: "Cancel",
style: .Default) { (action: UIAlertAction) -> Void in
}
alert.addAction(cancelAction)
alert.addAction(deleteAction)
presentViewController(alert,
animated: true,
completion: nil)
TableView.reloadData()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
// Get the new view controller using segue.destinationViewController.
// Pass the selected object to the new view controller.
}
*/
}
Since your problem isn't Core Data you need to use Table View beginUpdates and EndUpdates to insert the row. At the end of your buttonPressed function put this:
do{
fetchedIngredient = try moc.executeFetchRequest(ingredientFetch) as! [Cabinet]
self.tableView.beginUpdates()
let totalIngredients = fetchedIngredient.count
let newItemIndexPath = NSIndexPath(forRow: totalIngredients-1, inSection: 0)
self.tableView.insertRowsAtIndexPaths([newItemIndexPath], withRowAnimation: UITableViewRowAnimation.Automatic)
self.tableView.endUpdates()
} catch {
fatalError()
}
On your number of rows in section:
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return fetchedIngredient.count
}
And on the cell for row at index path
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath)
cell.textLabel?.text = fetchedIngredient[indexPath.row].ingredient
return cell
}
There are a couple of problems with your code. Firstly, since you're fetching records into an array, calling reloadData will not have any impact unless you update the array. There is no automatic connection between adding a new core data record and your fetchedIngredient array.
There are a few ways to solve this, although the most common is probably to just refetch the records into the same array whenever core data is updated. Alternatively you can change your code to us NSFetchedResultsController instead of an array, which will automatically update the tableView when core data is updated (based on the predicate you provide it). This class provides the automatic connection to core data for you.
Another problem is that you are refetching the records in cellForRowAtIndexPath and didSelectRowAtIndexPath. This should not be done. Instead you should just be referring to the class-level fetchedIngredient array (or the NSFetchedResultsController if you choose to use that).
Furthermore, the call to dataTaskWithRequest runs in the background. It's not clear from the code how you're using it, but the fact that you have reloadData afterwards suggests it should impact the tableView. However because the task runs in the background, the completion handler will run after the table is reloaded. Therefore you should be calling reloadData inside the completion handler. And since it would then be running on another thread, you would have to dispatch it to the main queue, using:
dispatch_async(dispatch_get_main_queue()) {
self.tableView.reloadData()
}

Error: 'NSInvalidArgumentException' ... unrecognized selector sent to instance

Here is my problem, I'm trying to make a clone of Snapchat, a very simple one, using Parse.com with the default project given in the page, everything was going ok, until I have to take pictures and save them to send them to an user. With this code:
import UIKit
import Parse
class UsersTableViewController: UITableViewController, UINavigationControllerDelegate, UIImagePickerControllerDelegate {
var usersArray: [String] = []
var activeRecipient: Int = 0
override func viewDidLoad() {
super.viewDidLoad()
var query = PFUser.query()
query?.whereKey("username", notEqualTo: PFUser.currentUser()!.username!)
var users = query?.findObjects()
if let user = users {
for username in user {
println(username.username!!)
usersArray.append(username.username!!)
}
}
}
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return usersArray.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("snapCell", forIndexPath: indexPath) as! UITableViewCell
cell.textLabel?.text = usersArray[indexPath.row]
return cell
}
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
activeRecipient = indexPath.row
pickImage()
}
func imagePickerController(picker: UIImagePickerController, didFinishPickingImage image: UIImage!, editingInfo: [NSObject : AnyObject]!) {
//Upload image to parse //error come somewhere from here I think
println("Image Selected")
self.dismissViewControllerAnimated(true, completion: nil)
var imageToSend = PFObject(className:"Image")
imageToSend["image"] = UIImageJPEGRepresentation(image, 0.5)
imageToSend["senderUsername"] = PFUser.currentUser()!.username
imageToSend["recipientUsername"] = usersArray[activeRecipient]
imageToSend.save()
}
func pickImage() {
var image = UIImagePickerController()
image.delegate = self
image.sourceType = UIImagePickerControllerSourceType.PhotoLibrary
image.allowsEditing = false
self.presentViewController(image, animated: true, completion: nil)
}
}
The error:
2015-06-24 15:03:13.414 SnapClomSwift[2043:124661] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[NSConcreteMutableData PF_base64EncodedString]: unrecognized selector sent to instance 0x7a648960'
Is not very helpful to debug, any help??
EDIT1: I would think the parse function is the last calling and breaking everything but I'm not sure.
EDIT2: I have I guess fixed but I'm not sure what was in the first place, still. The new code is this:
var imageToSend = PFObject(className:"Image")
//var imageData = UIImagePNGRepresentation(image)
imageToSend["senderUsername"] = PFUser.currentUser()!.username!
imageToSend["recipientUsername"] = usersArray[activeUser]
imageToSend.saveInBackgroundWithBlock({
(success: Bool, error: NSError?) -> Void in
if (success == false) {
// Error.
println("Error horray! \(error?.description)")
} else {
// There was a problem, check error.description
let imageData = UIImagePNGRepresentation(image)
let imageFile = PFFile(name: "image-png", data: imageData)
imageToSend["image"] = imageFile
imageToSend.saveInBackgroundWithBlock({
(success: Bool, error: NSError?) -> Void in
if success == false {
println("something is fucked up \(error?.description)")
} else {
println("Cool")
}
})
println("Done")
}
})
I guess this is not a solution but a workaround so I'm going to accept Zaph's answer.
To see the actual statement that is causing the error add an exception breakpoint:
From the Mian Menu Debug:Breakpoints:Create Exception Breakpoint.
Right-click the breakpoint and set the exception to Objective-C.
Add an action: "po $arg1".
Run the app to get the breakpoint and you will be at the line that causes the exception and the error message will be in the debugger console.

Tutorial for Google autocomplete places api for swift

I would like to have an autocomplete textfield that autocompletes locations for me like the one for android:
https://developers.google.com/places/training/autocomplete-android
Does anyone know where I can find a tutorial for this or an example?
Thanks!
Steps :
Add the Alamofire CocoaPods in your swift project.
Find your Google place API key on Google APIs Console.
Add following code
ViewController.swift
import UIKit
class ViewController: UIViewController {
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
let gpaViewController = GooglePlacesAutocomplete(
apiKey: "YOUR GOOGLE PLACE API KEY",
placeType: .Address
)
gpaViewController.placeDelegate = self
presentViewController(gpaViewController, animated: true, completion: nil)
}
}
extension ViewController: GooglePlacesAutocompleteDelegate {
func placeSelected(place: Place) {
println(place.description)
}
func placeViewClosed() {
dismissViewControllerAnimated(true, completion: nil)
}
}
GooglePlacesAutocomplete.swift
import UIKit
import Alamofire
enum PlaceType: Printable {
case All
case Geocode
case Address
case Establishment
case Regions
case Cities
var description : String {
switch self {
case .All: return ""
case .Geocode: return "geocode"
case .Address: return "address"
case .Establishment: return "establishment"
case .Regions: return "regions"
case .Cities: return "cities"
}
}
}
struct Place {
let id: String
let description: String
}
protocol GooglePlacesAutocompleteDelegate {
func placeSelected(place: Place)
func placeViewClosed()
}
// MARK: - GooglePlacesAutocomplete
class GooglePlacesAutocomplete: UINavigationController {
var gpaViewController: GooglePlacesAutocompleteContainer?
var placeDelegate: GooglePlacesAutocompleteDelegate? {
get { return gpaViewController?.delegate }
set { gpaViewController?.delegate = newValue }
}
convenience init(apiKey: String, placeType: PlaceType = .All) {
let gpaViewController = GooglePlacesAutocompleteContainer(
apiKey: apiKey,
placeType: placeType
)
self.init(rootViewController: gpaViewController)
self.gpaViewController = gpaViewController
let closeButton = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.Stop, target: self, action: "close")
gpaViewController.navigationItem.leftBarButtonItem = closeButton
gpaViewController.navigationItem.title = "Enter Address"
}
func close() {
placeDelegate?.placeViewClosed()
}
}
// MARK: - GooglePlaceSearchDisplayController
class GooglePlaceSearchDisplayController: UISearchDisplayController {
override func setActive(visible: Bool, animated: Bool) {
if active == visible { return }
searchContentsController.navigationController?.navigationBarHidden = true
super.setActive(visible, animated: animated)
searchContentsController.navigationController?.navigationBarHidden = false
if visible {
searchBar.becomeFirstResponder()
} else {
searchBar.resignFirstResponder()
}
}
}
// MARK: - GooglePlacesAutocompleteContainer
class GooglePlacesAutocompleteContainer: UIViewController {
var delegate: GooglePlacesAutocompleteDelegate?
var apiKey: String?
var places = [Place]()
var placeType: PlaceType = .All
convenience init(apiKey: String, placeType: PlaceType = .All) {
self.init(nibName: "GooglePlacesAutocomplete", bundle: nil)
self.apiKey = apiKey
self.placeType = placeType
}
override func viewDidLoad() {
super.viewDidLoad()
let tv: UITableView? = searchDisplayController?.searchResultsTableView
tv?.registerClass(UITableViewCell.self, forCellReuseIdentifier: "Cell")
}
}
// MARK: - GooglePlacesAutocompleteContainer (UITableViewDataSource / UITableViewDelegate)
extension GooglePlacesAutocompleteContainer: UITableViewDataSource, UITableViewDelegate {
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return places.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = self.searchDisplayController?.searchResultsTableView?.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as UITableViewCell
// Get the corresponding candy from our candies array
let place = self.places[indexPath.row]
// Configure the cell
cell.textLabel.text = place.description
cell.accessoryType = UITableViewCellAccessoryType.DisclosureIndicator
return cell
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
delegate?.placeSelected(self.places[indexPath.row])
}
}
// MARK: - GooglePlacesAutocompleteContainer (UISearchDisplayDelegate)
extension GooglePlacesAutocompleteContainer: UISearchDisplayDelegate {
func searchDisplayController(controller: UISearchDisplayController, shouldReloadTableForSearchString searchString: String!) -> Bool {
getPlaces(searchString)
return false
}
private func getPlaces(searchString: String) {
Alamofire.request(.GET,
"https://maps.googleapis.com/maps/api/place/autocomplete/json",
parameters: [
"input": searchString,
"type": "(\(placeType.description))",
"key": apiKey ?? ""
]).responseJSON { request, response, json, error in
if let response = json as? NSDictionary {
if let predictions = response["predictions"] as? Array<AnyObject> {
self.places = predictions.map { (prediction: AnyObject) -> Place in
return Place(
id: prediction["id"] as String,
description: prediction["description"] as String
)
}
}
}
self.searchDisplayController?.searchResultsTableView?.reloadData()
}
}
}
GooglePlacesAutocomplete.xib
Hope this will help others.
Here's full updated code for Google Autocomplete place API.
Xcode 10.0 & Swift 4.2
Follow this link as to Get Google API KEY.
After Getting the API KEY
Install Cocoa Pods:
source 'https://github.com/CocoaPods/Specs.git'
target 'YOUR_APPLICATION_TARGET_NAME_HERE' do
pod 'GooglePlaces'
pod 'GooglePlacePicker'
pod 'GoogleMaps'
end
Appdelegate File:
import UIKit
import GooglePlaces
let GOOGLE_API_KEY = "AIzaSyCuZkL7bh_hIDggnJob-b0cDueWlvRgpck"
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
GMSPlacesClient.provideAPIKey(GOOGLE_API_KEY)
return true
}
func applicationWillResignActive(_ application: UIApplication) {
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
// Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game.
}
func applicationDidEnterBackground(_ application: UIApplication) {
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
}
func applicationWillEnterForeground(_ application: UIApplication) {
// Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
}
func applicationDidBecomeActive(_ application: UIApplication) {
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
}
func applicationWillTerminate(_ application: UIApplication) {
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
}
}
ViewController File:
import UIKit
import GooglePlaces
class ViewController: UIViewController ,CLLocationManagerDelegate{
var placesClient: GMSPlacesClient!
// Add a pair of UILabels in Interface Builder, and connect the outlets to these variables.
#IBOutlet var nameLabel: UILabel!
#IBOutlet var addressLabel: UILabel!
let locationManager = CLLocationManager()
var resultsViewController: GMSAutocompleteResultsViewController?
var searchController: UISearchController?
var resultView: UITextView?
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
locationManager.delegate = self
if CLLocationManager.authorizationStatus() == .notDetermined
{
locationManager.requestAlwaysAuthorization()
}
placesClient = GMSPlacesClient.shared()
// self.addToNavbar()
// self.addToSubview()
self.addToPopover()
}
func addToNavbar(){
resultsViewController = GMSAutocompleteResultsViewController()
resultsViewController?.delegate = self
searchController = UISearchController(searchResultsController: resultsViewController)
searchController?.searchResultsUpdater = resultsViewController
// Put the search bar in the navigation bar.
searchController?.searchBar.sizeToFit()
navigationItem.titleView = searchController?.searchBar
// When UISearchController presents the results view, present it in
// this view controller, not one further up the chain.
definesPresentationContext = true
// Prevent the navigation bar from being hidden when searching.
searchController?.hidesNavigationBarDuringPresentation = false
}
func addToSubview(){
resultsViewController = GMSAutocompleteResultsViewController()
resultsViewController?.delegate = self
searchController = UISearchController(searchResultsController: resultsViewController)
searchController?.searchResultsUpdater = resultsViewController
let subView = UIView(frame: CGRect(x: 0, y: 65.0, width: 350.0, height: 45.0))
subView.addSubview((searchController?.searchBar)!)
view.addSubview(subView)
searchController?.searchBar.sizeToFit()
searchController?.hidesNavigationBarDuringPresentation = false
// When UISearchController presents the results view, present it in
// this view controller, not one further up the chain.
definesPresentationContext = true
}
func addToPopover(){
resultsViewController = GMSAutocompleteResultsViewController()
resultsViewController?.delegate = self
searchController = UISearchController(searchResultsController: resultsViewController)
searchController?.searchResultsUpdater = resultsViewController
// Add the search bar to the right of the nav bar,
// use a popover to display the results.
// Set an explicit size as we don't want to use the entire nav bar.
searchController?.searchBar.frame = (CGRect(x: 0, y: 0, width: 250.0, height: 44.0))
navigationItem.rightBarButtonItem = UIBarButtonItem(customView: (searchController?.searchBar)!)
// When UISearchController presents the results view, present it in
// this view controller, not one further up the chain.
definesPresentationContext = true
// Keep the navigation bar visible.
searchController?.hidesNavigationBarDuringPresentation = false
searchController?.modalPresentationStyle = .popover
}
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus)
{
print(status)
}
// Add a UIButton in Interface Builder, and connect the action to this function.
#IBAction func getCurrentPlace(_ sender: UIButton) {
placesClient.currentPlace(callback: { (placeLikelihoodList, error) -> Void in
if let error = error {
print("Pick Place error: \(error.localizedDescription)")
return
}
self.nameLabel.text = "No current place"
self.addressLabel.text = ""
if let placeLikelihoodList = placeLikelihoodList {
print("placeLikelihoodList -- \(placeLikelihoodList)")
let place = placeLikelihoodList.likelihoods.first?.place
if let place = place {
self.nameLabel.text = place.name
self.addressLabel.text = place.formattedAddress?.components(separatedBy: ", ")
.joined(separator: "\n")
print(place.name)
print(place.coordinate)
print(place.placeID)
print(place.phoneNumber)
print(place.formattedAddress ?? "")
}
}
})
}
}
//MARK: Extentions
// Handle the user's selection.
extension ViewController: GMSAutocompleteResultsViewControllerDelegate {
func resultsController(_ resultsController: GMSAutocompleteResultsViewController,
didAutocompleteWith place: GMSPlace) {
searchController?.isActive = false
// Do something with the selected place.
print("Place name: \(place.name)")
print("Place address: \(String(describing: place.formattedAddress))")
print("Place attributions: \(place.attributions)")
}
func resultsController(_ resultsController: GMSAutocompleteResultsViewController,
didFailAutocompleteWithError error: Error){
// TODO: handle the error.
print("Error: ", error.localizedDescription)
}
// Turn the network activity indicator on and off again.
func didRequestAutocompletePredictions(_ viewController: GMSAutocompleteViewController) {
UIApplication.shared.isNetworkActivityIndicatorVisible = true
}
func didUpdateAutocompletePredictions(_ viewController: GMSAutocompleteViewController) {
UIApplication.shared.isNetworkActivityIndicatorVisible = false
}
}
Lightweight Solution!
Instead of using Google framework and Third party library to make simple requests I created a simple library where you can Make a number of Google api requests like Google Autocomplete, Google ReverseGeo , Place Information and Path api for getting path between two location.
To use the library all you have to do is
step-1 Import GoogleApiHelper into your project.
step-2 Initialise GoogleApiHelper
GoogleApi.shared.initialiseWithKey("API_KEY")
step-3 Call the methods
var input = GInput()
input.keyword = "San francisco"
GoogleApi.shared.callApi(input: input) { (response) in
if let results = response.data as? [GApiResponse.Autocomplete], response.isValidFor(.autocomplete) {
//Enjoy the Autocomplete Api
} else { print(response.error ?? "ERROR") }
}
You can find the library here
Using Alamofire get the autocomplete Google places result from data, you can show it in table view cell
plist configuration
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
Code
import UIKit
import Alamofire
class GooglePlacesViewController: UIViewController,UISearchBarDelegate,UITableViewDataSource,UITableViewDelegate {
#IBOutlet weak var srchLocation: UISearchBar!
#IBOutlet weak var tblLoction: UITableView!
var arrPlaces = NSMutableArray(capacity: 100)
let operationQueue = OperationQueue()
let currentLat = 51.5033640
let currentLong = -0.1276250
var LocationDataDelegate : LocationData! = nil
var tblLocation : UITableView!
var lblNodata = UILabel()
override func viewDidLoad()
{
super.viewDidLoad()
lblNodata.frame = CGRect(x: 0, y: 80, width:
self.view.frame.size.width, height: self.view.frame.size.height-60)
lblNodata.text = "Please enter text to get your location"
self.view.addSubview(lblNodata)
srchLocation.placeholder = "Ente your location details"
lblNodata.textAlignment = .center
srchLocation.delegate = self
}
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
self.beginSearching(searchText: searchText)
}
func beginSearching(searchText:String) {
if searchText.characters.count == 0 {
self.arrPlaces.removeAllObjects()
tblLoction.isHidden = true
lblNodata.isHidden = false
return
}
operationQueue.addOperation { () -> Void in
self.forwardGeoCoding(searchText: searchText)
}
}
//MARK: - Search place from Google -
func forwardGeoCoding(searchText:String) {
googlePlacesResult(input: searchText) { (result) -> Void in
let searchResult:NSDictionary = ["keyword":searchText,"results":result]
if result.count > 0
{
let features = searchResult.value(forKey: "results") as! NSArray
self.arrPlaces = NSMutableArray(capacity: 100)
print(features.count)
for jk in 0...features.count-1
{
let dict = features.object(at: jk) as! NSDictionary
self.arrPlaces.add(dict)
}
DispatchQueue.main.async(execute: {
if self.arrPlaces.count != 0
{
self.tblLoction.isHidden = false
self.lblNodata.isHidden = true
self.tblLoction.reloadData()
}
else
{
self.tblLoction.isHidden = true
self.lblNodata.isHidden = false
self.tblLoction.reloadData()
}
});
}
}
}
//MARK: - Google place API request -
func googlePlacesResult(input: String, completion: #escaping (_ result: NSArray) -> Void) {
let searchWordProtection = input.replacingOccurrences(of: " ", with: ""); if searchWordProtection.characters.count != 0 {
let urlString = NSString(format: "https://maps.googleapis.com/maps/api/place/autocomplete/json?input=%#&types=establishment|geocode&location=%#,%#&radius=500&language=en&key= your key",input,"\(currentLocationLatitude)","\(currentLocationLongtitude)")
print(urlString)
let url = NSURL(string: urlString.addingPercentEscapes(using: String.Encoding.utf8.rawValue)!)
print(url!)
let defaultConfigObject = URLSessionConfiguration.default
let delegateFreeSession = URLSession(configuration: defaultConfigObject, delegate: nil, delegateQueue: OperationQueue.main)
let request = NSURLRequest(url: url! as URL)
let task = delegateFreeSession.dataTask(with: request as URLRequest, completionHandler:
{
(data, response, error) -> Void in
if let data = data
{
do {
let jSONresult = try JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions.allowFragments) as! [String:AnyObject]
let results:NSArray = jSONresult["predictions"] as! NSArray
let status = jSONresult["status"] as! String
if status == "NOT_FOUND" || status == "REQUEST_DENIED"
{
let userInfo:NSDictionary = ["error": jSONresult["status"]!]
let newError = NSError(domain: "API Error", code: 666, userInfo: userInfo as [NSObject : AnyObject])
let arr:NSArray = [newError]
completion(arr)
return
}
else
{
completion(results)
}
}
catch
{
print("json error: \(error)")
}
}
else if let error = error
{
print(error)
}
})
task.resume()
}
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return arrPlaces.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
let tblCell = tableView.dequeueReusableCell(withIdentifier: "locationCell")
let dict = arrPlaces.object(at: indexPath.row) as! NSDictionary
tblCell?.textLabel?.text = dict.value(forKey: "description") as? String
tblCell?.textLabel?.numberOfLines = 0
tblCell?.textLabel?.sizeToFit()
return tblCell!
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
{
if LocationDataDelegate != nil
{
let dict = arrPlaces.object(at: indexPath.row) as! NSDictionary
print(dict.value(forKey: "terms") as! NSArray)
let ArrSelected = dict.value(forKey: "terms") as! NSArray
LocationDataDelegate.didSelectLocationData(LocationData: ArrSelected)
}
self.dismiss(animated: true, completion: nil)
}
}
class AddNewAddressVC: UIViewController,UITextFieldDelegate{
func autocompleteClicked() {
let autocompleteController = GMSAutocompleteViewController()
autocompleteController.delegate = self
// Specify the place data types to return.
let fields: GMSPlaceField = GMSPlaceField(rawValue: UInt(GMSPlaceField.name.rawValue) |
UInt(GMSPlaceField.placeID.rawValue))!
autocompleteController.placeFields = fields
// Specify a filter.
let filter = GMSAutocompleteFilter()
filter.type = .address
autocompleteController.autocompleteFilter = filter
// Display the autocomplete view controller.
present(autocompleteController, animated: true, completion: nil)
}
#IBAction func action_selectGooglePlaces(_ sender: UIButton) {
autocompleteClicked()
}
}
extension AddNewAddressVC: GMSAutocompleteViewControllerDelegate {
// Handle the user's selection.
func viewController(_ viewController: GMSAutocompleteViewController, didAutocompleteWith place: GMSPlace) {
print("Place name: \(place.name)")
print("Place ID: \(place.placeID)")
print("Place attributions: \(place.attributions)")
dismiss(animated: true, completion: nil)
}
func viewController(_ viewController: GMSAutocompleteViewController, didFailAutocompleteWithError error: Error) {
// TODO: handle the error.
print("Error: ", error.localizedDescription)
}
// User canceled the operation.
func wasCancelled(_ viewController: GMSAutocompleteViewController) {
dismiss(animated: true, completion: nil)
}
// Turn the network activity indicator on and off again.
func didRequestAutocompletePredictions(_ viewController: GMSAutocompleteViewController) {
UIApplication.shared.isNetworkActivityIndicatorVisible = true
}
func didUpdateAutocompletePredictions(_ viewController: GMSAutocompleteViewController) {
UIApplication.shared.isNetworkActivityIndicatorVisible = false
}
}

Resources