Swift PrepareForSegue NSIndexPath error - ios

I want to prepare my segue via:
override func prepareForSegue(segue: UIStoryboardSegue?, sender: AnyObject?) {
if segue?.identifier != "fromOpenChatsToLogIn" {
if let controller: ChatViewController? = segue?.destinationViewController as? ChatViewController {
if let cell: onlineUserCell? = sender as? onlineUserCell {
let user = OneRoster.userFromRosterAtIndexPath(indexPath: tableView.indexPathForCell(cell!)!)
controller!.recipient = user
}
}
}
}
where onlineUserCell is my custom cell. Also, that's my userFromRosterAtIndexPath:
class func userFromRosterAtIndexPath(indexPath indexPath: NSIndexPath) -> XMPPUserCoreDataStorageObject {
return sharedInstance.fetchedResultsController()!.objectAtIndexPath(indexPath) as! XMPPUserCoreDataStorageObject
}
so, when I select my cell it crashes with:
fatal error: unexpectedly found nil while unwrapping an Optional value
on line:
let user = OneRoster.userFromRosterAtIndexPath(indexPath: tableView.indexPathForCell(cell!)!)
What is wrong? How can I fix it?

First of all, Swift introduced type inference, and the best practices is to use it properly, so write your code like this
if let controller = segue?.destinationViewController as? ChatViewController {
if let cell = sender as? onlineUserCell {
}
}
Also you should set an exception breakpoint in Xcode (here), to see more clearly where you're crashing.
In your case it was in
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?)
You should use the following statement :
if segue.identifier == "fromOpenChatsToLogIn" {
} else {
}
Your Login ViewController doesn't require an user, therefore it crashed at
let user = OneRoster.userFromRosterAtIndexPath(indexPath: tableView.indexPathForCell(cell)!)
Because there is no cell, and the sender in prepareForSegue is definitely NOT a cell.
For you second issue, the fix was easy.
You are sending OneRoster.userFromRosterAtIndexPath(indexPath: indexPath) in
performSegueWithIdentifier("fromUsersListToChatView", sender: OneRoster.userFromRosterAtIndexPath(indexPath: indexPath))
So you don't need a cell or anything since you already have the user object !
Set the recipient like this :
controller?.recipient = sender as? XMPPUserCoreDataStorageObject
Just replace your prepareForSegue method to the following and it will work:
if segue.identifier == "fromOpenChatsToLogIn" {
} else {
let controller = segue.destinationViewController as? ChatViewController
controller?.recipient = sender as? XMPPUserCoreDataStorageObject
}

Just check, wether your cell is nil or your userFromRosterAtIndexPath return nil

Check casting like this.
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "PhotoScrollerViewController" {
if let controller = segue.destinationViewController as? PhotoScrollerViewController {
var cell:UICollectionViewCell = (sender as? UICollectionViewCell)!
//Code here
}
}
}

My advise would to set tag on each cell and then in your prepareForSegue method, get the cell, get the tag and then tweak the code for userFromRosterAtIndexPath to take that tag instead. That tag value could be row number of the cell. See if this makes sense in your context.

Related

PersonalityQuiz guided app in swift fundamentals

I've got problem with some additional challenges. I need to filter an array of type Question by some property and then pass it into next View Controller via segue. I've done this:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
guard let sender = sender as? UIButton else {return}
if sender == quiz3Button {
let vc = segue.destination as? QuestionViewController
vc?.correctQuestions = questions.filter { question in
return question.quiz == .animals
}
} else if sender == quiz4Button {
let vc = segue.destination as? QuestionViewController
vc?.correctQuestions = questions.filter { question in
return question.quiz == .cars
}
}
}
#IBAction func quiz3ButtonTapped(_ sender: UIButton) {
performSegue(withIdentifier: "animals", sender: sender)
}
#IBAction func quiz4Button(_ sender: UIButton) {
performSegue(withIdentifier: "cars", sender: sender)
}
Filtration works but it doesn't pass value to next View Controller. I declared variable in QuestionViewControler like that
var correctQuestions: [Question] = []
But when I need to access it I get error "Index out of range". So I figured that its empty..
Segues been made from buttons to VC
Ok. I've got it. The NavigationController was the problem here. Added into function push through NC and it worked ;) so closed I think
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
guard let sender = sender as? UIButton else {return}
if sender == quiz3Button {
let destinationViewController = segue.destination as? UINavigationController
let questionViewController = destinationViewController?.viewControllers.first as! QuestionViewController
questionViewController.correctQuestions = questions.filter { questions in
return questions.quiz == .animals
}
} else if sender == quiz4Button {
let destinationViewController = segue.destination as? UINavigationController
let questionViewController = destinationViewController?.viewControllers.first as! QuestionViewController
questionViewController.correctQuestions = questions.filter { questions in
return questions.quiz == .cars
}
}
}

How to perform a segue if child does not exist in Firebase

I would like to check if a user has been banned from a group before to let him have access to the group chat and then perform a segue toward the next view controller.
For that I use exist(). When the child exists, the code works perfectly but when the child does not exist, nothing happens.
I tried to change blockedUserDB.observe(.childAdded, with: { (snapshot) in for blockedUserDB.observeSingleEvent(of: .value without success. I also tried to perform the segue in putting performSegue(withIdentifier: "toConversationControler", sender: Any?.self) in another function triggered when the node does not exist ...
Here is my code
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
self.selectedGroupID = "\(groupArray[indexPath.row].documentID)"
// Check if user blocked
let uid = Auth.auth().currentUser!.uid as String
// 1 - set breakpoint here
let blockedUserDB = self.ref2.child("blocked").child(self.selectedGroupID).child(uid)
blockedUserDB.observe(.childAdded, with: { (snapshot) in
// 2 - does this print?
print("snapshot.exists() ==", snapshot.exists())
if snapshot.exists(){
// 3 - does this print?
print("true rooms exist")
let snapshotValue = snapshot.value as! String
print("\(snapshotValue) the value")
self.blockUserCheck = snapshotValue
print(self.blockUserCheck)
if self.blockUserCheck == "true" {
SPAlert.present(message: "You have been banned from this group")
}
else
{
print("performSegue!")
self.performSegue(withIdentifier: "toConversationControler", sender: Any?.self)
print("selected group \(self.selectedGroupID)")
}
}
else
{
// 4 - does this print?
print("snapshot.exists() was false!")
self.performSegue(withIdentifier: "toConversationControler", sender: Any?.self)
}
})
}
And here is how I prepared for segue:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
guard let identifier = segue.identifier else {
assertionFailure("Segue had no idientifier")
return
}
if identifier == "toConversationControler" {
let vc = segue.destination as! ConversationViewController
vc.finalGroup = self.selectedGroupID
vc.groupName = self.selectedGroupName
vc.city = self.finalCity
vc.language = self.selectedLanguage
vc.info = self.selectedInfo
}
else if identifier == "toNewConvVC" {
let newConvVC = segue.destination as! NewGroupViewController
newConvVC.city = self.finalCity
}
else {
assertionFailure("Did not recognize storyboard identifier")
}
}
Thank you for your help.

collection view cells fetch data

I am new to collection view. I want to retrieve data from CoreData for collection view cell. I know how to retrieve for table view cell but it failed when I use similar way to fetch for collection view. Here are my functions from CoreDataHelper and ViewController class
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let identifier = segue.identifier {
if identifier == "displayCellDetail" {
print("Task View cell tapped")
CollectionViewCoreDataHelper.retrieveTasks()
let indexPath = collectionView.indexPathsForSelectedItems!
let task = tasks[indexPath.row]
let TaskSettingViewController = segue.destination as! ViewController
TaskSettingViewController.task = task
} else if identifier == "addTask" {
print("+ button tapped")
}
}
}
static func retrieveTasks() -> [Tasks] {
let fetchRequest = NSFetchRequest<Tasks>(entityName: "Tasks")
do {
let results = try managedContext.fetch(fetchRequest)
return results
} catch let error as NSError {
print("Could not fetch \(error)")
}
return []
}
Try this :
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let identifier = segue.identifier {
if identifier == "displayCellDetail" {
print("Task View cell tapped")
CollectionViewCoreDataHelper.retrieveTasks()
let cell = sender as? YourCellName //Cell from which this segue is being performed
let indexPath = self.collectionView!.indexPathForCell(cell)
let task = self.tasks[indexPath.item] //Downcast to type of task
let objTaskSettingVC = segue.destination as! TaskSettingViewController
objTaskSettingVC.tasks = task
}
else if identifier == "addTask" {
print("+ button tapped")
}
}
}
For passing the data from one viewController to other is here
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let identifier = segue.identifier {
if identifier == "displayCellDetail" {
print("Task View cell tapped")
CollectionViewCoreDataHelper.retrieveTasks()
let cell = sender as UICollectionViewCell
let indexPath = self.collectionView!.indexPathForCell(cell)
let task = self.tasks[indexPath.row] as [ToDo]
let objTaskSettingVC = segue.destination as! TaskSettingViewController // ViewController in which you want to send the data
objTaskSettingVC.tasks = [task] //tasks is your variable which is having same type and defined in your TaskSettingViewController
}
else if identifier == "addTask" {
print("+ button tapped")
}
}
}

Swift sending Multiple Objects to View Controller

I am trying to send multiple objects from my initial view controller to my Username VC. Here is the segue code from my controllers: The issue comes when I add in the code to send the second object, termreport. If I delete the termsM and the assignment, it send the students as usually, but I also need to send the termReport object. How would I fix this?
ViewControler:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
guard let students = sender as AnyObject as? [Student]
else { return }
guard let termsM = sender as AnyObject as? [TermReport] //How would I send both objects?
else { return }
if let secondVC = segue.destination as? UsernameVC {
secondVC.students = students
secondVC.userWebView = webView
secondVC.terms = termsM // not sending
}
let gradeResponse = try Parser(innerHTML)
self.performSegue(withIdentifier: "ShowStudents", sender: gradeResponse.students)
self.performSegue(withIdentifier: "ShowStudents", sender: gradeResponse.termReports) //how would I send both variables?
UsernameVC:
var terms: [TermReport]!
override func viewDidLoad() {
print("TERM \(terms[0].grades[3])")//returns found nil optional ERROR
}
You have to include all of the variables you want to send to another ViewController using a segue into a single object (which can be a collection as well). You either create a custom class/struct that has properties with type [Student] and [TermReport] or put these into a native collection (Tuple or Dictionary).
Create custom struct:
struct TermData {
var students = [Student]()
var termReports = [TermReport]()
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
guard let segueData = sender as? TermData
else { return }
if let secondVC = segue.destination as? UsernameVC {
secondVC.students = segueData.students
secondVC.userWebView = webView
secondVC.terms = segueData.termReports
}
}
let gradeResponse = try Parser(innerHTML)
let termData = TermData(students: gradeResponse.students, termReports: gradeResponse.termReports)
self.performSegue(withIdentifier: "ShowStudents", sender: termData)

Displaying Contact info on another ViewController with a segue

Im using contact framework to fetch user contact's name, number, email, and contact image, the data is being fetched. All I am able to display right now is only one of the keys in the table view, like either the name number or email, I wish to have the name be clicked and then segue to the other view controller where the rest of the properties show up. but I am having trouble displaying it on the other viewController. I have tried using the get contacts method in the prepareforsegue function, did not seem to work The code I am using to fetch contacts is
func getContacts() {
let store = CNContactStore()
if CNContactStore.authorizationStatusForEntityType(.Contacts) == .NotDetermined {
store.requestAccessForEntityType(.Contacts, completionHandler: { (authorized: Bool, error: NSError?) -> Void in
if authorized {
self.retrieveContactsWithStore(store)
}
})
} else if CNContactStore.authorizationStatusForEntityType(.Contacts) == .Authorized {
self.retrieveContactsWithStore(store)
}
tableView.reloadData()
}
func retrieveContactsWithStore(store: CNContactStore) {
do {
let groups = try store.groupsMatchingPredicate(nil)
// let predicate = CNContact.predicateForContactsInGroupWithIdentifier(groups[0].identifier)
let containerId = CNContactStore().defaultContainerIdentifier()
let predicate: NSPredicate = CNContact.predicateForContactsInContainerWithIdentifier(containerId)
let keysToFetch = [CNContactFormatter.descriptorForRequiredKeysForStyle(.FullName), CNContactEmailAddressesKey, CNContactPhoneNumbersKey,CNContactImageDataAvailableKey,CNContactThumbnailImageDataKey]
print(CNContactEmailAddressesKey.capitalizedString)
let contacts = try store.unifiedContactsMatchingPredicate(predicate, keysToFetch: keysToFetch)
self.objects = contacts
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.tableView.reloadData()
})
} catch {
print(error)
}
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if let identifier = segue.identifier {
if identifier == "DetailContact" {
}
}
}
As i said in my comments, create a new contact object in your other controller. For example: yourContactObject and then set some value to it in the prepare for segue as follows:
var selectedContact;// Your contact which you will set in the did select of the other controller
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if let identifier = segue.identifier {
if identifier == "DetailContact" {
let controller = segue.destinationViewController
controller.yourContactObject = selectedContact.
}
}
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
selectedContact = objects[indexPath.row]
}
Instead of trying to re-do the query in prepareForSegue, make a property on your other view controller, say var contact: CNContactStore! and then observe which row gets selected in the table view. So in didSelectRowAtIndexPath you can do the following...
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let contactSelected = yourDataModel[indexPath.row] //now you know which contact was selected
self.performSegueWithIdentifier("DetailContact", sender: contactSelected)
/**you can pass the selected contact as the sender argument,
or set it as a class-level variable,
or use a delegate or notification, all would work */
}
And then in your prepareForSegue method, could do the following...
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if let identifier = segue.identifier {
if identifier == "DetailContact" {
let controller = segue.destinationViewController
controller.contact = sender as! CNContactStore
}
}
}

Resources