How can a prepare a segue properly from inside a UIView NOT UIViewController
My UIViewController has a container view and inside that container view has a button.
class myInnerView: UIView {
...
func myButton(gesture: UIGestureRecognizer){
//calling a perform segue from another UIViewController does not recognize the SegueID
//ViewController().self.showProfile(self.id) -- DOES NOT WORK
}
}
class ViewController: UIViewController {
...
func showProfile(id: String){
...
self.performSegueWithIdentifier("toViewProfile", sender: self)
}
}
Your view should not handle a button tap. That should be handled by the controller. (This is why it is called "controller", while the UIView is called "view").
MVC as described by Apple.
If the button is a subview of your view controller's view, you should be able to drag an IBAction onTouchUpInside from the button to your view controller. You can then initiate the segue from that method.
One of the solution is to add UITapGestureRecognizer to your button but from inside the UIViewController :
class ViewController: UIViewController {
var myInnerView = myInnerView()
override func viewDidLoad() {
let tap = UITapGestureRecognizer(target: self, action: #selector(ViewController.handleTapGesture))
self.myInnerView.myButton.addGestureRecognizer(tap)
}
#objc func handleTapGesture(){
performSegue(withIdentifier: "toViewProfile", sender: nil)
}
}
class myInnerView: UIView {
// make outlet of your button so you can add the tapGestureRecognizer from your ViewController and thats all
#IBOutlet public weak var myButton: UIButton!
}
You need to tap gesture for view to navigate
let customView = myInnerView()
let gestureRec = UITapGestureRecognizer(target: self, action: #selector (self.someAction (_:)))
myView.addGestureRecognizer(customView)
func someAction(_ sender:UITapGestureRecognizer){
let controller = storyboard?.instantiateViewController(withIdentifier: "someViewController")
self.present(controller!, animated: true, completion: nil)
// swift 2
// self.presentViewController(controller, animated: true, completion: nil)
}
Related
I have programmed UIBarButtonItem so that it does some action before switching to a previous view. I was wondering how do I get the viewcontroller of that transitioning scene from my UIBarButtonItem?
So, scene 1 -> scene 2 (current scene) -> scene 1 (after clicking the UIBarButtonItem button)
I've tried to pass the previous scene variables (that I need) to the current scene to perform action on (sense I don't think the transitioning scene is instantiating a new view, but that doesn't work
override func viewDidLoad() {
super.viewDidLoad()
loadTuple()
let addButton: UIBarButtonItem = UIBarButtonItem(title: "Save", style: .plain, target: self, action: #selector(saveExercise(_: )))
self.navigationItem.setRightBarButton(addButton, animated: true)
}
#objc func saveExercise(_ sender: UIBarButtonItem) {
self.addNewTupleToDB(element: self.getNewTuple())
self.saveData()
debugPrint("saveExercise")
self.exerciseVCTableView?.reloadData() // tried to pass the table view from the previous scene to call here
self.navigationController?.popViewController(animated: true)
// Want to save reload the table data of the scene this button transitions to
}```
You may use delegate pattern for solving this. Delegate pattern is something, to delegate some work to other and return to the work after delegation is done.
Suppose ViewController1 has UIBarButton , goes to ViewController2, some function done and return to ViewController1
let us take a protocol
protocol MyProtocol {
func myFunction()
}
then in ViewController2 add a delegate method. Assuming in ViewController2, you have to call a method doMyWork and some work will be done here, then you have to pop.
class ViewController2 {
var delegate : MyProtocol?
override func viewDidLoad() {
super.viewDidLoad()
doMyWork()
}
func doMyWork() {
// my works
delegate?.myFunction()
self.navigationController.popViewController()
}
}
now the viewController1 have to receive the delegate work has done.
in viewController1, in barButtonItem
class ViewController1 {
#objc func barButton(_sender : UIBarButton) {
let viewController = ViewController2()
viewController.delegate = self
self.naviagtionController.pushViewController(viewController, animated : true)
}
}
now you have to implement protocol method
extension ViewController1 : MyProtocol {
func myFunction() {
self.tableView.reloadData()
}
}
I have two UIViewController, when I click a button, it goes from the first view controller to the second one. And before that, I animated a UIView to move to another place. After dismissing the second View Controller, I want to move the UIView in the first view controller back to where it originally was. However, when I call a function from the second View Controller to animate the UIview in the first view controller after dismissing the second one, It could not get the UIView's properties, and cannot do anything with it. I think because the first UIViewController is not loaded yet. Is that the problem? And How should I solve this?
There are two solutions you can either use swift closures
class FirstViewController: UIViewController {
#IBAction func start(_ sender: Any) {
guard let secondController = self.storyboard?.instantiateViewController(withIdentifier: "SecondController") as? SecondController else { return }
secondController.callbackClosure = { [weak self] in
print("Do your stuff")
}
self.navigationController?.pushViewController(secondController, animated: true)
}
}
//----------------------------
class SecondController: UIViewController {
var callbackClosure: ((Void) -> Void)?
override func viewWillDisappear(_ animated: Bool) {
callbackClosure?()
}
}
or you can use protocols
class FirstViewController: UIViewController {
#IBAction func start(_ sender: Any) {
guard let secondController = self.storyboard?.instantiateViewController(withIdentifier: "SecondController") as? SecondController else { return }
secondController.delegate = self
self.navigationController?.pushViewController(secondController, animated: true)
}
}
extension ViewController : ViewControllerSecDelegate {
func didBackButtonPressed(){
print("Do your stuff")
}
}
//--------------------------
protocol SecondControllerDelegate : NSObjectProtocol {
func didBackButtonPressed()
}
class SecondController: UIViewController {
weak var delegate: SecondControllerDelegate?
override func viewWillDisappear(_ animated: Bool) {
delegate?.didBackButtonPressed()
}
}
You can try to use a closure. Something like this:
class FirstViewController: UIViewController {
#IBOutlet weak var nextControllerButton: UIButton!
private let animatableView: UIView = UIView()
private func methodsForSomeAnimation() {
/*
perform some animation with 'animatableView'
*/
}
#IBAction func nextControllerButtonAction() {
// you can choose any other way to initialize controller :)
let storyboard = UIStoryboard(name: "Main", bundle: nil)
guard let secondController = storyboard.instantiateViewController(withIdentifier: "SecondViewController") as? SecondViewController else { return }
secondController.callbackClosure = { [weak self] in
self?.methodsForSomeAnimation()
}
present(secondController, animated: true, completion: nil)
}
}
class SecondViewController: UIViewController {
#IBOutlet weak var dismissButton: UIButton!
var callbackClosure: ((Void) -> Void)?
#IBAction func dismissButtonAction() {
callbackClosure?()
dismiss(animated: true, completion: nil)
/*
or you call 'callbackClosure' in dismiss completion
dismiss(animated: true) { [weak self] in
self?.callbackClosure?()
}
*/
}
}
When you present your second view controller you can pass an instance of the first view controller.
The second VC could hold an instance of the first VC like such:
weak var firstViewController: NameOfController?
then when your presenting the second VC make sure you set the value so it's not nil like so:
firstViewController = self
After you've done this you'll be able to access that viewControllers functions.
iOS 11.x Swift 4.0
In calling VC you put this code ...
private struct Constants {
static let ScannerViewController = "Scan VC"
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == Constants.ScannerViewController {
let svc = destination as? ScannerViewController
svc?.firstViewController = self
}
}
Where you have named the segue in my case "Scan VC", this is what it looks like in Xcode panel.
Now in scan VC we got this just under the class declaration
weak var firstViewController: HiddingViewController?
Now later in your code, when your ready to return I simply set my concerned variables in my firstViewController like this ...
self.firstViewController?.globalUUID = code
Which I have setup in the HiddingViewController like this ...
var globalUUID: String? {
didSet {
startScanning()
}
}
So basically when I close the scanning VC I set the variable globalUUID which in term starts the scanning method here.
When you are saying it could not get the UIView's properties it's because you put it as private ? Why you don't replace your UIView in the first controller when it disappears before to go to your secondViewController. I think it's a case where you have to clean up your view controller state before to go further to your second view controller.
Check IOS lifecycle methods : viewWillDisappear or viewDidDisappear through Apple documentation and just do your animation in one of these methods.
Very simple solution actually... Just put your animation in the viewDidAppear method. This method is called every time the view loads.
class firstViewController: UIViewController {
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
// insert animation here to run when FirstViewController appears...
}
}
In one of my iOS project, I have added a subview for Filter ViewController(subview) on my Feed ViewController(main view) programmatically.
There are few button on Filter ViewController to select price, city etc.
The outlets are connected but when I am trying to shoot button action, it is not working.
I have also enabled the isUserInteractionEnabled but still it is not working.
Acc. to me, this is something related to subview on a view !! but to resolve this. Can you suggest me how to shoot a button action of subview it happen ?
class FilterViewController: BaseUIViewController{
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
cityButton.isUserInteractionEnabled = true
}
#IBAction func selectCity(_ sender: Any)
{
print("selectCity action")
}
}
Add your FilterViewController as subview to your ViewController using the code below
filterViewController.willMove(toParentViewController: self)
self.view.addSubview(filterViewController.view)
self.addChildViewController(filterViewController)
filterViewController.didMove(toParentViewController: self)
#SandeepBhandari solution is still valid for similar issues.
These UIViewController extensions will be useful.
extension UIViewController {
func add(asChildViewController viewController: UIViewController, containerView: UIView) {
addChild(viewController)
containerView.addSubview(viewController.view)
viewController.view.frame = containerView.bounds
viewController.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
viewController.didMove(toParent: self)
}
func remove(asChildViewController viewController: UIViewController) {
viewController.willMove(toParent: nil)
viewController.view.removeFromSuperview()
viewController.removeFromParent()
}
}
I'd like to have a close button on each view controller that appears in the navigation stack. I've read here that I need to create an object that is a uinavigationdelegate, I think this object will have a method like didTapCloseButton?
Questions:
Should I create a protocol and make everything confirm to it, i.e.:
protocol CustomDelegate: UINavigationControllerDelegate {
func didTapCloseButton()
}
public class ViewController: CustomDelegate {
func didTapCloseButton() {
//not sure what goes in here?
}
}
How do I get the close button to show on the navigation bars of every view?
When the user clicks the close button, how do I get that to dismiss every view on that stack?
Thanks for your help!
Here a simple solution. Create UINavigationController subclass and override pushViewController method.
class NavigationController: UINavigationController {
override func pushViewController(_ viewController: UIViewController, animated: Bool) {
super.pushViewController(viewController, animated: animated)
let closeBarButtonItem = UIBarButtonItem(
title: "Close",
style: .done,
target: self,
action: #selector(self.popViewController(animated:)))
viewController.navigationItem.rightBarButtonItem = closeBarButtonItem
}
}
Not sure if this is what you intended but you can do:
protocol CustomDelegate: UINavigationControllerDelegate {
func didTapCloseButton()
}
extension CustomDelegate where Self : UIViewController{
func didTapCloseButton(){
// write your default implementation for all classes
}
}
now for every UIViewController class you have you can just do :
class someViewController: CustomDelegate{
#IBAction buttonClicked (sender: UIButton){
didTapCloseButton()
}
}
Create a UIBarButtonItem which name is nextVc on my navigationBar, and set its action by nextVc.action = #selector(self.gotoVC4), but it does not work.
my code is below:
class ViewController3: UIViewController {
#IBOutlet weak var nextVc: UIBarButtonItem!
override func viewDidLoad() {
super.viewDidLoad()
nextVc.action = #selector(self.gotoVC4)
}
func gotoVC4() -> Void {
print("go to vc4")
let vc4 = ViewController4()
self.navigationController!.pushViewController(vc4, animated: true)
}
}
and the image of storyboard is here:
Since you have a storyboard, simply ctrl-drag from the bar button to the "View Controller 4" scene to create a segue. No code needed.
You can omit self in selector expression
Method should be dynamic or #objc
override func viewDidLoad() {
super.viewDidLoad()
nextVc.action = #selector(gotoVC4)
}
dynamic func gotoVC4() -> Void {
print("go to vc4")
let vc4 = ViewController4()
self.navigationController!.pushViewController(vc4, animated: true)
}