perform segue from UIView - ios

I have ViewController and there is UIView in it.
This UIView has separate class myView and there are many UI elements - one of them is CollectionView.
What I want is to perform segue when one of collection elements in myView is selected. But when I try to add line
performSegue(withIdentifier: "myIdintifier", sender: self)
to collection's view didSelectItemAt method I get error
Use of unresolved identifier 'performSegue'
And I understand that this is because I do it inside class that extends UIView and not UIViewController.
So how can I perfrom segue in this case? And also how can I prepare for segue?

Here I am going to evaluate it in step by step manner.
Step - 1
Create custom delegate using protocol as below snippet will guide you on your custom UIView. protocol must exist out of your custom view scope.
protocol CellTapped: class {
/// Method
func cellGotTapped(indexOfCell: Int)
}
Don't forgot to create delegate variable of above class as below on your custom view
var delegate: CellTapped!
Go with your collection view didSelect method as below
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
if(delegate != nil) {
self.delegate.cellGotTapped(indexOfCell: indexPath.item)
}
}
Step - 2
Let's come to the your view controller. give the CellTapped to your viewcontroller.
class ViewController: UIViewController,CellTapped {
#IBOutlet weak var myView: MyUIView! //Here is your custom view outlet
override func viewDidLoad() {
super.viewDidLoad()
myView.delegate = self //Assign delegate to self
}
// Here you will get the event while you tapped the cell. inside it you can perform your performSegue method.
func cellGotTapped(indexOfCell: Int) {
print("Tapped cell is \(indexOfCell)")
}
}
Hope this will help you.

You can achieve using protocols/delegates.
// At your CustomView
protocol CustomViewProtocol {
// protocol definition goes here
func didClickBtn()
}
var delegate:CustomViewProtocol
#IBAction func buttonClick(sender: UIButton) {
delegate.didClickBtn()
}
//At your target Controller
public class YourViewController: UIViewController,CustomViewProtocol
let customView = CustomView()
customView.delegate = self
func didClickSubmit() {
// Perform your segue here
}

Other than defining protocol, you can also use Notification.
First, extent nonfiction.name:
extension Notification.Name {
static let yourNotificationName = Notification.Name(“yourNotificationName”)
}
Then right where you want to perform segue but can’t in your custom UIView:
NotificationCenter.default.post(name: .yourNotificationName, object: self)
Finally, you can listen to the notification in your viewControllers:
private var observer: NSObjectProtocol?
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
observer = NotificationCenter.default.addObserver(forName: .yourNotificationName, object: nil, queue: nil) {notification in
self.performSegue(withIdentifier:”your segue”, sender: notification.object}
Don’t forget to remove it:
override func viewWillDisappear(_ animated: Bool){
super.viewWillDisappear(animated)
NotificationCenter.default.removeObserver(observer)
}

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

Best way to reload vc table view backing from another vc

I'm trying to reload table view in Calculation controller, pressing back navigation button on Setup controller (red arrow on screenshot).
Which is the best way to do it?
Thanks !
In a navigation controller its's pretty easy. In Swift the most efficient way is a callback closure, it avoids the overhead of protocol/delegate.
In SetupController declare a callback property, a closure with no parameter and no return type
var callback : (() -> Void)?
and call it in viewWillDisappear. viewWillDisappear is allways called when the back button is pressed.
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
callback?()
}
In CalculationController assign the callback in prepare(for
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
...
let setupController = segue.destination as! SetupController
setupController.callback = {
self.tableView.reloadData()
}
Using Delegate Pattern
Create delegate with some method for second ViewController. Implement this protocol to first ViewController and when this method is called, reload UITableView data (in overriden prepare(for:sender:) set delegate of second ViewController to self). When second ViewController will disappear, call method on delegate variable of second ViewController.
Now when you're able to use delegates, you can easily add parameter to delegate's method and pass data from second to first ViewController.
protocol SecondVCDelegate: class { // define delegate protocol
func controllerDismissed()
}
class ViewController: UIViewController {
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "yourIdentifier" {
let destinationVC = segue.destination as! SecondViewController
destinationVC.delegate = self
}
}
}
extension ViewController: SecondVCDelegate {
func controllerDismissed() { // this is called when you call delegate method from second VC
tableView.reloadData()
}
}
class SecondViewController: UIViewController {
weak var delegate: SecondVCDelegate? // delegate variable
override func viewWillDisappear(_ animated: Bool) {
delegate?.controllerDismissed() // call delegate's method when this VC will disappear
}
}
An easy solution is to reload the tableView, when the view is going to appear again.
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
tableView.reloadData()
}
Alternative solutions could be to implement unwindSegue or delegation.
to achieve this you have multiple solutions first of all you have to know what is the best to use by your case,
1- are you passing data back to the CalculationVC
2- do you just need to reload the CalculationVC each time it appears ?
for the first case you use what called Delegates in swift.
for the second case you can use a life-cycle function that is called viewWillAppear() in the ViewController.
for the Delegate case you can find tons of articles online this one recommended for newbies !
and for the second case just use this code.
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
tableView.reloadData()
}
Try this code
protocol VC2Delegate: class {
func viewController(_ myVC2: VC2?, didFinishEditingWithChanges hasChanges: Bool)
}
class VC2 {
private weak var: VC2Delegate? delegate?
weak var: VC2Delegate? delegate?
#IBAction func finishWithChanges() {
delegate.viewController(self, didFinishEditingWithChanges: true)
}
#IBAction func finishWithoutChanges() {
delegate.viewController(self, didFinishEditingWithChanges: false)
}
}
//VC1: implement the VC2Delegate protocol
class VC1: VC2Delegate {
var: Bool _needsReload?
func awakeFromNib() {
super.awakeFromNib()
needsReload = true
}
func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
reloadTableIfNeeded()
}
#IBAction func displayVC2() {
}

Provide action for button in the xib file using swift3

PROVIDED: I have a button in my xib
The swift file associated with this is also attached.
ISSUE: this cell has a button that need to display a ViewController on the button click. This cell is attached to the table view in another ViewController. I want to implement an action on the button "BOOK" so as on clicking the new view controller should open. i am not able to do this can any one suggest me something that i should do?
CODE:
import UIKit
class HotelBookingCell: UITableViewCell {
#IBOutlet weak var BookbtnOutlet: UIButton!
override func awakeFromNib() {
super.awakeFromNib()
BookbtnOutlet.layer.cornerRadius = 7
// Initialization code
}
#IBAction func bookbtn1(_ sender: Any) {
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
remove the following code in tableviewcell class
/*
#IBAction func bookbtn1(_ sender: Any) {
} */
and add into your UIviewcontroller cellForRowAtIndexPath
cell. BookbtnOutlet.tag = indexpath.row
cell. BookbtnOutlet.addTarget(self, action: #selector(self. bookbtn1(_:)), for: .touchUpInside);
And anywhere in the same UIVeiwController define the function as below
func bookbtn1(_ sender : UIButton){
// call your segue code here
}
One of possible solutions it to create a protocol for this:
protocol HotelBookingCellDelegate: class {
// you can add parameters if you want to pass. something to controller
func bookingCellBookButtonTouched()
}
then is you cell class
class HotelBookingCell: UITableViewCell {
// add a propery
public weak var delegate: HotelBookingCellDelegate?
#IBAction func bookbtn1(_ sender: Any) {
delegate?.bookingCellBookButtonTouched()
}
}
in your cellForRowAtIndexPath
cell.delegate = self
after that in you controller where you signed for this protocol, implement it
extension YourViewController: HotelBookingCellDelegate {
func bookingCellBookButtonTouched() {
// Do whatever you want
}
}
You can create a delegate protocol in the cell class and then set the delegate equal to viewcontroller where tablview cell will show up. Then on click the delegate function will be called and you will get the action the view controller where you can push or pop a view controller or any other action you want.
Sample code - Cell Class
protocol ButtonDelegate {
func buttonClicked()
}
weak var delegate : ButtonDelegate?
#IBAction func bookbtn1(_ sender: Any) {
delegate?.buttonClicked()
}
Now in View Controller conform to the protocol - "ButtonDelegate", set
cell.delegate = self
and then implement the method "buttonClicked()"
You will get the action in buttonClicked() when button is clicked.
There are two possible solution here
1) You can add target for this button in cellForRowAtIndexPath like below code
cell.bookbtn1.tag = indexPath.row
cell.bookbtn1.addTarget(self, action: #selector(self. bookbtn1(sender:)), for: .touchUpInside)
2) another solution is in your main view controller you can add Notification center observer in viewDidLoad like this
NotificationCenter.default.addObserver(self, selector: #selector(self.bookbtn1ClickedFromCell), name: NSNotification.Name(rawValue: BUTTON_CLICK), object: nil)
and implement method and navigate in another view controller from this method
func bookbtn1ClickedFromCell()
{
//navigate to another vc
}
and in action method that you implemented in UITableViewCell file post this notification like this
#IBAction func bookbtn1(_ sender: Any) {
NotificationCenter.default.post(name: Notification.Name(rawValue: BUTTON_CLICK), object: self)
}
so it will called bookbtn1ClickedFromCell in your main view controller from this you can navigate to another view controller
you should remove observer in viewWillDisappear or in deinit method
deinit {
NotificationCenter.default.removeObserver(self)
}
or
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
NotificationCenter.default.removeObserver(self)
}

stop UICollectionView from loading while initializing

I have a UICollectionView on a separate view let say "dashboardView" for the partial code is giving below,
class DashBoardView: UIView
{
#IBOutlet weak var collectionView: UICollectionView!
override func awakeFromNib() {
collectionView.dataSource=self
collectionView.delegate=self
let nibLineChart = UINib(nibName: "GraphCell", bundle: nil)
collectionView.registerNib(nibLineChart, forCellWithReuseIdentifier:lineChartIdentifier )
}
}
Now this view is embeded in a UIViewController and the sample code is giving below,
class ViewController : UIViewController
{
override func viewDidLoad() {
super.viewDidLoad()
graphicalDashBoardView = DashBoardView.instanceFromNib() as! DashBoardView
}
}
now what it does as soon the viewdidload call finishes it tries to render the UIcollectionView in the inner view and call the cellForItemAtIndexPath. I just dont want the uicollectionview to load when it finishes viewdidload call. I rather want to load this collectionview on a button click. I know i can reload it by using the collectionview.reloadData() but how i should stop it for the first time loading.
Any help would be appreciable.
You may try this solution.
Don't fill the items when viewdidload delegate method is called.
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return self.items.count
}
On button tap fill the items and collection view.reloadData().
This should work.
Remove the code from your viewdidload so that it doesn't load at the beginning. Add a button and connect it with a button click event. Inside the click event add your code to load the uicollectionview.
Try this code:
class ViewController : UIViewController
{
override func viewDidLoad() {
super.viewDidLoad()
}
}
#IBAction func buttonPressed(sender: AnyObject) {
graphicalDashBoardView = DashBoardView.instanceFromNib() as! DashBoardView
}
not saying it's elegant, but you could make a
var renderCollection: bool = false
and in your numberOfSectionsInCollectionView and numberOfItemsInSection functions add
if !renderCollection return 0
until the moment you want the collection to load when you would do
renderCollection = true ; collectionView.reloadData();

Responder Chain but NOT delegate property passes value back to view controller from container

The following code should show two ways to pass information from an embedded controller (UICollectionView) back to a detailed view controller using either the Responder Chain OR delegate approach. Both approaches use the same protocol, and delegate method. The only difference is if I comment out the delegate?.method line in didSelectItemAtIndex path, the Responder Chain works. BUT, if I comment out the Responder Chain line in the didSelectItemAtIndex method, the uncommentented delegate? property doesn't call the method, and remains nil.
Protocol defined and included above DetailViewController. Needed for both approaches.
protocol FeatureImageController: class {
func featureImageSelected(indexPath: NSIndexPath)
}
Delegate property declared in the custom UICollectionViewController class, which is only needed for delegate approach.
class PhotoCollectionVC: UICollectionViewController
{
weak var delegate: FeatureImageController?
In DetailViewController, an instance of PhotoCollectionVC() is created, and the delegate property set to self with the delegate protocol as type.
class DetailViewController: UIViewController, FeatureImageController
{...
override func viewDidLoad() {
super.viewDidLoad()
let photoCollectionVC = PhotoCollectionVC()
photoCollectionVC.delegate = self as FeatureImageController
Within the collection view controller's didSelectItemAtIndexPath method, pass back the selected indexPath via either the Responder Chain (commented out) OR the delegate to the featureImageSelected method in the DetailVC.
override func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath)
{
// if let imageSelector = targetForAction("featureImageSelected:", withSender: self) as? FeatureImageController {
// imageSelector.featureImageSelected(indexPath)
// }
self.delegate?.featureImageSelected(indexPath)
}
An instance of elegate method in DetailViewController. Needed for both.
func featureImageSelected(indexPath: NSIndexPath) {
record?.featureImage = record?.images[indexPath.row]
self.configureView()
}
Why would the Responder Chain approach work, but the delegate not?
There are no compiler or run time errors. Within the didSelectItemAtIndexPath method, the delegate always returns nil and nothing prints from the delegate method.
Your responder code calls a featureImageSelected on self:
self.featureImageSelected(indexPath)
but the delegate code calls featureImageSelected on the delegate:
self.delegate.featureImageSelected(indexPath)
Which would be the DetailVC's delegate, not the collectionViews delegate. Im not really sure what your code is doing, but you probably want something like
collectionView.delegate?.featureImageSelected(IndexPath)
which looks like it would just end up being
self.featureImageSelected(indexPath)
The error in the question is where, in the conforming class, "an instance of PhotoCollectionVC() is created, and the delegate property set to self". In viewDidLoad, that just creates another instance with an irrelevant delegate property that will never be called. The delegate property of the actual embedded PhotoCollectionVC needs to be assigned to self - in order for the two VCs to communicate. This is done from within the prepareForSegue method:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?)
{
...
let controller = (segue.destinationViewController as! PhotoCollectionVC)
...
controller.delegate = self
}
}
}
The rest of the example code is fine.
Here is a super simple example of delegation from an embedded container to its delegate VC. The embedded container simply tells the VC that a button has been pressed. The story board is just a VC with a container in it and a text outlet. In the container VC, there is just a button. And the segue has an identifier.
The code in the delegate ViewController is:
protocol ChangeLabelText: class
{
func changeText()
}
class ViewController: UIViewController, ChangeLabelText
{
#IBOutlet weak var myLabel: UILabel!
override func viewDidLoad()
{
super.viewDidLoad()
myLabel?.text = "Start"
}
func changeText()
{
myLabel?.text = "End"
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "feelTheBern"
{
let secondVC: myViewController = segue.destinationViewController as! myViewController
secondVC.delegate = self
}}
}
The code in the delegating View Controller, myViewController, is:
class myViewController: UIViewController
{
weak var delegate: ChangeLabelText?
#IBAction func myButton(sender: AnyObject)
{
print("action")
delegate?.changeText()
}
override func viewDidLoad() {
super.viewDidLoad()
}
}

Resources