How to use custom UIAlertview in multiple ViewControllers? - swift

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.:)

Related

Testing tableview.reloadData()

while using a MockTableView this code still not calling reloadData() from the mock,
please i wanna know what is wrong here.
following this book: Test-Driven IOS Development with Swift 4 - Third Edition
page 164, i was as an exercise
full code repo - on github
ItemListViewController.swift
import UIKit
class ItemListViewController: UIViewController, ItemManagerSettable {
#IBOutlet var tableView: UITableView!
#IBOutlet var dataProvider: (UITableViewDataSource & UITableViewDelegate &
ItemManagerSettable)!
var itemManager: ItemManager?
override func viewDidLoad() {
super.viewDidLoad()
itemManager = ItemManager()
dataProvider.itemManager = itemManager
tableView.dataSource = dataProvider
tableView.delegate = dataProvider
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
tableView.reloadData()
}
#IBAction func addItem(_ sender: UIBarButtonItem) {
if let nextViewController =
storyboard?.instantiateViewController(
withIdentifier: "InputViewController")
as? InputViewController {
nextViewController.itemManager = itemManager
present(nextViewController, animated: true, completion: nil)
}
}
}
ItemListViewControllerTest.swift
import XCTest
#testable import ToDo
class ItemListViewControllerTest: XCTestCase {
var sut: ItemListViewController!
var addButton: UIBarButtonItem!
var action: Selector!
override func setUpWithError() throws {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let vc = storyboard.instantiateViewController(withIdentifier:
"ItemListViewController")
sut = vc as? ItemListViewController
addButton = sut.navigationItem.rightBarButtonItem
action = addButton.action
UIApplication.shared.keyWindow?.rootViewController = sut
sut.loadViewIfNeeded()
}
override func tearDownWithError() throws {}
func testItemListVC_ReloadTableViewWhenAddNewTodoItem() {
let mockTableView = MocktableView()
sut.tableView = mockTableView
guard let addButton = sut.navigationItem.rightBarButtonItem else{
XCTFail()
return
}
guard let action = addButton.action else{
XCTFail()
return
}
sut.performSelector(onMainThread: action, with: addButton, waitUntilDone: true)
guard let inputViewController = sut.presentedViewController as?
InputViewController else{
XCTFail()
return
}
inputViewController.titleTextField.text = "Test Title"
inputViewController.save()
XCTAssertTrue(mockTableView.calledReloadData)
}
}
extension ItemListViewControllerTest{
class MocktableView: UITableView{
var calledReloadData: Bool = false
override func reloadData() {
calledReloadData = true
super.reloadData()
}
}
}
You inject a MockTableview Then you call loadViewIfNeeded(). But because this view controller is storyboard-based and the table view is an outlet, the actual table view is loaded at this time. This replaces your MockTableview.
One solution is:
Call loadViewIfNeeded() first
Inject the MockTableview to replace the actual table view
Call viewDidLoad() directly. Even though loadViewIfNeeded() already called it, we need to repeat it now that we have a different tableview in place.
Another possible solution:
Avoid MockTableview completely. Continue to use a real table view. You can test whether it reloads data by checking whether the number of rows matches the changed data.
Yet another solution:
Avoid storyboards. You can do this with plain XIBs (but these lack table view prototype cells) or programmatically.
By the way, I see all your tearDownWithError() implementations are empty. Be sure to tear down everything you set up. Otherwise you will end up with multiple instances of your system under test alive at the same time. I explain there here: https://qualitycoding.org/xctestcase-teardown/

errors encountered while discovering extensions: Error Domain=PlugInKit Code=13 "query cancelled"

I am trying to display or upload UIImage and I am getting this error.
"errors encountered while discovering extensions: Error Domain=PlugInKit Code=13 "query cancelled" UserInfo={NSLocalizedDescription=query cancelled}"
import UIKit
class ViewController: UIViewController, UINavigationControllerDelegate, UIImagePickerControllerDelegate {
// linked labels and UiButtons
#IBOutlet weak var ifix: UILabel!
#IBOutlet weak var UIImage: UIImageView!
let someImageView: UIImageView = {
let theImageView = UIImageView()
theImageView.translatesAutoresizingMaskIntoConstraints = false // call this property so the image is added to your view
return theImageView
}()
#IBAction func UploadImage(_ sender: UIButton) {
let myPickerController = UIImagePickerController()
myPickerController.delegate = self;
myPickerController.sourceType = UIImagePickerController.SourceType.photoLibrary
self.present(myPickerController, animated: true, completion: nil)
}
#objc func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any])
{
let image_data = info[UIImagePickerController.InfoKey.originalImage] as? UIImage
let imageData:Data = image_data!.pngData()!
_ = imageData.base64EncodedString()
self.dismiss(animated: true, completion: nil)
}
#IBAction func UIShuffle(_ sender: UIButton) {
}
#IBAction func UIReset(_ sender: UIButton) {
}
override func viewDidLoad() {
super.viewDidLoad()
// additional setup after loading the view, typically from a nib.
view.addSubview(someImageView) //This add it the view controller without constraints
someImageViewConstraints() //This function is outside the viewDidLoad function that controls the constraints
}
// `.isActive = true` after every constraint
func someImageViewConstraints() {
someImageView.widthAnchor.constraint(equalToConstant: 180).isActive = true
someImageView.heightAnchor.constraint(equalToConstant: 180).isActive = true
someImageView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
someImageView.centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: 28).isActive = true
}
}
This message is harmless. The message comes from the OS and is related to the OS keeping an eye out for any newly discovered extensions related to whatever your app is doing. I see it often on macOS when displaying an open file dialog. While browsing around for a file, the OS is checking to see if there are any file-related extensions it needs to load in order to show you something. When the user presses "OK" or "Cancel" it stops searching for the extensions and spits that message to the console. I gather iOS may be doing something similar, perhaps related to share or other user-file-related activities. The message does not indicate an error in your application.

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

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

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.

UIPopoverController, Xcode 6, IOS 8 using Swift

I'm having some trouble getting a UIPopover to appear using swift. The code that is commented out works fine in Objective-C, but doesn't work using Swift. When I tap the + in my view controller I do get the "click" in my debugger, however no popover appears.
class PlayerInformationTableViewController: UITableViewController, NSFetchedResultsControllerDelegate, UIPopoverControllerDelegate {
#IBOutlet weak var addBarButtonItem: UIBarButtonItem!
var playerInformationViewController = PlayerInformationViewController()
var popover:UIPopoverController? = nil
override func viewDidLoad() {
super.viewDidLoad()
/*
//setup the popover
_cuesPopoverViewController = [self.storyboard instantiateViewControllerWithIdentifier:#"CuesPopoverViewController"];
self.cuesPopover = [[UIPopoverController alloc] initWithContentViewController:_cuesPopoverViewController];
self.cuesPopover.popoverContentSize = CGSizeMake(540, 300);
self.cuesPopover.delegate = self;
*/
playerInformationViewController.storyboard?.instantiateViewControllerWithIdentifier("PlayerInformationViewController")
popover?.contentViewController = playerInformationViewController
popover?.popoverContentSize = CGSizeMake(300, 300)
popover?.delegate = self
// Uncomment the following line to preserve selection between presentations
// self.clearsSelectionOnViewWillAppear = false
// Uncomment the following line to display an Edit button in the navigation bar for this view controller.
// self.navigationItem.rightBarButtonItem = self.editButtonItem()
}
#IBAction func addPopover(sender: AnyObject) {
println("Click")
popover?.presentPopoverFromBarButtonItem(addBarButtonItem, permittedArrowDirections: UIPopoverArrowDirection.Any, animated: true)
}
Solution
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func addPopover(sender: AnyObject) {
var popoverViewController = self.storyboard?.instantiateViewControllerWithIdentifier("PlayerInformationViewController") as UIViewController
popoverViewController.modalPresentationStyle = .Popover
popoverViewController.preferredContentSize = CGSizeMake(450, 450)
let popoverPresentationViewController = popoverViewController.popoverPresentationController
popoverPresentationViewController?.permittedArrowDirections = .Any
popoverPresentationViewController?.delegate = self
popoverPresentationViewController?.barButtonItem = sender as UIBarButtonItem
presentViewController(popoverViewController, animated: true, completion: nil)
}
Here is a simple example for iOS 8. Popover are presented using adaptivity apis in iOS 8.
class PlayerInformationTableViewController: UITableViewController, UIPopoverPresentationControllerDelegate, NSFetchedResultsControllerDelegate{
...
#IBAction func addPopover(sender: UIBarButtonItem){
let playerInformationViewController = PlayerInformationViewController()
playerInformationViewController.modalPresentationStyle = .Popover
playerInformationViewController.preferredContentSize = CGSizeMake(300, 300)
let popoverPresentationViewController = playerInformationViewController.popoverPresentationController
popoverPresentationViewController?.permittedArrowDirections = .Any
popoverPresentationViewController?.delegate = self
popoverPresentationController?.barButtonItem = sender
presentViewController(playerInformationViewController, animated: true, completion: nil)
}
func adaptivePresentationStyleForPresentationController(controller: UIPresentationController!) -> UIModalPresentationStyle{
return .None
}
}
Display Popover with contentView from xib
func showPopover(sender: AnyObject) {
let contentViewController = UINib(nibName: "ContentVC", bundle: nil).instantiateWithOwner(nil, options: nil)[0] as ContentVC
contentViewController.modalPresentationStyle = UIModalPresentationStyle.Popover
var detailPopover: UIPopoverPresentationController = contentViewController.popoverPresentationController!
detailPopover.delegate = self
detailPopover.barButtonItem = sender as UIBarButtonItem
detailPopover.permittedArrowDirections = UIPopoverArrowDirection.Any
presentViewController(contentViewController,
animated: true, completion:nil)
}
Next allows to make not full screen PopoverView on iPhone
for this do not forget to inherit MainViewController: UIPopoverPresentationControllerDelegate and set delegate to PopoverView
func adaptivePresentationStyleForPresentationController(controller: UIPresentationController!) -> UIModalPresentationStyle
{
return .None
}
It looks like your popover is nil. Where are you assigning/instantiating it?
Try changing this:
popover?.presentPopoverFromBarButtonItem(addBarButtonItem, permittedArrowDirections: UIPopoverArrowDirection.Any, animated: true)
To this:
if let pop = popover {
pop.presentPopoverFromBarButtonItem(addBarButtonItem, permittedArrowDirections: UIPopoverArrowDirection.Any, animated: true)
} else {
NSLog("Error: Popover was nil")
}
I imagine you'll see that error message in your console. In the .XIB for your PlayerInformationTableViewController, do you have a UIPopoverController?
If so, you probably need to ensure that the var popover is either (1) being manually instantiated in your awakeFromNib, or that it's an #IBOutlet and is being connected properly.
Alternatively, can you simply use the popover already present in your playerInformationViewController?