UITableView cell invokes UIAlertController popup ..."present" error - swift

I have a tableview definition in which I am attempting to invoke an UIAlertController popup. I installed a button in the prototype tableView cell, when the button is touched, an IBAction handles the event. The problem is that the compiler won't let me.
present(alertController, animated: true, completion: nil)
Generates compiler error: "Use of unresolved identifier 'present'
Here is the code:
class allListsCell: UITableViewCell {
#IBOutlet var cellLable: UIView!
#IBOutlet var cellSelected: UILabel!
var colorIndex = Int()
#IBAction func cellMarkButton(_ sender: UIButton, forEvent event: UIEvent) {
if colors[self.colorIndex].selected == false {
colors[self.colorIndex].selected = true
cellSelected.text = "•"
let alertController = UIAlertController(title: "???", message: "alertA", preferredStyle: .alert)
let OKAction = UIAlertAction(title: "dismiss", style: .default) { (action:UIAlertAction!) in
print("Sand: you have pressed the Dismiss button");
}
alertController.addAction(OKAction)
present(alertController, animated: true, completion: nil) // ERROR
} else {
colors[self.colorIndex].selected = false
cellSelected.text = ""
}
}
If I comment that one line, the app runs correctly for each cell...

You can't call present AlertController inside a tableView cell , it needs a subclass of UIViewController or other equivalent one , you should use a delegate or some sort of notification to handle that , see my answer here for the same problem AlertControllerCallInsideCell
Edit : Form Docs , it's an instance method inside UIViewController . so it can't be called inside any other class of other type (UITableViewCell) in your case

It is not possible to call the "present" method from a TableViewCell, I recommend having a function in the main controller to show your UIAlertController.
Using this code you can instantiate the parent driver and execute any available function:
extension UIView {
var parentViewController: UIViewController? {
var parentResponder: UIResponder? = self
while parentResponder != nil {
parentResponder = parentResponder!.next
if let viewController = parentResponder as? UIViewController {
return viewController
}
}
return nil
}
}
//UITableViewCell
if let controller = self.parentViewController as? YourController
{
controller.showAlert()
}
Here is an example of its use with a CollectionViewCell:
https://github.com/AngelH2/CollectionViewCell-Comunication/tree/master/CollectionCellAction

Related

Delegates and protocols with VC not connected

I have a parent view controller i.e. HomeViewController which has a navigation bar button via which a user can trigger an alert and enter a string. This string needs to be passed to the child view controller.
Here is the relevant code in the parent view controller:
protocol NewSectionDelegate {
func sendSectionName(name : String)
}
class HomeViewController: UIViewController {
var sectionNameDelegate : NewSectionDelegate?
func addCardAsChild() { // add the child VC to the parent VC
if cardViewController == nil {
cardViewController = CardViewController()
addViewController(newViewController: cardViewController!)
} else {
addViewController(newViewController: cardViewController!)
}
}
func triggerAlert() {
let alertController = UIAlertController(title: "New section", message: "Name the section with a words or a sentence", preferredStyle: .alert)
alertController.addTextField(configurationHandler:
{(_ textField: UITextField) -> Void in //txtview customization
})
let addAction = UIAlertAction(title: "Add", style: .default) { _ in
guard let sectionName = alertController.textFields?.first?.text else { return }
self.sectionNameDelegate?.sendSectionName(name: sectionName) // Sending string; verified that the string is not nil
}
alertController.addAction(addAction)
self.present(alertController, animated: true, completion: nil)
}
And here is the child view controller:
class CardViewController: UIViewController, NewSectionDelegate {
override func viewDidLoad() {
super.viewDidLoad()
let homeViewController = HomeViewController()
homeViewController.sectionNameDelegate = self
}
func sendSectionName(name: String) {
print("received name:\(name)") // This line of code is never called
}
The data is not getting passed and I have no idea why.
Is this what you are looking for?
protocol NewSectionDelegate {
func sendSectionName(name : String)
}
class HomeViewController: UIViewController {
var sectionNameDelegate : NewSectionDelegate?
var cardViewController = CardViewController()
func addCardAsChild() { // add the child VC to the parent VC
self.addChild(self.cardViewController)
}
func triggerAlert() {
let alertController = UIAlertController(title: "New section", message: "Name the section with a words or a sentence", preferredStyle: .alert)
alertController.addTextField(configurationHandler:
{(_ textField: UITextField) -> Void in //txtview customization
})
let addAction = UIAlertAction(title: "Add", style: .default) { _ in
guard let sectionName = alertController.textFields?.first?.text else { return }
self.sectionNameDelegate?.sendSectionName(name: sectionName) // Sending string; verified that the string is not nil
}
alertController.addAction(addAction)
self.present(alertController, animated: true, completion: nil)
}
}
class CardViewController: UIViewController, NewSectionDelegate {
override func viewDidLoad() {
super.viewDidLoad()
guard let homeViewController = self.parent as? HomeViewController else { return }
homeViewController.sectionNameDelegate = self
}
func sendSectionName(name: String) {
print("received name:\(name)") // This line of code is never called
}
}
I think the delegate method is getting called but for the internal HomeViewController you're making in the ChildViewController viewDidLoad method. It looks like you're expecting the method to get called on a different object. I would remove that code from viewDidLoad and set the sectionNameDelegate in HomeViewController

Return control to function when modal viewcontroller dismissed

I need to present a modal VC that sets a property in my presenting VC, and then I need to do something with that value back in the presenting VC. I have to be able to pass pointers to different properties to this function, so that it's reusable. I have the code below (KeyPickerTableViewController is the modal VC).
It should work, except not, because the line after present(picker... gets executed immediately after the picker is presented.
How do I get my presenting VC to "wait" until the modal VC is dismissed?
#objc func fromKeyTapped(_ button: UIBarButtonItem) {
print("from tapped")
setKey(for: &sourceKey, presentingFrom: button)
}
#objc func toKeyTapped(_ button: UIBarButtonItem) {
print("from tapped")
setKey(for: &destKey, presentingFrom: button)
}
fileprivate func setKey(for key: inout Key!, presentingFrom buttonItem: UIBarButtonItem) {
let picker = KeyPickerTableViewController()
picker.delegate = self
picker.modalPresentationStyle = .popover
picker.popoverPresentationController?.barButtonItem = buttonItem
present(picker, animated: true, completion: nil)
if let delKey = delegatedKey {
key = delKey
}
}
You could use delegate pattern or closure.
I would do the following
1. I would not use inout pattern, I would first call the popover and then separately update what is needed to be updated
2. In KeyPickerTableViewController define property var actionOnDismiss: (()->())? and setting this action to what we need after initialisation of KeyPickerTableViewController
I could show it in code, but the abstract you've shown is not clear enough to come up with specific amendments. Please refer the illustration below.
import UIKit
class FirstVC: UIViewController {
var key = 0
#IBAction func buttonPressed(_ sender: Any) {
let vc = SecondVC()
vc.action = {
print(self.key)
self.key += 1
print(self.key)
}
present(vc, animated: true, completion: nil)
}
}
class SecondVC: UIViewController {
var action: (()->())?
override func viewDidLoad() {
onDismiss()
}
func onDismiss() {
action?()
}
}
While presenting VC, add dismissing modal VC action in its completion handler, so that Viewcontroller will be presented after dismissal is completed
present(picker, animated: true, completion: { (action) in
//dismissal action
if let delKey = delegatedKey {
key = delKey
}
})

Not able to transfer information between rootVC and modally presented otherVC?

I want to use information from one rootVC in a modally presented otherVC. Here is the setup for it:
protocol OtherVCDelegate {
func didHitOK()
func didHitCancel()
}
class ViewController: UIViewController, OtherVCDelegate {
func didHitCancel() {
//do a function
}
func didHitOK() {
//do another function
}
var stringy = "Hello"
#IBAction func ButtonAction(_ sender: Any) {
let otherVC = self.storyboard?.instantiateViewController(withIdentifier: "AlertVC") as! AlertVC
otherVC.modalPresentationStyle = .overCurrentContext
otherVC.delegate = self
otherVC.label.text = stringy //THIS is where my question focuses
self.present(otherVC, animated: true, completion: nil)//presents the other VC modally
}
The otherVC has a UILabel named "label". The issue I run into, though, when running the ButtonAction function is that xcode finds a fatal error because it unexpectedly found nil when unwrapping an optional value. I am a bit confused as to why this occurs, because putting in a print statement within ButtonAction confirms that stringy is not nil. The label in the otherVC is 'set up' correctly so I am not sure what is giving the nil value.
I don't think your label is available until you present your view controller. Pass the string instead and set your label text in your AlertVC viewDidLoad method.
in your AlertVC declare a string:
var stringy:String?
then change your code
#IBAction func ButtonAction(_ sender: Any) {
let otherVC = self.storyboard?.instantiateViewController(withIdentifier: "AlertVC") as! AlertVC
otherVC.modalPresentationStyle = .overCurrentContext
otherVC.delegate = self
otherVC.stringy = stringy //you pass the string instead of setting the label text
self.present(otherVC, animated: true, completion: nil)//presents the other VC modally
}
at this point you can set the text label in the viewDidLoad:
self.label.text = self.stringy

How to use custom UIAlertview in multiple ViewControllers?

I am looking for a way to call a custom alert view from multiple view controllers. So far I have made several different attempts without success.
I created an alert view with an interface builder that works fine on one view controller but not the other.
I then tried creating the alert view programmatically thinking it may have something to do with the outlets not being connected on the other view controller. This one also worked on one view controller and not the other.
I made a separate swift file and made a public function and the same result. With this last method, I am able to successfully re-use a regular UIAlertController on multiple view controllers but that is not exactly what I am looking for.
With the first two methods, I do not get any compiling errors. The app runs fine and then crashes when I call the alert from another view controller.
Thanks in advance for any input!
EDIT:
This example works when I put it in another swift file.
public func showSimpleAlert(title: String, message: String?, presentingController: UIViewController) {
if IS_OS_8_OR_LATER() {
let controller = UIAlertController(title: title, message: message, preferredStyle: .Alert)
controller.addAction(UIAlertAction(title: "OK", style: .Cancel, handler: { (action) -> Void in
}))
presentingController.presentViewController(controller, animated: true, completion: nil)
} else {
let alert = UIAlertView(title: title, message: message, delegate: nil, cancelButtonTitle: "OK")
alert.show()
}
}
This is the one I want to work on.
public func showAlert(oMsg: String, oTitle:String) {
alertView.backgroundColor = UIColor.whiteColor()
alertView.layer.cornerRadius = 25
alertTitleLabel.text = oTitle as String
alertTitleLabel.font = UIFont(name: "Open-Sans-Bold", size: 20)
alertTitleLabel.textColor = UIColor.blackColor()
alertTitleLabel.textAlignment = .Center
alertTitleLabel.numberOfLines = 1
alertTitleLabel.frame = CGRectMake(25, 60, 264, 112)
alertLabel.text = oMsg as String
alertLabel.font = UIFont(name: "Open-Sans", size: 20)
alertLabel.textColor = UIColor.blackColor()
alertLabel.textAlignment = .Center
alertLabel.numberOfLines = 4
alertLabel.frame = CGRectMake(25, 130, 264, 112)
okButton.setTitle("OK", forState: .Normal)
okButton.setTitleColor(UIColor.blueColor(), forState: .Normal)
okButton.frame = CGRectMake(60, 230, 197, 75)
okButton.addTarget(UIViewController.self, action:#selector(LoginViewController.buttonAction(_:)), forControlEvents: .TouchUpInside)
}
I will give the answer for a simple custom alertview which is basically a modified uiviewcontroller. you can use a uiviewcontroller as a uialertviewcontroller as follow.
Simple AlertView::
The AlertVC:
import UIKit
class ErrorAlert: UIViewController {
var titlenote:String = ""
var message:String = ""
#IBOutlet weak var cancelBtn: UIButton!
#IBOutlet weak var messageHolder: UILabel!
#IBOutlet weak var imageHolder: UIImageView!
#IBOutlet weak var titleHolder: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = UIColor.black.withAlphaComponent(0.7)
// Do any additional setup after loading the view.
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.messageHolder.text = self.message
self.titleHolder.text = self.titlenote
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func dismiss(_ sender: Any) {
self.dismiss(animated: true, completion: nil)
}
}
This viewcontroller can be reuse in any vc and any number of times.
Useage Example::
let alertController = self.storyboard?.instantiateViewController(withIdentifier: "erroralert") as! ErrorAlert
alertController.titlenote = "Invalid login"
alertController.message = "Invalid facebook account."
alertController.providesPresentationContextTransitionStyle = true
alertController.definesPresentationContext = true
alertController.modalPresentationStyle = UIModalPresentationStyle.overCurrentContext
alertController.modalTransitionStyle = UIModalTransitionStyle.crossDissolve
self.present(alertController, animated: true, completion: nil)
I have made the background of the alertviewvc semitransparent by setting the alpha value.
Actual Display ::
You can make more complex alertview by this method but for reusability you have apply some logic as the button actions will be different for different viewcontroller. Example -- Sometime you can use the alertview for logout alert or sometime for submitting a form .So in both cases the action will be different so for reusability you have to write extra logic.
Another alertView::
I hope my answer will help you.:)

I managed to segue to a blank view controller. How do I segue back?

//
// ViewController.swift
// FunFacts
//
// Created by Alex Macleod on 4/10/14.
// Copyright (c) 2014 Alex Macleod. All rights reserved.
//
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var funFactLabel: UILabel!
#IBOutlet weak var funFactButton: UIButton!
#IBOutlet weak var swipeView: UIView!
// let swipeRec = UISwipeGestureRecognizer()
let factBook = FactBook()
let colorWheel = ColorWheel()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
// swipeRec.addTarget(self, action: "swipedView")
// swipeView.addGestureRecognizer(swipeRec)
// swipeView.userInteractionEnabled = true
var swipeRight = UISwipeGestureRecognizer(target: self, action: "respondToSwipeGesture:")
swipeRight.direction = UISwipeGestureRecognizerDirection.Right
self.view.addGestureRecognizer(swipeRight)
var swipeLeft = UISwipeGestureRecognizer(target: self, action: "respondToSwipeGesture:")
swipeLeft.direction = UISwipeGestureRecognizerDirection.Left
self.view.addGestureRecognizer(swipeLeft)
var swipeDown = UISwipeGestureRecognizer(target: self, action: "respondToSwipeGesture:")
swipeDown.direction = UISwipeGestureRecognizerDirection.Down
self.view.addGestureRecognizer(swipeDown)
var swipeUp = UISwipeGestureRecognizer(target: self, action: "respondToSwipeGesture:")
swipeUp.direction = UISwipeGestureRecognizerDirection.Up
self.view.addGestureRecognizer(swipeUp)
funFactLabel.text = factBook.randomFact()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func respondToSwipeGesture(gesture: UIGestureRecognizer) {
if let swipeGesture = gesture as? UISwipeGestureRecognizer {
switch swipeGesture.direction {
case UISwipeGestureRecognizerDirection.Right:
// swipedAlertViewRight()
self.performSegueWithIdentifier("segueSwipeRight", sender: nil)
case UISwipeGestureRecognizerDirection.Left:
// swipedAlertViewLeft()
swipedLeft()
case UISwipeGestureRecognizerDirection.Down:
var randomColor = colorWheel.randomColor()
view.backgroundColor = randomColor
funFactButton.tintColor = randomColor
funFactLabel.text = factBook.randomFact()
case UISwipeGestureRecognizerDirection.Up:
var randomColor = colorWheel.randomColor()
view.backgroundColor = randomColor
funFactButton.tintColor = randomColor
funFactLabel.text = factBook.randomFact()
default:
break
}
}
}
func swipedLeft() {
self.performSegueWithIdentifier("segueSwipeLeft", sender: nil)
}
// func swipedAlertViewRight(){
// let tapAlert = UIAlertController(title: "Swiped", message: "You just swiped right", preferredStyle: UIAlertControllerStyle.Alert)
// tapAlert.addAction(UIAlertAction(title: "OK", style: .Destructive, handler: nil))
// self.presentViewController(tapAlert, animated: true, completion: nil)
// }
//
// func swipedAlertViewLeft(){
// let tapAlert = UIAlertController(title: "Swiped", message: "You just swiped left", preferredStyle: UIAlertControllerStyle.Alert)
// tapAlert.addAction(UIAlertAction(title: "OK", style: .Destructive, handler: nil))
// self.presentViewController(tapAlert, animated: true, completion: nil)
// }
#IBAction func showFunFact() {
var randomColor = colorWheel.randomColor()
view.backgroundColor = randomColor
funFactButton.tintColor = randomColor
funFactLabel.text = factBook.randomFact()
}
}
So I swipe left and I it takes me to a new viewViewcontroller, I swipe right it takes me to another blank view controller. How do I tell these blank view controllers to segue back to the main view controller?
Do you have a navigation bar in that view controller?
If you do, then you can simply do:
self.navigationController.popViewControllerAnimated(YES)
If you do not, then you simply need to
self.performSegueWithIdentifier("segueSwipeLeft", sender: nil)
(which is to say, perform a segue back to the view controller you came from). Segues are not necessarily a push and pop thing.
Segues create instances of their view controllers. This makes sense when moving in a forward direction, but if you "performSegueWithIdentifier" when moving in a backward direction, it's likely you're not returning to the previous view controller, but rather you're creating and presenting a new instance of the previous view controller.
For example, let's say you two view controllers, A and B, and A has a text field on it that has a value specified by the user. Then you segue to B. Then you use a standard segue back to A. The text won't be in the text field on A because you're looking at a new instance of A, not the original instance of that view controller.
If you want to back-up, there are Unwind segues, which are a special kind of segue to return you to a previous instance. They are rigged up to the green "exit" button at the top of your scene in the storyboard editor. Unwind segues (sometimes called Exit Segues) are interesting because they let you unwind not just to the previous view controller, but all the way back through a deep stack of view controllers, and as part of the unwind they can call different methods on the destination view controller, such as indicating that a Cancel or Save button was tapped on the source view controller.
Programatically, if your view controller was presented modally, you can also use dismissViewController:animated:completion: to back up.