I am trying to dismiss a tableview controller with a navigation controller embeded after performing a segue.
Here's my code:
class SelectAlbum: UITableViewController {
..............
override func viewDidLoad() {
super.viewDidLoad()
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
guard let destination = (segue.destination as? CustomNavigationBar)?.topViewController as? AddPhotoPostVC
else { fatalError("unexpected view controller for segue") }
guard let cell = sender as? AlbumListCells else { fatalError("unexpected cell for segue") }
switch SegueIdentifier(rawValue: segue.identifier!)! {
case .showAllPhotos:
destination.fetchResult = allPhotos
destination.headerTitleBtnString = cell.allPhotoTitle.text!
case .showCollection:
// get the asset collection for the selected row
let indexPath = tableView.indexPath(for: cell)!
let collection: PHCollection
switch Section(rawValue: indexPath.section)! {
case .smartAlbums:
collection = smartAlbums.object(at: indexPath.row)
case .userCollections:
collection = userCollections.object(at: indexPath.row)
default: return // not reached; all photos section already handled by other segue
}
// configure the view controller with the asset collection
guard let assetCollection = collection as? PHAssetCollection
else { fatalError("expected asset collection") }
destination.fetchResult = PHAsset.fetchAssets(in: assetCollection, options: nil)
destination.assetCollection = assetCollection
destination.headerTitleBtnString = cell.collectionTitle.text!
}
}
........
}
The issue is that if I dismiss the controller in the prepare for segue function than the segue is not performed and I get and error in my console log: "Attempt to present....whose view is not in the window hierarchy!".
How can I dismiss the controller and pass the data?
Thank you.
Related
I have an application where the users can update and add tasks. But when the users update the task I need to update the details in the DetailViewController
When the users updates the task and saves the data in MasterViewController saves but the data in DetailViewController stays the same
How can I fix it so that the details get updated as well
The tasks are added and edited using the AddProjectController
I tried the following methods but none if them worked, all caused the the application to crash
let vc = DetailViewController()
let row = vc.view.layoutIfNeeded();
//Second method
let vc = MasterViewController()
vc.performSegue(withIdentifier: "showDetail", sender: vc.self)
The data is set in the DetailViewController using
override func viewDidLoad() {
super.viewDidLoad()
table.delegate = self
table.dataSource = self
lblName.text = detail.name ?? ""
lblCode.text = detail.code ?? ""
}
MasterViewController
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "showDetail" {
if let indexPath = tableView.indexPathForSelectedRow {
let object = fetchedResultsController.object(at: indexPath)
let controller = (segue.destination as! UINavigationController).topViewController as! DetailViewController
controller.details = object;
controller.navigationItem.leftBarButtonItem = splitViewController?.displayModeButtonItem
controller.navigationItem.leftItemsSupplementBackButton = true
detailViewController = controller
}
}
}
EditViewController
func editEvents(_ sender: Any){
let fetchRequest: NSFetchRequest<NSFetchRequestResult> = NSFetchRequest(entityName: "Proj")
do {
let test = try context.fetch(fetchRequest)
var objectUpdate = test[0] as! Assesment;
objectUpdate.name = txtname.text ?? ""
objectUpdate.code = txtCode.text ?? "";
do {
try context.save()
}
catch {
print(error)
}
}
catch {
print(error)
}
}
I want to update the details when the editEvent function is finished executing
Is there anyway I can get this to work?
If You are using coreData .. You can implement FRC in details .. so you get refreshed data every time in callback ...
In your MasterViewController define a variable
Here is another workaround
private var controller : DetailViewController? = nil
Then in your Segue method use this controller
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "showDetail" {
if let indexPath = tableView.indexPathForSelectedRow {
let object = fetchedResultsController.object(at: indexPath)
self.controller = (segue.destination as! UINavigationController).topViewController as! DetailViewController // here you set your instence variable controller
controller.details = object;
controller.navigationItem.leftBarButtonItem = splitViewController?.displayModeButtonItem
controller.navigationItem.leftItemsSupplementBackButton = true
detailViewController = controller
}
}
}
func editEvents(_ sender: Any){
let fetchRequest: NSFetchRequest<NSFetchRequestResult> = NSFetchRequest(entityName: "Proj")
do {
let test = try context.fetch(fetchRequest)
var objectUpdate = test[0] as! Assesment;
objectUpdate.name = txtname.text ?? ""
objectUpdate.code = txtCode.text ?? "";
do {
try context.save()
if let detailsCotrollerExists = controller {
detailsCotrollerExists.updateDataWithObject(objectUpdate) // function you will write in detail class which takes object and refresh data there .. Either outlets or Table/Collection View reload
}
}
catch {
print(error)
}
}
catch {
print(error)
}
}
Then you write a function in DetailViewController func updateDataWithObject(_ obj: Assesment) function you will write in detail class which takes object and refresh data there .. Either outlets or Table/Collection View reload
Write function in DetailViewController
func updateDataWithObject(_ obj: Assesment) {
// Refresh data
}
I have a tableView and it has about 5 cells and each cell has a different subject. Upon clicking on the cell, I will like to segue to a view controller that is populated with Firebase data only containing posts that has the specific subject clicked.
override func viewDidLoad() {
super.viewDidLoad()
FIRDatabase.database().reference().child("books").observeSingleEvent(of: .value, with: { (snapshot:FIRDataSnapshot) in
if let postsDictionary = snapshot .value as? [String: AnyObject] {
self.Category = postsDictionary["Category"] as? String ?? ""
}})
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "segue" {
let showBooks = segue.destination as! SearchTableViewController
showBooks.categorySegue = self.Category
}
}
In my Firebase database, I have a bunch of posts made by different users and in every post, they set the category to which the post belongs to. For example, if I click on the tableView cell that has the subject: Economics, I would like to segue to a view controller that only has the Economics category.
This is how I retrieve the post attributed to certain subjects:
databaseRef.child("books").queryOrdered(byChild: "category").queryEqual(toValue: categorySegue).observeSingleEvent(of: .value, with: { (snapshot) in
let key = snapshot.key
let snapshot = snapshot.value as? NSDictionary
snapshot?.setValue(key, forKey: "uid")
if(key == self.loggedUser?.uid)
{
print("same as logged in user")
}
else
{
self.usersArray.append(snapshot)
self.followUsersTableView.insertRows(at: [IndexPath(row:self.usersArray.count-1,section:0)], with: UITableViewRowAnimation.automatic)
}
}) { (error) in
print(error.localizedDescription)
}
tableView.tableFooterView = UIView()
If the category is directly below the posts node under a uniqueID, then try this
Example :
posts
-> post_id1
->category : "books"
-> other keys
-> post_id2
->category : "music"
-> other keys
The query to fetch the posts by category will be like this,
let strCategory = "music"
baseRef.child("posts").queryOrdered(byChild: "category").queryEqualToValue(strCategory).observeSingleEvent(of: .value, with: { (snapshot) in
print(snapshot)
})
To get the cell index on selection,
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let indexPath = tableView.indexPathForSelectedRow{
let selectedRow = indexPath.row
let categorySelected = Subjects[selectedRow]
}
}
i am trying to pass an image from one view controller to another but i am getting an error in prepare for segue function
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let post = posts[indexPath.row]
if let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath)as? collectionViewCellBooks {
if let img = booksVC.imageCache.object(forKey: post.imageUrl as NSString) {
cell.configureCell(post: post, img: img)
return cell
}else {
cell.configureCell(post: post)
return cell
}
}
else {
return collectionViewCellBooks()
}
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
self.performSegue(withIdentifier: "showImage", sender: self)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?)
{
if segue.identifier == "showImage"
{
let indexPaths = self.collectionView!.indexPathsForSelectedItems!
let indexPath = indexPaths[0] as IndexPath
let vc = segue.destination as! newViewController
// vc.image = self.posts[(indexPath as NSIndexPath).row]
vc.image = self.posts[indexPath.row]
}
class newViewController: UIViewController {
#IBOutlet weak var imageView: UIImageView!
var image = UIImage()
override func viewDidLoad() {
super.viewDidLoad()
self.imageView.image = self.image
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
this is my Post class
class Post {
private var _caption: String!
private var _imageUrl: String!
private var _postKey: String!
var caption: String {
return _caption
}
var imageUrl: String {
return _imageUrl
}
var postKey: String {
return _postKey
}
init(caption: String, imageUrl: String) {
self._caption = caption
self._imageUrl = imageUrl
}
init(postKey: String, postData: Dictionary<String, AnyObject>) {
self._postKey = postKey
if let caption = postData["title"] as? String {
self._caption = caption
}
if let imagesUrl = postData["imageURL"] as? String {
self._imageUrl = imagesUrl
}
}
}
title and imageURL are saved on firebase database
You are getting the error because you are not sending an image to the new viewController but an instance of your Post class, which by the way doesn't even contain an image (only an imageURL). You have to extract the image from the server first before you can parse it anywhere.
You should parse the whole post as you are doing it right now and download the image via the postID directly in the new ViewController. (in case you saved the image in Firebase storage) I always end up parseing the whole object because later in the development you maybe decide to show more properties in the newViewController. If you parsed the whole object you don't have to change your code structure anymore.
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "your Identifier" {
if let vc = segue.destination as? NewViewController {
if let post = sender as? Post {
vc.post = post
}
}
}
}
in your didSelectIdemAt function you need to change the sender of the performSegue function:
performSegue(withIdentifier: "your Identifier", sender: post)
now your newViewController has a little bit more code but that is how i do it and it works stable.
let reference = FIRDatabase.database().reference()
reference.child("posts").child("<postID>").observeSingleEvent(of: FIRDataEventType.value, with: { (snapshot) in
print(snapshot.value)
if let snapshots = snapshot.children.allObjects as? [FIRDataSnapshot] {
for snap in snapshots {
if let postsDict = snap.value as? Dictionary<String, AnyObject> {
if let imageURL = postsDict["imageURL"] as? String {
let httpsReference = FIRStorage.storage().reference(forURL: imageURL)
httpsReference.data(withMaxSize: 1 * 1024 * 1024, completion: { (data, error) in
if error != nil {
print("error... couldn't get picture from Server \(error.localizedDescription)")
} else {
let image = UIImage(data: data!)
self.img = image! // you need to create this variable somewhere in your viewController Class before this code
//"your UIImageVIew.image" = img AND THAT IS IT
}
}
}
}
}
}
Can you try with following change in preparefor segue method.. let me know
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
{
if segue.identifier == "showImage"
{
let indexPaths = self.collectionView!.indexPathsForSelectedItems!
let indexPath = indexPaths[0] as IndexPath
let vc = segue.destinationViewController as! newViewController
let post = posts[indexPath.row]
if let img = booksVC.imageCache.object(forKey: post.imageUrl as NSString) {
vc.image = img
}
}
}
class newViewController: UIViewController {
#IBOutlet weak var imageView: UIImageView!
var image = nil
override func viewDidLoad() {
super.viewDidLoad()
if self.image {
self.imageView.image = self.image
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
I am passing a Core Data entity to the next View Controller with a prepareForSegue like this:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "MemberDetails" {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let vc = storyboard.instantiateViewControllerWithIdentifier("MemberDetails") as! MemberDetails
let index = self.memberTable.indexPathForSelectedRow
if searchPredicate == nil {
let member = self.sections[index!.section].members[index!.row]
member.printMember()
vc.member = member
} else {
vc.member = self.filteredMembers[index!.row]
}
}
}
And in my receiving View Controller i have this:
var member : Member? {
didSet {
print("")
print(" --------------------- ")
print("")
member?.printMember()
}
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
print("")
print(" --------View Did Load -------- ")
print("")
self.member?.printMember()
}
With the following output:
----------_-----------
// member.printMember() function output
--------View Did Load --------
// no ouput -> object is nil
This means that the didSet happens before the viewDidLoad and it has values but for some reason it is emptied again when the viewDidLoad is executed (object = nil)
Why is this happening? / How do i mitigate this effect?
I think you misunderstood what a segue is.
You are instantiating a new MemberDetails on prepareForSegue
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let vc = storyboard.instantiateViewControllerWithIdentifier("MemberDetails") as! MemberDetails
If the prepareForSegue method is called, this means a viewController from the storyboard is already being loaded
what you need is
if segue.identifier == "MemberDetails" {
if let vc = segue.destinationViewController as? MemberDetails{
let index = self.memberTable.indexPathForSelectedRow
if searchPredicate == nil {
let member = self.sections[index!.section].members[index!.row]
member.printMember()
vc.member = member
} else {
vc.member = self.filteredMembers[index!.row]
}
}
}
I have the segue setup as:
and the tableView row selection:
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
tableView.deselectRowAtIndexPath(indexPath, animated: true)
// Ensure controller knows which dataset to pull from,
// so detail view is correct
var friendChat: Friend!
if searchController.active && searchController.searchBar.text != "" {
friendChat = filterMappedFriends[indexPath.row]
} else {
friendChat = mappedFriends[indexPath.row]
}
// Now set the conditional cases: if a friend then chat, if user then friend request if not user then can invite them:
if(friendChat.statusSort == 2) {
self.performSegueWithIdentifier("showIndividualChat", sender: friendChat)
} else if (friendChat.statusSort == 1) {
print("Can invite to be friend")
} else if (friendChat.statusSort == 0) {
print("Invite to Feast")
}
}
and the prepareForSegue:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if let indexPath = tableView.indexPathForSelectedRow {
// Ensure controller knows which dataset to pull from,
// so detail view is correct
let friendChat: Friend
if searchController.active && searchController.searchBar.text != "" {
friendChat = filterMappedFriends[indexPath.row]
} else {
friendChat = mappedFriends[indexPath.row]
}
// Now set the conditional cases: if a friend then chat, if user then friend request if not user then can invite them:
if segue.identifier == "showIndividualChat" {
let controller = segue.destinationViewController as! IndividualChatController
controller.friendChat = friendChat
controller.senderId = Global.sharedInstance.userID
controller.senderDisplayName = Global.sharedInstance.userName
}
}
}
However, the object friendChat, seen in controller.friendChat, of the destination controller is always nil.
How can I pass the data:
controller.friendChat = friendChat
controller.senderId = Global.sharedInstance.userID
controller.senderDisplayName = Global.sharedInstance.userName
to the destination controller successfully?
The first thing you are doing in didSelectRowAtIndexPath is deselecting the row, so when you try and access the selected row in prepareForSegue you are going to get no row selected.
Since you are passing the Friend instance as your sender to performSegueWithIdentifier you can just say let friendChat = sender as? Friend in prepareForSegue;
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?)
if segue.identifier == "showIndividualChat" {
if let friendChat = sender as? Friend {
let controller = segue.destinationViewController as! IndividualChatController
controller.friendChat = friendChat
controller.senderId = Global.sharedInstance.userID
controller.senderDisplayName = Global.sharedInstance.userName
}
}
}
For Swift 3
override func prepare(for segue: UIStoryboardSegue, sender: Any?)
if segue.identifier == "showIndividualChat" {
if let friendChat = sender as? Friend {
let controller = segue.destination as! IndividualChatController
controller.friendChat = friendChat
controller.senderId = Global.sharedInstance.userID
controller.senderDisplayName = Global.sharedInstance.userName
}
}
}