I have a viewController that presents a 'CollectionViewController', via an action button.
#objc func addPhoto() {
let layout = UICollectionViewFlowLayout()
let photoController = UINavigationController(rootViewController: PhotoController(collectionViewLayout: layout))
present(photoController, animated: true, completion: nil)
}
Inside the 'PhotoController', I'm trying to pass data back from this controller to the original viewController using the standard protocol procedure.
protocol PhotoControllerDelegate {
func store(image: UIImage)
}
var delegate: PhotoControllerDelegate?
#objc func handleSave() {
if let image = headerImage {
delegate?.store(image: image)
}
///Dimiss viewController
dismiss(animated: true, completion: nil)
}
Now back inside the original viewController, I have re-instantiated the dismissed PhotoController:
let photoController = PhotoController()
and assigned the delegate inside viewDidLoad()
photoController.delegate = self
then provided the delegate method inside the original viewController:
func store(image: UIImage) {
selectedPhotoImage.image = image
}
Issue is: this method is not even being called.
I usually don't have any issues however this one has stumped me.
Any help would be great.
P.S. My viewControllers have a lot of extra junk inside, so couldn't post the complete code.
Make sure to add delegate as self to receive protocol event in that viewController
#objc func addPhoto() {
let layout = UICollectionViewFlowLayout()
let objVC = PhotoController(collectionViewLayout: layout)
objVC.delegate = self
let photoController = UINavigationController(rootViewController: objVC)
present(photoController, animated: true, completion: nil)
}
Don't forget to add delegate in same viewController. Use extension to make it clean code.
extension SourceController: PhotoControllerDelegate {
func store(image: UIImage) {
selectedPhotoImage.image = image
}
}
Define protocol like below in PhotoController
public protocol PhotoControllerDelegate {
func store(image: UIImage)
}
Related
Is there a way to present an UIImagePickerController from inside a modal view?
In my case I have a class called MakeWishView which is a UIView and the user should be able to pick an image if he taps on a button inside the MakeWishView. My problem right now is that I can not call present because MakeWishView is not a ViewController but a UIView.
This is what I would like to call:
#objc private func wishImageButtonTapped(){
showImagePickerController()
print("wishImageButtonTapped")
}
// image picker
extension MakeWishView: UIImagePickerControllerDelegate, UINavigationControllerDelegate {
#objc func showImagePickerController(){
let imagePickerController = UIImagePickerController()
imagePickerController.delegate = self
present(imagePickerController, animated: true, completion: nil) // -> error
}
}
Add a delegate to the presenting vc like
class MakeWishView:UIView {
weak var delegate:VCName?
or
let root = (UIApplication.shared.delegate as! AppDelegate).window!.rootViewController
root.present(imagePickerController, animated: true, completion: nil)
I have 2 UIViewControllers, ViewController, SecondViewController. I defined delegate function in VC, and using in Second VC. But delegate functions not calling in Second VC.
This is mu first VC code
import UIKit
//Step1:
protocol testDelegate {
func testFunction(string1: String, string2:String)
func math(a:Int, b:Int)
}
class ViewController: UIViewController {
//Step2:
var delegateVariable: testDelegate?
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
#IBAction func moveToSecondVC(_ sender: Any) {
let nav = self.storyboard?.instantiateViewController(withIdentifier: "SVC") as! SecondViewController
//Step3:
delegateVariable?.testFunction(string1: "String1", string2: "String2")
delegateVariable?.math(a:30, b:10)
self.navigationController?.pushViewController(nav, animated: true)
}
}
My second VC code
import UIKit
//Step4:
class SecondViewController: UIViewController , testDelegate {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
//Step5:
let svc = ViewController()
svc.delegateVariable = self
}
#IBAction func btn(_ sender: Any) {
//Step5:
let svc = ViewController()
svc.delegateVariable = self
}
//Step6:
func testFunction(string1: String, string2: String) {
print(string1+string2)
}
func math(a:Int, b:Int) {
print(a+b)
print(a-b)
print(a*b)
}
}
Here i'm just passing small amount of data for practice, but can any one please suggest some high level delegate example tutorial links for me.
This is why nothing is happening...
let svc = ViewController()
svc.delegateVariable = self
You are creating a NEW ViewController, not using the one that is actually in use.
It does not look like you are using the delegate pattern properly. Your ViewController should not be calling code on other view controllers.
SecondViewController should "do stuff" and then let ViewController know what it has done.
For the Math function you could just use a new class (not a view controller) and create and use this as needed. You do not need a ViewController for this.
An example of using a delegate might be something like:
protocol CreateProfileDelegate: class {
func didCreateProfile(profile: Profile?)
func didCancelCreateProfile()
}
class ViewController: UIViewController {
func showCreateProfile() {
let vc = CreateProfileViewController()
vc.delegate = self
present(vc, animated: true)
}
}
extension ViewController: CreateProfileDelegate {
func didCreateProfile(profile: Profile?) {
// show the profile?
}
func didCancelCreateProfile() {
// show an alert maybe?
}
}
This way the SecondViewController (CreateProfileViewController) basically tells the first that something has happened so that it can react to it.
in SecondViewController you are setting....
let svc = ViewController()
svc.delegateVariable = self
That just create an object of ViewController() class and then you set the delegate. So when the obj. of the scope is finished then the memory of the object will be increased automatically.
The flow should like below....
Create an object of the Viewcontroller in SecondViewController and set the delegate
let vc = self.storyboard?.instantiateViewController(withIdentifier: "ViewController") as! ViewController
vc.delegateVariable = self
Then push the view controller in to the navigation stack.
self.navigationController?.pushViewController(svc, animated: true)
Implement the delegate method of testDelegate in SecondViewController
func testFunction(string1: String, string2: String) {
print(string1+string2)
}
func math(a:Int, b:Int) {
}
EDIT
The final code of the SecondViewController Will be...
import UIKit
class SecondViewController: UIViewController , testDelegate {
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func btn(_ sender: Any) {
let vc = self.storyboard?.instantiateViewController(withIdentifier: "ViewController") as! ViewController
vc.delegateVariable = self
self.navigationController?.pushViewController(svc, animated: true)
}
//MARK:- TestDelegate Methods
func testFunction(string1: String, string2: String) {
print(string1+string2)
}
func math(a:Int, b:Int) {
print(a+b)
print(a-b)
print(a*b)
}
}
any help is appreciated.
I am new to Ios development and I am trying to change a label text which is located in my first initial view controller. I want this text to change as I press a button in the second view controller which is segued to the initial one.
here is my first view controller
import UIKit
protocol gameModeDelegate {
func didTapChoice(test:String)
}
class ViewController2: UIViewController {
var selectionDelegate:gameModeDelegate!
#IBAction func chooseButton(_ sender: Any) {
selectionDelegate.didTapChoice(test: "TEST")
let selectVC = storyboard?.instantiateViewController(withIdentifier: "VC1") as! ViewController
present(selectVC,animated: true,completion: nil)
}
override func viewDidLoad() {
super.viewDidLoad()
}
}
here is what i have done in the second where the label is
override func viewDidLoad() {
super.viewDidLoad()
let selectVC2 = storyboard?.instantiateViewController(withIdentifier: "VC1") as! ViewController2
selectVC2.selectionDelegate = self
winningLabel.isHidden = true
winningLabel.center = CGPoint(x: winningLabel.center.x, y: winningLabel.center.y - 400)
playAgainoutlet.isHidden = true
playAgainoutlet.center = CGPoint(x: playAgainoutlet.center.x, y: playAgainoutlet.center.y + 400)
}
extension ViewController: gameModeDelegate{
func didTapChoice(test: String) {
CommunicationLabel.text = test
}
}
I tried these two methods so far and i keep getting this error.
Thread 1: Fatal error: Unexpectedly found nil while unwrapping an Optional value
You should not use this approach to achieve the result, you may use two different approaches to achieve the same result.
1- Use a delegate protocol approach:
in secondViewController you should declare a protocol like this
protocol applySelecction {
func applyText(text: String)
}
and in the class declare a variable like this.
var delegate: apply selection?
then in the button action
#IBAction func saveButtom(sender: UIButton){
//print(selected)
delegate?.applySelection(text: text) //text is the value select from UILAbel o the option the user select
self.dismiss(animated: true, completion: nil)
}
then in firstViewController conforms to applySelection protocol like this
class FirstViewController: UIViewController,applySelection{
func applyText(text: String){
//update the UIlabel here
2- Use a closure.
here in secondViewController you should add a new var like this,
var applyText: ((String) -> Void)?
then in
#IBAction func saveButtom(sender: UIButton){
self.applyText(text) //text is your text to update
}
and in firstViewController in prepare for segue rewrite like this.
let vc = segue.destination as! fisrtViewController)
vc.applyText = { [weak self] data in
guard let self = self else {return}
self.text = text //this is assigning the text to self-text supposing text is a UILabel in this viewController
}
You may try one of the two approaches which may seem right for you.
EDIT.
try this.
class ViewController2: UIViewController {
var selectionDelegate:gameModeDelegate!
#IBAction func chooseButton(_ sender: Any) {
selectionDelegate.didTapChoice(test: "TEST")
//if segue is a show segue
self.navigationController?.popViewController(animated: true)
//else is a modal segue.
dismiss(animated: true, completion: nil)
}
override func viewDidLoad() {
super.viewDidLoad()
}
}
PD. you dont have to present a viewcontroller already present in the view stack, only dissmis it. Good luck
I have two view controller that opens modally. When the first VC closed then the second should be opened. But when I close the first one, the second one is not displayed at all.
what is the problem?
My code is:
self.dismiss(animated: true) {
let flowVC = LanguageFlowViewController()
self.present(flowVC, animated: true)
}
You need reference of view controller from where you to present first view controller.
For example, you have view controller name as X, from there your first view controller A present. So you need reference of X to present B, because A will not be available in memory.
So when you try to present second view controller using self, it will do nothing.
So, for solution assign reference of X view controller to A. In class A, declare:
var refX: X?
While present A from X, set self to refX. Like:
var aVC = A() // This is temp, you need to instantiate your view controller here.
aVC.refX = self
self.present(aVC, animated: true, completion: nil)
Now, inside view controller A, when dismiss:
var bVC = B() // This is temp, you need to instantiate your view controller here.
self.dismiss(animated: true) {
if self.refX != nil {
self.refX?.present(bVC, animated: true, completion: nil)
}
}
I hope this will help you.
If you want to open modally ThirdViewController after Dismiss SecondViewController then you have to create protocol for it.
Like we have three UIViewController(FirstViewController,SecondViewController and
ThirdViewController)
Step 1: We need to create a protocol in SecondViewController as given below code.
protocol DismissedViewProtocal {
func dismissView()
}
class SecondViewController: UIViewController {
var delegate: DismissedViewProtocal?
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
#IBAction func dismissSecondViewAction(_sender : AnyObject) {
dismiss(animated: true) {
self.delegate?.dismissView()
}
}
Step 2: You need to add protocol in FirstViewController as given below
class FirstViewController: UIViewController, DismissedViewProtocal {
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func anAction(_sender : AnyObject){
let flowVC = self.storyboard?.instantiateViewController(withIdentifier:"SecondViewController") as? SecondViewController
secondVC?.delegate = self
self.present(secondVC!, animated: true) {
}
}
func dismissView() {
let thirdVC = self.storyboard?.instantiateViewController(withIdentifier:"ThirdViewController")
self.present(thirdVC!, animated: true) {
}
}
}
Your are dismissing the current view controller by calling self.dismiss().
Therefore it is impossible for it to present anything anymore, since it is removed from the view hierarchy. As others have mentioned, try using the self.presentingViewController or self.navigationController (if it is on a navigationController) to present your new view.
However, if you need maximum flexibility create a delegate protocol. Create a protocol with a function presentForChild(viewController: UIViewController). Before your previous view presents the view from which the code in your question belongs to, give it a reference of the protocol.
Example:
protocol ChildPresentDelegate: class {
func presentForChild(vc: UIViewController)
}
class FirstController: UIViewController, ChildPresentDelegate {
func presentForChild(vc: UIViewController) {
present(vc, animated: true, completion: nil)
}
/**
other code
*/
func showControllerAsWasShownInTheQuestion() {
let controller = SecondController()
controller.delegate = self
present(controller, animated: true, completion: nil)
}
}
class SecondController: UIViewController {
weak var delegate: ChildPresentDelegate?
func dismissMySelf() {
self.dismiss(animated: true) {
delegate?.presentForChild(vc: LanguageFlowViewController())
}
}
}
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...
}
}