I have a table view using a custom cell (xib).
This custom cell have an image and a description.
If user tapped the image (not the cell) , it should display other page/view.
this is the function called when image tapped
#objc func imageTapped(){
let sb = UIStoryboard(name: "SbIdentifier", bundle: nil)
let vc = sb.instantiateViewController(withIdentifier: "ProfileViewController") as! ProfileViewController
print("image tapped")
//self.window?.rootViewController?.parent?.navigationController?.pushViewController(vc, animated: true)
}
I tried used the push view controller but nothing happened.
You can use delegate pattern here to handle tap on profile image in tableViewCell to push ProfileViewController.
First create the CellImageTappableDelegate protocol
protocol CellImageTappableDelegate: class {
func didTapProfileImage(with imageUrl: String)
}
then create the delegate property in Your CustomCell
weak var imageTapDelegate: CellImageTappableDelegate?
now enable isUserInteractionEnabled property on UIImageView
image.isUserInteractionEnabled = true
then add the tapGestureRecognizer to your imageView and implement the selector
let tapGestureRecognizer = UITapGestureRecognizer.init(target: self, action: #selector(CustomProfileCell.didTapProfileImage(_:)))
profileImageView.addGestureRecognizer(tapGestureRecognizer)
func didTapProfileImage(_ tapGesture: UITapGestureRecognizer) {
self.imageTapDelegate?.didTapProfileImage(with: self.profileImageUrl)
}
in tableView datasource cellForRowAt: method in your UIViewController subclass assign the delegate to self and implement the protocol method
cell.imageTapDelegate = self
func didTapProfileImage(with imageUrl: String) {
print("Profile image tapped")
let storyboard = UIStoryboard(name: "SbIdentifier", bundle: nil)
let profileVC = storyboard.instantiateViewController(withIdentifier: "ProfileViewController") as! ProfileViewController
profileVC.imageUrl = imageUrl
self.navigationController?.pushViewController(profileVC, animated: true)
}
RootViewController would not have a parent, which would definitely not have a navigationController. If the navigationController is the first viewController in your hierarchy, you can try:
(self.window?.rootViewController as? UINavigationController)?.pushViewController(vc, animated: true)
Related
I am using a Class which is a subclass of MessageView (Swift Message Library) which is inherit from UIView. Inside, I have a UIButton and I want to present programmatically another ViewController through it.
Here is my code below :
import Foundation
import SwiftMessages
import UIKit
class MyClass: MessageView {
var hideBanner: (() -> Void)?
#IBAction func helpButtonPressed(_ sender: UIButton) {
let storyBoard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let newViewController = storyBoard.instantiateViewController(withIdentifier: "newViewController") as! NewViewController
self.present(newViewController, animated: true, completion: nil)
#IBAction func tryAgainButtonPressed(_ sender: UIButton) {
hideBanner?()
}
open override func awakeFromNib() {
}
}
I have tried this, but it is not working since the UIView do not have the present method.
First get top ViewController using this. Then you can present your viewController.
if var topController = UIApplication.sharedApplication().keyWindow?.rootViewController {
while let presentedViewController = topController.presentedViewController {
topController = presentedViewController
}
// topController now can use for present.
let storyBoard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let newViewController = storyBoard.instantiateViewController(withIdentifier: "newViewController") as! NewViewController
topController.present(newViewController, animated: true, completion: nil)
}
.present is a method in UIViewController class, that's the reason you cannot present view controller from UIView class.
To achieve this, get the root view controller and present the controller as follows:
let appDelegate = UIApplication.shared.delegate as! AppDelegate
let viewController = appDelegate.window!.rootViewController as! YourViewController
let storyBoard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let newViewController = storyBoard.instantiateViewController(withIdentifier: "newViewController") as! NewViewController
viewController .present(newViewController, animated: true, completion: nil)
The iOS convention is that only a ViewControllers presents another ViewController.
So the answers above - where the View is finds the current ViewController via UIApplication.sharedApplication().keyWindow?.... will work but is very much an anti-pattern.
The preferred way would be:
Your MyClass view has presentation code only
You must have a ViewController which has a reference to this MyClass view
This ViewController has the #IBAction func tryAgainButtonPressed
From there, you can present the next ViewController
Try this #simple code.
import Foundation
import SwiftMessages
import UIKit
class MyClass: MessageView {
var hideBanner: (() -> Void)?
#IBAction func helpButtonPressed(_ sender: UIButton) {
let storyBoard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let newViewController = storyBoard.instantiateViewController(withIdentifier: "newViewController") as! NewViewController
UIApplication.shared.keyWindow?.rootViewController?.present(newViewController, animated: true, completion: nil)
}
#IBAction func tryAgainButtonPressed(_ sender: UIButton) {
hideBanner?()
}
open override func awakeFromNib() {
}
}
Here is the example code using delegation pattern.
class YourViewController: UIViewController {
var yourView: MyClass // may be outlet
override func viewDidLoad() {
super.viewDidLoad()
yourView.delegate = self
}
}
protocol MyClassDelegate:class {
func tryAgainButtonDidPressed(sender: UIButton)
}
class MyClass: MessageView {
weak var delegate: MyClassDelegate?
#IBAction func tryAgainButtonPressed(_ sender: UIButton) {
delegate?.tryAgainButtonDidPressed(sender: sender)
}
}
You can achieve this by two ways
Protocol
By giving reference of that view controller to the view when you are initializing view
Sorry for the late reply. MessageView already provides a buttonTapHandler callback for you:
/// An optional button tap handler. The `button` is automatically
/// configured to call this tap handler on `.TouchUpInside`.
open var buttonTapHandler: ((_ button: UIButton) -> Void)?
#objc func buttonTapped(_ button: UIButton) {
buttonTapHandler?(button)
}
/// An optional button. This buttons' `.TouchUpInside` event will automatically
/// invoke the optional `buttonTapHandler`, but its fine to add other target
/// action handlers can be added.
#IBOutlet open var button: UIButton? {
didSet {
if let old = oldValue {
old.removeTarget(self, action: #selector(MessageView.buttonTapped(_:)), for: .touchUpInside)
}
if let button = button {
button.addTarget(self, action: #selector(MessageView.buttonTapped(_:)), for: .touchUpInside)
}
}
}
which is automatically invoked for any button you connect to the button outlet. So the recommended method for presenting another view controller is to have the presenting view controller configure the presentation logic in this callback:
messageView.tapHandler = { [weak self] in
guard let strongSelf = self else { return }
let storyBoard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let newViewController = storyBoard.instantiateViewController(withIdentifier: "newViewController") as! NewViewController
strongSelf.present(newViewController, animated: true, completion: nil)
}
If your view has more than one button, you can handle them all through buttonTapHandler since it takes a button argument. You just need to configure the target-action mechanism for each button:
otherButton.addTarget(self, action: #selector(MessageView.buttonTapped(_:)), for: .touchUpInside)
Or you can add one callback for each button by duplicating the above pattern.
I have an alert view that i made using presentr from GitHub. I used a simple view controller that will overlay over the current view controller. Now i have elements such as a UIImage and a UIlabel from the first view controller that needs to be accessed by the alert view controller. But when I click a unbutton in the alert view controller to access the uiimage and text from uilabel from the firstviewcontroller. Here is the code. Can you show me how I can fix this. I can't segue the data because I'm presenting the view controller and the data I'm trying to access is too much too segue anyway. I keep getting this error "fatal error: unexpectedly found nil while unwrapping an Optional value" overtime i click the save button in alertviewcontroller.
class firstviewcontroller: UIViewController{
var photos2: [ImageSource]?
#IBOutlet weak var Label: UIlabel!
#IBOutlet weak var Image: UIImage!
}
class alertviewcontroller: UIViewController{
#IBAction func Save(_ sender: Any) {
dismiss(animated: true, completion: nil)
let storyboard: UIStoryboard = UIStoryboard.init(name: "Main", bundle: nil)
let firstViewController: firstviewcontroller = storyboard.instantiateViewController(withIdentifier: "firstviewcontroller") as! firstviewcontroller
if let image = firstViewController.Image {
imageview.image = image
}
if let label = firstViewController.Label {
label.text = Label.text
}
}
}
The biggest mistake you're making is that you are creating a new instance of firstviewcontroller instead of just accessing the current one.
class alertviewcontroller: UIViewController{
#IBAction func Save(_ sender: Any) {
dismiss(animated: true, completion: nil)
let storyboard: UIStoryboard = UIStoryboard.init(name: "Main", bundle: nil)
let firstViewController: firstviewcontroller = storyboard.instantiateViewController(withIdentifier: "firstviewcontroller") as! firstviewcontroller // This is the mistake
if let image = firstViewController.Image {
imageview.image = image
}
if let label = firstViewController.Label {
label.text = Label.text
}
}
}
What you should do instead is access the presentingViewController since you presented the alertviewcontroller using the present function
The code would look like this
class alertviewcontroller: UIViewController{
#IBAction func Save(_ sender: Any) {
dismiss(animated: true, completion: nil)
let storyboard: UIStoryboard = UIStoryboard.init(name: "Main", bundle: nil)
// NOTE: only do implicit unwrapping `as!` if you're sure that the value you're unwrapping is not `nil` or it is of the correct `data type` cause it might cause your app to crash
let firstViewController: firstviewcontroller = self.presentingViewController as! firstviewcontroller
if let image = firstViewController.Image {
imageview.image = image
}
if let label = firstViewController.Label {
label.text = Label.text
}
}
}
Tip: Please review Swift coding guidelines since there are some minor mistakes regarding your naming of methods, variables, and classes.
Might I am wrong but after review your code I have found this messing statement
let firstViewController: firstviewcontroller = storyboard.instantiateViewController(withIdentifier: "firstviewcontroller") as! firstviewcontroller
In this statement you are creating a new instance of firstviewcontroller not accessing already created instance of firstviewcontroller.
Next, you said
I used a simple view controller that will overlay over the current
view controller
So I have assumed you didn't present or push alertviewcontroller on current viewController, on that behave I suggest you the following solutions
In alertviewcontroller get the instance of topViewController by using this method
class func topViewController(base: UIViewController? = (UIApplication.shared.delegate as! AppDelegate).window?.rootViewController) -> UIViewController? {
if let nav = base as? UINavigationController {
return topViewController(base: nav.visibleViewController)
}
if let tab = base as? UITabBarController {
if let selected = tab.selectedViewController {
return topViewController(base: selected)
}
}
if let presented = base?.presentedViewController {
return topViewController(base: presented)
}
return base
}
then you can transfer your data from alertviewcontroller to topViewController.
Next solution is that you can pass values from alertviewcontroller to firstviewcontroller by using delegates.
I've been successfully able to implement a popviewcontroller, which contains a tableview of 4 cells. I want to close the popViewController when the cell is selected, but I cannot seem to do this. In my tableViewController, I have a method inside didSelectRowAtIndexPath, which calls this MasterViewController's method "updateToSelectedTab". I am able to print out the indexpath of the selected cell here, but I cannot remove the popViewController when the cell is selected. How do I do this?
//Here is our MasterViewController class
#IBAction func pop(sender: AnyObject) {
let contentView = UIStoryboard(name: "Main", bundle: nil).instantiateViewControllerWithIdentifier("popViewController") as! PopViewController
contentView.modalPresentationStyle = UIModalPresentationStyle.Popover
contentView.preferredContentSize = CGSizeMake(aButton.frame.width, 240.0)
let popoverMenuViewController = contentView.popoverPresentationController!
popoverMenuViewController.delegate = self
popoverMenuViewController.permittedArrowDirections = UIPopoverArrowDirection.Any
popoverMenuViewController.sourceView = view
popoverMenuViewController.permittedArrowDirections = UIPopoverArrowDirection(rawValue:0)
popoverMenuViewController.sourceRect = CGRectMake(aButton.frame.origin.x, aButton.frame.origin.y - 60, aButton.frame.width, aButton.frame.height)
presentViewController(contentView, animated: true, completion: nil)
}
func adaptivePresentationStyleForPresentationController(controller: UIPresentationController) -> UIModalPresentationStyle {
return UIModalPresentationStyle.None
}
//This method is called from didSelectRowAtIndexPath of the View Controller that handles the UIPopoverPresentationController
func updateToSelectedTab(tab: Int){
print("Current tab \(tab)")
//Need to dismiss controller here
}
All I had to do was add this code in select row at index path delegate method:
dismissViewControllerAnimated(true, completion: nil)
I have a view in my xib file which contain buttons. i want to move to a ViewController when i will press the button (#IBAction). I have used below code
let storyBoard : UIStoryboard = UIStoryboard(name: "Main", bundle:nil)
let nextViewController = storyBoard.instantiateViewControllerWithIdentifier("About") as! AboutViewController
self.presentViewController(nextViewController, animated: true, completion: nil)
I am getting the error "Value of type 'SlideMenuView' has no member 'presentViewController'.
because my class is a UIView type :
class SlideMenuView: UIView {
}
so how can I navigate to other view controller.
That is beacuase the class you are trying to present from is a UIView and not a UIViewController. It has no Present method.
I'm guessing your view (SlideMenuView) is embedded inside a viewcontroller. what you need to do is implement a delegate, and inform your containing viewController to present next Viewcontroller.
code below:
#protocol SlideMenuViewDelegate: class {
func slideMenuViewAboutButtonClicked(menuView: SlideMenuView)
class SlideMenuView: UIView {
weak var delegate: SlideMenuViewDelegate?
#IBAction func aboutButtonClicked(sender: AnyObject) {
self.delegate?.slideMenuViewAboutButtonClicked(self)
}
now, in your viewController, implement this delegate method:
func slideMenuViewAboutButtonClicked(menuView: SlideMenuView) {
let storyBoard : UIStoryboard = UIStoryboard(name: "Main", bundle:nil)
let nextViewController = storyBoard.instantiateViewControllerWithIdentifier("About") as! AboutViewController
self.presentViewController(nextViewController, animated: true, completion: nil)
}
Also, dont forget to assign the sliderMenuView object the viewcontroller as a delegate.
something like:
self.sliderMenuView.delegate = self // (self == the containing viewController
I did it in a different way. In class file
class SlideMenuView: UIView {
var navigationController: UINavigationController? // Declare a navigation controller variable
// And create a method which take a navigation controller
func prepareScreen(navController: UINavigationController)-> UIView {
navigationController = navController
let nibView = NSBundle.mainBundle().loadNibNamed("SlideMenuView", owner: self, options: nil)[0] as! UIView
self.addSubview(nibView)
return nibView
}
// In Button action
#IBAction func btnAction(sender: UIButton) {
var storyBoard = UIStoryboard(name: "Main", bundle: nil)
let nextViewController = storyBoard!.instantiateViewControllerWithIdentifier("NextViewController") as! UIViewController
navigationController?.pushViewController(nextViewController, animated: true)
}
}
// For calling from UIViewController
slideBarMenuIstance.prepareScreen(self.navigationController!)
I am using the below code to display MyEditViewController but all the field outlets in MyEditViewController are nil because of this line let editController = MyEditViewController(). Here I am creating new instance. So outlets are nil now. Is there any other way to call the edit controller without creating instance of it?
#IBAction func editMethod(sender: UILongPressGestureRecognizer) {
if sender.state == UIGestureRecognizerState.Began {
let cell = sender.view as! MyTableViewCell
let editController = MyEditViewController()
editController.sample= samples[cell.tag]
presentViewController(editController, animated: true, completion: nil)
}
}
To instantiate a new controller with your outlets you can use a xib
let editController = MyEditViewController(nibName: "MyEditViewController", bundle: nil)
Or you can attribute a StoryboardId to your controller and use with instantiateViewControllerWithIdentifier
let editController = storyboard?.instantiateViewControllerWithIdentifier("MyEditViewControllerStoryboardId") as! MyEditViewController
Or as a suggestion for your case, you can use a different approach using storyboard segues. Read more about it here
performSegueWithIdentifier("PresentMyEditController", sender: self)
You should initiate not a Class, you shoud initiate it as an ViewController from Storyboard. So in your case set an "Storyboard-ID" for your Controller.
And then use:
let editController = UIStoryboard(name: "Main", bundle: nil).instantiateViewControllerWithIdentifier("StoryboardID") as! MyEditViewController
So all outlets should work fine.