I've created UIAlertView that has 2 buttons positive button and negative button.
AlertView is viewcontroller as well.
I am opening AlertVC from Main viewController.
Here is my AlertVC
class AlertVC: UIViewController {
var transitioner : CAVTransitioner
#IBOutlet weak var alertPositiveBtn: IFOButton!
#IBOutlet weak var alertNegativeBtn: IFOButton!
override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
self.transitioner = CAVTransitioner()
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
self.modalPresentationStyle = .custom
self.transitioningDelegate = self.transitioner
}
convenience init() {
self.init(nibName:nil, bundle:nil)
}
required init?(coder: NSCoder) {
fatalError("NSCoding not supported")
}
#IBAction func postiveBtnPressed(_ sender: IFOButton) {
}
#IBAction func negativeBtnPressed(_ sender: IFOButton) {
}
#IBAction func closeBtnPressed(_ sender: UIButton) {
self.presentingViewController?.dismiss(animated: true, completion: nil)
}
}
What I want: I want MainViewController somehow detect which button pressed negative or positive.
Is anyone can tell me how could I do this?
UPDATE: after using delegate pattern
#IBAction func positiveBtnPressed(_ sender: IFOButton) {
delegate?.positiveBtnPressed(onAlertVC: self)
self.presentingViewController?.dismiss(animated: true, completion: nil)
}
#IBAction func negativeBtnPressed(_ sender: IFOButton) {
delegate?.negativeBtnPressed(onAlertVC: self)
self.presentingViewController?.dismiss(animated: true, completion: nil)
}
Here is what I did on MainViewController
class MainViewController: UIViewController, AlertVCDelegate
and here is my functions
func positiveBtnPressed(onAlertVC: IFOAlertVC) {
print("Pos")
}
func negativeBtnPressed(onAlertVC: IFOAlertVC) {
print("Neg")}
It still not being called.
This is a textbook example of the delegate pattern.
Add a protocol to your AlertVC:
protocol AlertVCDelegate : class {
func positiveBtnPressed(onAlertVC: AlertVC)
func negativeBtnPressed(onAlertVC: AlertVC)
}
Then create a weak property in your AlertVC class and pass the button presses to it:
class AlertVC : UIViewController {
weak var delegate: AlertVCDelegate?
...
#IBAction func postiveBtnPressed(_ sender: IFOButton) {
delegate?.positiveBtnPressed(onAlertVC: self)
}
#IBAction func negativeBtnPressed(_ sender: IFOButton) {
delegate?.negativeBtnPressed(onAlertVC: self)
}
}
Implement the AlertVCDelegate protocol in your MainViewController, and set the delegate when you present the AlertVC from your MainViewController.
If you present the alert vc from a segue, use the prepare(for: sender:) method to set the MainViewController as the delegate.
You have to CTRL + Drag the button from your app Storyboard to their own method.
Your IFOButton should be an UIButton derived class (inheritance).
Even you only need one method to do that
#IBAction internal func handleButtonTap(_ sendder: UIButton) -> Void
{
if sender === alertPositiveBtn
{
// Do something positive here
}
else
{
// Do something negative here
}
}
Related
I have my main screen with only one button on it "Show next screen". When the second screen(VC) pops up it has 2 buttons (go back and toSelect button).
My goal is to when I show my second screen and select a button on it then go back to first. The button on my second screen will stay selected. How can I do that?
So basically I need to save my actions on the second screen so if I go back to it it will show everything I did.
What is the best way to do it?
Storyboard
The easiest way to achieve this using Delegate and protocol.
you should listen and save the changes of SecondViewController at FirstViewController using delegate methods.
And when you are presenting the secondViewController you will share the saved changes to secondViewController so that button can be selected on behalf of that information
Code -
class FirstViewController: UIViewController {
//secondViewController States
private var isButtonSelected = false
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
#IBAction func gotoSecondVCAction(_ sender: Any) {
let storyBoard = UIStoryboard(name: "Main", bundle: nil)
guard let secondVC = storyBoard.instantiateViewController(withIdentifier: "SecondViewController") as? SecondViewController else { return }
secondVC.isButtonSelected = isButtonSelected
secondVC.delegate = self
self.present(secondVC, animated: true, completion: nil)
}
}
extension FirstViewController: ButtonSelectionProtocol {
func button(isSelected: Bool) {
isButtonSelected = isSelected
}
}
and for secondViewController
protocol ButtonSelectionProtocol {
func button(isSelected:Bool)
}
class SecondViewController: UIViewController {
var isButtonSelected : Bool = false
var delegate:ButtonSelectionProtocol?
#IBOutlet weak var selectButton: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
if isButtonSelected {
selectButton.tintColor = .red
selectButton.setTitle("Selected", for: .normal)
}else{
selectButton.tintColor = .green
selectButton.setTitle("Select Me", for: .normal)
}
}
#IBAction func gobackAction(_ sender: Any) {
self.dismiss(animated: true, completion: nil)
}
#IBAction func selectAction(_ sender: Any) {
self.dismiss(animated: true, completion: nil)
isButtonSelected.toggle()
delegate?.button(isSelected: isButtonSelected)
}
}
I have 2 UIViewControllers and I try to hide an UILabel from the second UIViewController using Notifications and Observer.
Is the first time when I use this design pattern and I'm a little bit confused. What I'm doing wrong ?
I want to specify that I'm getting the message from that print for the first time only when I click the back button from the second ViewController.
And after that I'm getting the message normal when I click Go Next but the UILabel is not hidden or colour changed.
Here is my code for first UIViewController:
class ReviewPhotosVC: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.post(name: Notification.Name("NotificationOfReviewMode"), object: nil)
}
#IBAction func goNextTapped(_ sender: UIButton) {
let fullscreenVC = storyboard?.instantiateViewController(withIdentifier: "FullscreenPhoto") as! FullscreenPhotoVC
self.present(fullscreenVC, animated: true, completion: nil)
}
}
Here is my code for second UIViewController:
class FullscreenPhotoVC: UIViewController {
#IBOutlet weak var customLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self,
selector: #selector(hideCustomLabel),
name: Notification.Name("NotificationOfReviewMode"),
object: nil)
}
#IBAction func goBackTapped(_ sender: UIButton) {
let reviewPhotosVC = storyboard?.instantiateViewController(withIdentifier: "ReviewPhotos") as! ReviewPhotosVC
self.present(reviewPhotosVC, animated: true, completion: nil)
}
#objc func hideCustomLabel(){
customLabel.isHidden = true
customLabel.textColor = .red
print("My func was executed.")
}
}
Here is my Storyboard:
Thanks if you read this.
The problem is that you are posting the notification before the next controller is initialised and has started observing. Also, there is no need for the notification you can do it directly. In this case I have used an extra variable shouldHideLabel as you cannot call the function hideCustomLabel() directly because this will lead to crash as the outlets are only initialised after view is loaded.
class ReviewPhotosVC: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
//NotificationCenter.default.post(name: Notification.Name("NotificationOfReviewMode"), object: nil)
}
#IBAction func goNextTapped(_ sender: UIButton) {
let fullscreenVC = storyboard?.instantiateViewController(withIdentifier: "FullscreenPhoto") as! FullscreenPhotoVC
fullscreenVC.shouldHideLabel = true
self.present(fullscreenVC, animated: true, completion: nil)
}
}
class FullscreenPhotoVC: UIViewController {
var shouldHideLabel = false
#IBOutlet weak var customLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
if shouldHideLabel {
hideCustomLabel()
}
/*
NotificationCenter.default.addObserver(self,
selector: #selector(hideCustomLabel),
name: Notification.Name("NotificationOfReviewMode"),
object: nil)
*/
}
#IBAction func goBackTapped(_ sender: UIButton) {
self.dismiss(animated: true, completion: nil)
}
#objc func hideCustomLabel() {
customLabel.isHidden = true
customLabel.textColor = .red
print("My func was executed.")
}
}
I am exploring protocol and got a probelem
ViewController1.swift
protocol filterApplied {
func appliedFiiler(isApplied: Bool)
}
class : UIViewController{
var delegate : filterApplied?
// on some button action
delegate?.appliedFiiler(isApplied: true)
}
ViewController2.swift
class ViewController2 : UIViewController,filterApplied {
func appliedFiiler(isApplied: Bool) {
if isApplied{
filterButton.imageView?.image = UIImage(named: "filter_applied")
}
}
}
now I know that this will not do anything
as I haven't assigned the delegate to self.
how and where would i Do that so the appliedFilterFunction in 2 swift file is working?
You have to connect delegate to self of ViewController2.
protocol FilterApplied {
func appliedFiiler(isApplied: Bool)
}
Class ViewController1: UIViewController {
var delegate: FilterApplied?
#IBaction func onClick() {
self.delegate?.appliedFiiler(isApplied: true)
}
In Viewcontroller2 you have to connect delegate to self. By either navigating controller or presenting controller.In this case i have connected on presenting controller.
Class ViewController2: UIViewController, FilterApplied {
func onPresent() {
let vc = UIStoryboard.init(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "ViewController1") as! ViewController1
vc.delegate = self
self.present(vc, animated: false, completion: nil)
}
func appliedFilter(isApplied: Bool) {
if isApplied{
filterButton.imageView?.image = UIImage(named: "filter_applied")
}
}
}
You may want something like this
//ViewController1.swift
// definition
protocol FilterApplied: class {
var filterButton: UIButton! { get set }
func appliedFilter(isApplied: Bool)
}
// defaults protocol behaviour
extension FilterApplied {
//
func appliedFilter(isApplied: Bool) {
if isApplied{
filterButton.imageView?.image = UIImage(named: "filter_applied")
}
}
}
//ViewController2.swift
class ViewController2: UIViewController, FilterApplied {
#IBOutlet var filterButton: UIButton! {
didSet {
// used defaults
appliedFilter(isApplied: true)
}
}
// overrides defaults
func appliedFilter(isApplied: Bool) {
}
}
In the end you can choose to use default behaviour or add a new one.
I have a requirement where I have to call a first view controller function from second view controller on a button tap.
class FirstViewController: UIViewController {
#IBAction func firstButtonPressed(_ sender: Any) {
// Doing ABC
}
#IBAction func showSecondVC_ sender: Any) {
// showingSecondVC
}
}
class secondViewController: UIViewController {
#IBAction func SecondButtonPressed(_ sender: Any)
// Dismiss second vc & call First View controller method so that it does ABC.
}
My first question is can we initiate First VC IBAction directly from second VC ? Is it possible ?
I am thinking to do following
class FirstViewController: UIViewController {
#IBAction func firstButtonPressed(_ sender: Any) {
// call DoABC
}
func DoABC {
// Doing ABC
}
}
class secondViewController: UIViewController {
#IBAction func SecondButtonPressed(_ sender: Any)
// Dismiss second vc
// Call Firstvc.DoABC ?? How to do this ??
}
How to call the first vc method from the second vc ??
You have a few options here:
Split out the logic, call the same code from each view controller
Use a closure callback
Use the delegate pattern as a method of calling back
Option 1 - Split out the logic:
class FirstViewController: UIViewController {
let abcPerformer = ABCPerformer()
#IBAction func firstButtonPressed(_ sender: Any) {
abcPerformer.doABC()
}
#IBAction func showSecondVC_ sender: Any) {
// showingSecondVC
}
}
class SecondViewController: UIViewController {
let abcPerformer = ABCPerformer()
#IBAction func SecondButtonPressed(_ sender: Any) {
// Dismiss second vc & call First View controller method so that it does ABC.
abcPerformer.doABC()
}
}
struct ABCPerformer {
func doABC() {
// do ABC
}
}
Option 2 - Create a callback:
class FirstViewController: UIViewController {
#IBAction func firstButtonPressed(_ sender: Any) {
doABC()
}
#IBAction func showSecondVC_ sender: Any) {
// showingSecondVC
secondVC.doABC = doABC
}
func doABC() {
// do ABC
}
}
class SecondViewController: UIViewController {
var doABC: (() -> Void)?
#IBAction func SecondButtonPressed(_ sender: Any) {
// Dismiss second vc & call First View controller method so that it does ABC.
doABC?()
}
}
Option 3 - Use a delegate:
protocol ABCProtocol {
func doABC()
}
class FirstViewController: UIViewController, ABCProtocol {
#IBAction func firstButtonPressed(_ sender: Any) {
doABC()
}
#IBAction func showSecondVC_ sender: Any) {
// showingSecondVC
secondVC.delegate = self
}
func doABC() {
// do ABC
}
}
class SecondViewController: UIViewController {
weak var delegate: ABCProtocol?
#IBAction func SecondButtonPressed(_ sender: Any) {
// Dismiss second vc & call First View controller method so that it does ABC.
delegate?.doABC()
}
}
There is probably more options too, but these should give you enough choice to make a decision
Create a protocol, say, SecondViewControllerDelegate.
Add a method signature to that protocol, something like secondViewControllerDidPressButton.
Add a var to secondViewController: var delegate: SecondViewControllerDelegate
Update firstViewController to implement that protocol.
In prepareForSegue of firstViewController, assign firstViewController as the delegate for the secondViewController that is about to be presented.
Update secondViewController to call self.delegate.secondViewControllerDidPressButton when the button is pressed.
You can Use custom delegate for that Like below and add function "presentPage" wherever you want to call.
protocol MainDelegate {
func presentPage(page : Int)
}
To present your second view controller from First, You can use push or present transition.
#IBAction func firstButtonPressed(_ sender: Any) {
// call DoABC
//presenting VC
let secondVC = SecondViewController() //change this to your class name
self.presentViewController(secondVC, animated: true, completion: nil)
//for push :
navigationController?.pushViewController(SecondViewController, animated: true)
}
You can use pop/dismiss VC accordingly in your second view to get back first view.
class secondViewController: UIViewController {
#IBAction func SecondButtonPressed(_ sender: Any)
// Dismiss second vc // Call Firstvc.DoABC ?? How to do this ??
//if you used Present in first step then use dismiss
self.dismissViewControllerAnimated(false, completion: nil)
//if you used push in first step then use pop
self.navigationController?.popViewController(animated: true) }
There are two ViewController in my app, ViewController and ViewController2
In ViewController, a button set Present Modally segue to "ViewController2"
And ViewController override viewWillAppear
override func viewWillAppear(_ animated: Bool) {
super.viewDidAppear(animated)
print("will appear")
}
In ViewController2, a button to go back
#IBAction func close(_ sender: Any) {
self.dismiss(animated: true, completion: nil)
}
Now it still can trigger viewWillAppear then I go back to ViewController from ViewController2
If I change ViewController2's presentation from Full Screen to Over Current Context, viewWillAppear will not be triggered
How can I trigger some code when go back?
You can do it without giving up storyboard segues, but you nevertheless had to setup will/did Disappear handler in ViewCOntroller2:
class ViewController: UIViewController {
...
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let destination = segue.destination as? ViewController2 {
(segue.destination as? ViewController2).onViewWillDisappear = {
//Your code
}
}
}
}
class ViewController2: UIViewController {
var onViewWillDisappear: (()->())?
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
onViewWillDisappear?()
}
...
}
There are several ways to handle this operation. Here is one, which I used to use.
// ViewController1
class ViewController1: UIViewController {
#IBAction func presentOverCurrentContext(button: Button) {
let vc2 = // instantiate ViewController2
vc2.modalPresentationStyle = .overFullScreen
vc2.presentingVC = self // use this variable 'presentingVC' to connect both view controllers
self.present(vc2, animated: true)
}
}
// ViewController2
class ViewController2: UIViewController {
var presentingVC: UIViewController? // use this variable to connect both view controllers
#IBAction func close(button: Button) {
// handle operation here
presentingVC?.viewWillAppear(true)
self.dismiss(animated: true, completion: {
// or here
// presentingVC?.viewWillAppear(true)
})
}
}
You can also use, your own method to reload view/viewcontroller, but viewWillAppear is common accessible method for all view controllers (as part of super class life cycle) hence you may not need to specify custom type of view controller for presentingVC
While the answers so far provided do work I think it's a good idea to show how to do it using a protocol and delegate as that's a clean implementation which then also allows for further functionality to be added with minimal effort.
So set up a protocol like this:
protocol SecondViewControllerProtocol: class {
func closed(controller: SecondViewController)
}
Setup the second view controller like this:
class SecondViewController {
public weak var delegate: SecondViewControllerProtocol?
#IBAction func close(_ sender: Any) {
self.dismiss(animated: true, completion: nil)
self.delegate?.close(controller: self)
}
}
Setup the first view controller like this:
class FirstViewController: SecondViewControllerProtocol {
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "SecondViewControllerID",
let secondViewController = segue.destination as? SecondViewController {
secondViewController.delegate = self
}
}
func closed(controller: SecondViewController) {
// Any code you want to execute when the second view controller is dismissed
}
}
Implementing it like this does what the original request was and allows for extra methods to be put in the protocol so that the FirstViewController can respond to other actions in the SecondViewController.
Note:
You might want to move the delegate method call into the closure of the dismiss handler so that you know the method is not called until the SecondViewController is actually gone (in case you try to present another view which would fail). If that's the case you could do this:
#IBAction func close(_ sender: Any) {
self.dismiss(animated: true) {
self.delegate?.close(controller: self)
}
}
In fact you could have a will and did methods and call them like this:
#IBAction func close(_ sender: Any) {
self.delegate?.willClose(controller: self)
self.dismiss(animated: true) {
self.delegate?.didClose(controller: self)
}
}
Which would allow you to do something immediately while the second controller is animating away and then know when it has actually gone.
Best/Clean way to handle this scenario to use call back handler.
Example Code
typealias CloseActionHandler = ()-> Void
class TestController: UIViewController {
var closeActionHandler: CloseActionHandler?
func close(_ handler:#escaping CloseActionHandler) {
self.closeActionHandler = handler
}
#IBAction func closeButtonTapped(_ sender: Any) {
self.dismiss(animated: true, completion: nil)
self.closeActionHandler?()
}
}
class ViewController: UIViewController {
func loadTestController(viewController: TestController) {
viewController.close {
//will be called when user will tap on close button
}
}
}