Swift - save ViewController set up after segue - ios

At the Moment I have to ViewControllers. You can switch between ViewControllerA and ViewControllerB with show-segues. The problem is that every time I switch back, the ViewControllers state gets reseted. How can I save the setUp of a ViewController?
From A to B
#IBAction func editButtonTapped(_ sender: Any) {
let imageCollectionView = self.storyboard?.instantiateViewController(withIdentifier: "ImageCollectionVC") as! ImageCollectionViewController
self.navigationController?.pushViewController(imageCollectionView, animated: true)
}
From B to A
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
tappedImage = images[indexPath.row]
performSegue(withIdentifier: "backToPopUpView", sender: self)
}

To move between view controllers passing data both ways you need a few things
a consistent method of navigation: segues or pushing/popping via a navigation controller or modally presenting/dismissing, but not a mix of them for one A - B - A transition
protocols/deleagtes to allow data to be passed back from th child to the parent.
In the example below navigation is via the navigation controller, and an image is used as an example of how to pass data back to the parent. It should be trivial to adapt this for other circumstances.
The child class needs an agreed interface with its parent to allow it to communicate. This is done with a protocol. In this example we provide a means for the child to pass its updated image back to the parent:
protocol ClassBDelegate {
func childVCDidComplete(with image: UIImage?)
}
Then create a delegate variable in the child class that can point to any class that adopts the protocol (which in this case will be its parent) and then use that delegate and the protocol's function to pass the image data back. Once the data has been passed back via the delegate, view controller B calls the navigation controller's popViewCntroller method to close itself and return focus to view controller A
class B: UIViewController {
var delegate: ClassBDelegate?
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
tappedImage = images[indexPath.row]
delegate?.childVCDidComplete(with: tappedImage)
navigationController?.popViewController(animated: true)
}
}
For this all to work, the child view controller's delegate needs to be set to point its parent view controller (A), but before this can happen that class needs to conform to the protocol:
extension A: ClassBDelegate {}
func childVCDidComplete( with image: UIImage?) {
self.image = image
}
}
Now, when instantiating the child view controller the parents set itself as the delegate, thus completing the communication loop.
Class A: UIViewController {
var image: UIImage?
#IBAction func editButtonTapped(_ sender: Any) {
let imageCollectionView = self.storyboard?.instantiateViewController(withIdentifier: "ImageCollectionVC") as! ImageCollectionViewController
imageCollectionView.delegate = self
self.navigationController?.pushViewController(imageCollectionView, animated: true)
}
}

You are created new ViewControllerA, and you have the following result A -> B -> A.
But it is not true, you should dismiss ViewControllerB...
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
tappedImage = images[indexPath.row]
self.dismiss(animated: true, completion: nil)
//performSegue(withIdentifier: "backToPopUpView", sender: self)
}

Related

how to push ViewController from xib tableview Cell?

I have tableView within collectionView and i created using xib. I want to pushViewController when item is selected .I tried pushing the view controller in itemForRowAt method but it's not possible
class SecondTableView: UITableViewCell , UIcollectionViewDelegate ,UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
// let vc = DataTableViewController()
// vc.delegate = self
// vc.pushNav()
let memeVC = storyboard?.instantiateViewController(withIdentifier: "MemeViewController") as! MemeViewController
memeVC.imagePassed = image
navigationController?.pushViewController(memeVC, animated: true)
print("item tapped\(indexPath)")
}
}
errors
Use of unresolved identifier 'storyboard'; did you mean 'UIStoryboard'?
Add segue as mentioned in the image and select show
Set segue identifier in your storyboard as mentioned in the image
Add below Protocol:
protocol CollectionViewCellDelegate: class {
func userDidTap()
}
Add below property in your table view class where the collection view delegate returns.
weak var delegate: CollectionViewCellDelegate?
Update delegate method
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
delegate.userDidTap()
print("item tapped\(indexPath)")
}
}
confirm CollectionViewCellDelegate delegate to your first view controller & add this method:
func userDidTap() {
performSegue(withIdentifier: "showMemeViewController", sender: nil)
}
Add prepare for segue in your first VC.
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "showMemeViewController" {
let memeVC: MemeViewController = segue.destination as! MemeViewController
memeVC.imagePassed = image
}
}
Please try this let me know if you faced any other issue. Thanks.
You're trying to access storyboard property in a tableview cell, which isn't there in the first place, it's a UIViewController property not a UITableViewCell property, hence the error, you'll have to delegate your selection to the viewController then let the controller do the segue normally.
You can start doing that via delegates as I mentioned above, for example if you're passing image to the controller you wanna segue to:
protocol ImageTableViewCellDelegate: class {
// pass the the image or any variable you want in the func params
func userDidTap(_ image: UIImage)
}
then you'd add it to the cell and confirm the controller to it
class SecondTableView {
weak var delegate: ImageTableViewCellDelegate?
}
In your controller:
extension MyViewController: ImageTableViewCellDelegate {
func userDidTap(_ image: UIImage){
let memeVC = storyboard?.instantiateViewController(withIdentifier: "MemeViewController") as! MemeViewController
memeVC.imagePassed = image
navigationController?.pushViewController(memeVC, animated: true)
print("item tapped\(indexPath)")
}
}
this should work, let me know if you had any further problems

Properly segue from collectionview inside tableviewcell to another viewcontroller

I am trying to segue (pass data) from a collectionview inside a tableviewcell to a another viewcontroller. I tried using the didselect delegate but stuck on how to pass the data properly. It seems i kind of somehow hack my way around it but i would like to learn the proper way. Below is my code:
My Main view controller:
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "segue" {
let vc = segue.destination as! DetailViewController
vc.text = "Hello World"
}
}
func segue() {
self.performSegue(withIdentifier: "segue", sender: self)
}
}
My Table View:
import UIKit
class MainTableView: UITableView, UITableViewDelegate, UITableViewDataSource {
override func awakeFromNib() {
self.delegate = self
self.dataSource = self
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "MainTableViewCell", for: indexPath) as! MainTableViewCell
return cell
}
}
My Collection View:
import UIKit
class MoviesCollectionView: UICollectionView, UICollectionViewDelegate, UICollectionViewDataSource {
override func awakeFromNib() {
self.delegate = self
self.dataSource = self
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 10
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "MoviesCollectionViewCell", for: indexPath) as! MoviesCollectionViewCell
return cell
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let vc = ViewController()
vc.segue()
}
}
The View controller I am trying to segue to:
class DetailViewController: UIViewController {
#IBOutlet weak var label: UILabel!
var text: String?
override func viewDidLoad() {
super.viewDidLoad()
self.label.text = text
// Do any additional setup after loading the view.
}
}
My tableviewcell and collectionviewcell are empty at the moment.
Your question is a little hard to answer as currently stated because it's not entirely clear what you are doing. For example, it's not clear if you are using Storyboards or not.
If you are, then you probably want to define your segues in the storyboard and let UIKit invoke them for you. This documentation provides the overview that will hopefully help you. In particular, you don't have to do the segue manually because UIKit will do it for you once you've set it up in the Storyboard (emphasis mine):
You do not need to trigger segues programmatically. At runtime, UIKit
loads the segues associated with a view controller and connects them
to the corresponding elements. When the user interacts with the
element, UIKit loads the appropriate view controller, notifies your
app that the segue is about to occur, and executes the transition. You
can use the notifications sent by UIKit to pass data to the new view
controller or prevent the segue from happening altogether.
In figure 9-4 you will see the event flow of a segue process. In particular, note that if you override prepareForSegue:sender: in your source view controller for the segue then that is your opportunity to prepare data and send it to the destination view controller (either view setting the representedObject for the destination view controller, or via a custom setter method defined for your destination view controller class). The description text below that figure spells it out:
The prepareForSegue:sender: method of the source view controller lets
you pass data from the source view controller to the destination view
controller. The UIStoryboardSegue object passed to the method contains
a reference to the destination view controller along with other
segue-related information.
If you aren't using storyboards, then you're essentially doing the same thing by hand. You allocate the UIViewController subclass you want to show in response to the touch in the cell item, and then set its representedObject to the data it should display (or call a custom method defined by that class and pass in the data the view controller needs to display), then you show the view controller. To present it manually you'll want to read the Presenting a View Controller document from Apple. In particular:
Presenting a View Controller
There are several ways to initiate the presentation of a view controller:
Use a segue to present the view controller automatically. The segue
instantiates and presents the view controller using the information
you specified in Interface Builder. For more information on how to
configure segues, see Using Segues. Use the showViewController:sender:
or showDetailViewController:sender: method to display the view
controller. In custom view controllers, you can change the behavior of
these methods to something more suitable for your view controller.
Call the presentViewController:animated:completion: method to present
the view controller modally.
Hopefully that's enough to get you going. If not, maybe clarify your question with a bit more context and we'll try again.

Swift UICollectionView Delegate perform segue

I'm using an external delegate file to handle all a UICollectionView's processing and I'm struggling to get the collection view cell to perform a segue based on the selected cell, via the delegate file.
Here's what I currently have inside the delegate file:
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let cell = collectionView.cellForItem(at: indexPath)
let mainController = MainController()
mainController.performSegue(withIdentifier: "detailSegue", sender: cell)
}
and on the Main Controller I have:
override func performSegue(withIdentifier identifier: String, sender: Any?) {
if identifier == "detailSegue" {
let detailController = DetailController()
self.present(detailController, animated: true)
}
}
The error I'm getting:
Warning: Attempt to present <DetailController: 0x7fbed8e6e4b0> on <MainController: 0x7fbedda79830> whose view is not in the window hierarchy!
I thought I could call up the reference via the delegate and it would present the controller.
Thanks
The reference of MainController that you are trying to get like below is wrong.
let mainController = MainController()
This will not give you the loaded/previewing instance of the object instead of this it will create a new object of MainController for you which is not in the previewing Hierarchy. So you need an actual previewing instance.
Solution
Create a global object of of UIViewController type in your delegate class and pass you previewing class reference so that you may use it when you need.
Example.
class DelegateDataSource: NSObject {
var viewController: UIViewController?
//Other Methods/Objects that you need.
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let cell = collectionView.cellForItem(at: indexPath)
if let mainController = viewController as? MainController {
mainController.performSegue(withIdentifier: "detailSegue", sender: cell)
}
}
}
class MainController: UIViewController {
var delegateDataSource: DelegateDataSource?
func initializeDelegates() {
//Initialize you datasource object and do some tasks that you need and then assign your current instance to this class like this.
delegateDataSource.viewController = self
}
}
let mainController = MainController()
mainController.performSegue(withIdentifier: "detailSegue", sender: cell)
This will not work, either use delegate pattern or use singleton pattern for your MainController.
Only an already presented controller can present another controller. Is the class implementing didSelectItemAt a UIViewController? If so, why not just do:
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let cell = collectionView.cellForItem(at: indexPath)
performSegue(withIdentifier: "detailSegue", sender: cell)
}

prepare function not called - swift 3

I have a collection view. each item should pass data to second view, in other world I want to pass current view to second view by navigationController.
class GroupsViewController: UIViewController ,UICollectionViewDataSource, UICollectionViewDelegate {
....
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
// handle tap events
var group = self.items[indexPath.item]
var cat_title = group.cat_title
self.navigationController!.pushViewController(self.storyboard!.instantiateViewController(withIdentifier: "GroupSubOneTableViewController") as UIViewController, animated: true)
}
....
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
print("ooopps")
if (segue.identifier == "GroupSubOneTableViewController") {
let navController = segue.destination as! UINavigationController
let detailController = navController.topViewController as! GroupSubOneTableViewController
//detailController.currentId = nextId!
print("selected GroupSubOneTableViewController")
}
}
but in console log doesn't show anything!
You are mixing here two thing perfomSegue and pushViewController. perform​Segue(with​Identifier:​sender:​)
is called when you perform a segue using performSegue. So create one segue from your SourceVC to DestinationVC.
If you want to go with pushViewController then simply type cast UIViewController to your ViewController pass the data.
let vc = self.storyboard!.instantiateViewController(withIdentifier: "GroupSubOneTableViewController") as! GroupSubOneTableViewController
//pass vaue
vc.passStr = "Hello"
self.navigationController?.pushViewController(vc, animated: true)
It's not called because pushViewController doesn't perform a segue.
You need to call performSegue(withIdentifier: "GroupSubOneTableViewController", sender: self) instead of pushViewController.
Of course, you'll also need to set this up in your storyboard.

Perform a segue selection from a uicollectionview that is embedded in a tableview cell?

Currently we have a uicollectionview that is embedded in a tableview cell. When the collection view cell is selected it's suppose to initiate a push segue to another view controller. The problem is there is no option to perform the segue on the cell. Is there a way around it? Here is the cell:
class CastCell : UITableViewCell {
var castPhotosArray: [CastData] = []
let extraImageReuseIdentifier = "castCollectCell"
let detailToPeopleSegueIdentifier = "detailToPeopleSegue"
var castID: NSNumber?
#IBOutlet weak var castCollectiontView: UICollectionView!
override func awakeFromNib() {
castCollectiontView.delegate = self
castCollectiontView.dataSource = self
}
}
extension CastCell: UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return castPhotosArray.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = castCollectiontView.dequeueReusableCell(withReuseIdentifier: extraImageReuseIdentifier, for: indexPath) as! CastCollectionViewCell
cell.actorName.text = castPhotosArray[indexPath.row].name
return cell
}
}
extension CastCell: UICollectionViewDelegate {
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
self.castID = castPhotosArray[indexPath.row].id
performSegue(withIdentifier: detailToPeopleSegueIdentifier, sender: self) //Use of unresolved identifier 'performSegue' error
}
}
extension CastCell {
func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let peopleVC = segue.destination as! PeopleDetailViewController
peopleVC.id = self.castID
}
}
The problem is there is no option to perform the segue on the cell
There is no such thing as a "segue on a cell". A segue is from one view controller to another. performSegue is a UIViewController method. So you cannot say performSegue from within your CastCell class, because that means self.performSegue, and self is a UITableViewCell — which has no performSegue method.
The solution, therefore, is to get yourself a reference to the view controller that controls this scene, and call performSegue on that.
In a situation like yours, the way I like to get this reference is by walking up the responder chain. Thus:
var r : UIResponder! = self
repeat { r = r.next } while !(r is UIViewController)
(r as! UIViewController).performSegue(
withIdentifier: detailToPeopleSegueIdentifier, sender: self)
1: A clean method is to create a delegate protocol inside your UITableViewCell class and set the UIViewController as the responder.
2: Once UICollectionViewCell gets tapped, handle the taps inside the UITableViewCell and forward the tap to your UIViewController responder through delegatation.
3: Inside your UIViewController, you can act on the tap and perform/push/present whatever you want from there.
You want your UIViewController to know what is happening, and not call push/presents from "invisible" subclasses that should not handle those methods.
This way, you can also use the delegate protocol for future and other methods that you need to forward to your UIViewController if needed, clean and easy.

Resources