Returning the result of a UIAlert to a delegate method - swift

Is it possible, with a UIAlert, to return what the user selected to a delegate method? If so, how?
I'd prefer not to change the delegate method or stop using UIAlert, if such a solution exists. Though all help and ideas are appreciated.
Delegate Protocol
protocol RouteManagerDelegate {
//behaves like textFieldShouldEndEditing
func routeShouldUpdateUnfinished() -> Bool
}
My Current Attempt at Implementation
extension MyController : RouteManagerDelegate {
func routeShouldUpdateUnfinished() -> Bool {
var response = false
//make Alert
let confirmationAlert = UIAlertController(title: "Current Route is Unfinished", message: "Do you want to continue?", preferredStyle: .alert)
//add Yes or No options
confirmationAlert.addAction(UIAlertAction(title: "Yes", style: .destructive) { _ in
response = true
})
confirmationAlert.addAction(UIAlertAction(title: "No", style: .cancel)
self.present(confirmationAlert, animated: true)
return response
}
}
This doesn't work because UIAlert behaves asynchronously. The function simply returns false every time.
But you can clearly see the intent:
If the user selects 'Yes' (continue), then routeShouldUpdateUnfinished should return true.

For me the provided code looks like the delegate method is being understood wrong. To help you out a little, here a little diagram:
The delegate pattern is used to communicate from child to parent.
In your solution this could look like:
Parent implements delegate pattern with function
func routeShouldUpdateUnfinished(result: bool) { ... }
When parent sets child up the parent calls
child.delegate = self
The alert inside child calls
delegate.routeShouldUpdateUnfinished(true)
The parent handles the code

Just in case someone finds themselves in this esoteric position:
I wanted to do as Ramden suggested, but also didn't want to expose a function that other classes should never use (except in this scenario).
I ended up using a default implementation for my delegate method (defined in an extension of my protocol) and a static fileprivate method to handle the result of my delegate method returning true.
Now the fileprivate 'handler' method isn't exposed but I can also use a UIAlert. This has flaws but works. Implementation below, if ever it's useful to someone.
protocol RouteManagerDelegate {
func routeShouldUpdateUnfinished() -> Bool
}
extension RouteManagerDelegate { //default implementations of delegate protocol
func routeShouldUpdateUnfinished() -> Bool {
if let delegate = self as? UIViewController {
let alert = UIAlertController(title: "Wait!", message: "Do you want to continue?", preferredStyle: .alert)
confirmationAlert.addAction(UIAlertAction(title: "Yes", style: .destructive) { _ in
RouteManager.handleRouteChangedUnfinished() //the 'handler' function
})
confirmationAlert.addAction(UIAlertAction(title: "No", style: .cancel))
delegate.present(confirmationAlert, animated: true)
}
return false
}
}
struct RouteManager {
func myFunc() {
...
if delegate?.routeShouldUpdateUnfinished() ?? true {
RouteManager.handleRouteChangedUnfinished() //the 'handler' function
}
...
}
static fileprivate func handleRouteChangedUnfinished() { //don't want to expose this
//Notify the Database
}
}

Related

Swift - Is there a way run a method on an object without calling the object? (Self?)

Quick question on a few different functions I'm having, all have the same idea. I have an object, and I want to run a function with it. But because it's an extension of the Objects type, I'm calling the object.method(on: object). Is there a way to simplify this so that it runs on itself without having to state it?
Example:
import UIKit
extension UINavigationController {
func removeBackButtonString(on navigationController: UINavigationController) {
let backButton = UIBarButtonItem(title: "", style: .plain, target: nil, action: nil)
navigationController.navigationBar.topItem?.backBarButtonItem = backButton
}
}
Used in Code:
navigationController?.removeBackButtonString(on: navigationController!)
But is there a way to call it like so?
navigationController?.removeBackButtonString()
Or, to extended a NSManagedObject:
extension Object {
func setFavorite(object: Object) {
object.isFavorite = true
}
}
Use: object.setFavorite(object: object) But how can you tap into the object properties if you don't have the parameter?
Example: object.setFavorite()
Yes, you can modify your extension function to something like this:
extension UINavigationController {
func removeBackButtonString() {
let backButton = UIBarButtonItem(title: "", style: .plain, target: nil, action: nil)
// `self` is inferred in Swift
navigationBar.topItem?.backBarButtonItem = backButton
}
}
Then, this will run perfectly fine:
navigationController?.removeBackButtonString()
Update: The same applies also to your second example:
extension Object {
func setFavorite() {
isFavorite = true
}
}

How to get Stripe's STPPaymentCardTextField Data programmatically?

I've successfully set my first view controller to STPAddCardViewController. I now need to get the user information in the STPPaymentCardTextField. Problem is, I'm used to using the storyboard to make outlets. How do I detect the STPPaymentCardTextField programmatically?
I've tried:
class ViewController: STPAddCardViewController, STPPaymentCardTextFieldDelegate {
let paymentCardTextField = STPPaymentCardTextField()
func paymentCardTextFieldDidChange(_ textField: STPPaymentCardTextField) {
print(paymentCardTextField.cardNumber)
//ERROR: printing nil in the console
}
}
But I'm getting nil as an output. Any help?
You should use either STPAddCardViewController, or STPPaymentCardTextField, not both. The SDK's ViewControllers are not designed to be extended. The intended use is:
class MyVC : STPAddCardViewControllerDelegate {
override func viewDidLoad() {
…
let addCardView = STPAddCardViewController()
addCardView.delegate = self
// Start the addCardView
self.navigationController.pushViewController(addCardView, animated: true)
}
…
func addCardViewController(_ addCardViewController: STPAddCardViewController, didCreatePaymentMethod paymentMethod: STPPaymentMethod, completion: #escaping STPErrorBlock) {
// TODO: do something with paymentMethod
// Always call completion() to dismiss the view
completion()
}
func addCardViewControllerDidCancel(_ addCardViewController: STPAddCardViewController) {
// TODO: handle cancel
}
}
But rather than my partial example I'd recommend reading these docs and trying out this example iOS code. Best wishes!

swift calling a func from inside static func?

I am trying to call upon func to bring up an alert and it is inside another static func though it's not working
inside my fetch user func when actual longitude return nil i want to call the alert func though its not working as i thought it would
it says error: extra argument in call "message
func alertTheUser(title: String , message: String) {
let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
let ok = UIAlertAction(title: "OK", style: .default, handler: nil);
alert.addAction(ok);
present(alert, animated: true, completion: nil);
}
static func firestorefetchUserWithUID(uid: String, completion:#escaping (User2) -> ()) {
//code taken out for this example
//fetch user from database
//dictionary = snapshot etc
guard let latitude = dictionary["Actual Latitude"] as? String else {
alertTheUser(title:"title" , message:"message")
return
}
//code taken out for this example
}
A static func cannot call a non-static func directly. A non-static func is an instance method, and in a static func there is no instance — it is static, meaning it belongs to the type, not to an instance of the type.
Thus, static firestorefetchUserWithUID cannot call alertTheUser because alertTheUser is an instance method and you have no instance to send it to. And if alertTheUser were static, you'd have the same problem again, because then it could not call present for the same reason, as present is an instance method.
It looks to me like making the static func static was just a mistake to begin with; make it an instance method, if you know you'll always have an instance to send it to. (And I presume you do have an instance, because your use of present suggests that this code must be in a UIViewController subclass.)

How to call extension methods in overrideViewDidLoad in swift

I have the following code:
extension ViewController {
func AddLeftGesture(){
let SwipeLeft:UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(MyDismissOnSwipeLeft))
self.view.addGestureRecognizer(SwipeLeft)
}
func MyDismissOnSwipeLeft(){
self.dismiss(animated: true, completion: nil)
}
and What I would like to accomplish is that override the viewDidLoad and
call AddLeftGesture method so that it'll be part of each VC I make
and I don't have to type it again and again in each viewDidLoad,
is this possible? or do you guys have any other suggestions?
well I don't think it's a good idea, because typically viewDidLoad is used for setting most properties and if you would like to override it in a view controller you should write it again.What I can suggest is that to make a base ViewController and add this code in the viewDidLoad of that and then subclass every viewController from the base view controller , This way whenever you want to change anything you just call super.viewDidLoad
class BaseViewController: UIViewController{
override func viewDidLoad() {
super.viewDidLoad()
addLeftGesture()
}
}
class CustomViewController: BaseViewController{
}
Make this class which inherits UITapGestureRecognizer
open class BlockTap: UITapGestureRecognizer {
fileprivate var tapAction: ((UITapGestureRecognizer) -> Void)?
public override init(target: Any?, action: Selector?) {
super.init(target: target, action: action)
}
public convenience init (
tapCount: Int = 1,
fingerCount: Int = 1,
action: ((UITapGestureRecognizer) -> Void)?) {
self.init()
self.numberOfTapsRequired = tapCount
#if os(iOS)
self.numberOfTouchesRequired = fingerCount
#endif
self.tapAction = action
self.addTarget(self, action: #selector(BlockTap.didTap(_:)))
}
open func didTap (_ tap: UITapGestureRecognizer) {
tapAction? (tap)
}
}
then make an extension of UIView
extension UIView {
public func addTapGesture(tapNumber: Int = 1, action: ((UITapGestureRecognizer) -> ())?) {
let tap = BlockTap(tapCount: tapNumber, fingerCount: 1, action: action)
addGestureRecognizer(tap)
isUserInteractionEnabled = true
}
}
Then You can use this as
override func viewDidLoad() {
super.viewDidLoad()
self.view.addTapGesture(action: {[unowned self] (_) in
//Do whatever on click of View
})
}
Hope it helps!
There's two options AFAIK. Either you can subclass UIViewController and then make all of your controllers inherit from the subclassed one, or you can swizzle UIViewController's viewDidLoad().
I personally would choose swizzling, although it has one disadvantage - it hides the implementation and might be confusing for a new developer coming onto a project. So make sure you document this properly, somewhere in your project README and in the code as well.
Now for some code examples:
Subclassing UIViewController
MyViewController.swift
class MyViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
addGesture()
}
func addGesture() {
// Do what you need
}
}
class OtherViewController: MyViewController {
// Automatically will add gesture because it's a subclass of MyViewController
}
Swizzling viewDidLoad
What method swizzling does is, that it exchanges implementations of your methods. That simply means that the name of your function points at code from a different function. For more information on this topic read this article.
UIViewController+Swizzle.swift
static func swizzle(selector originalSelector: Selector,
with newSelector: Selector,
on targetClass: AnyClass) {
let originalMethod = class_getInstanceMethod(targetClass, originalSelector)
let swizzledMethod = class_getInstanceMethod(targetClass, newSelector)
// If we were able to add the swizzled function, replace methods.
// Otherwise exchange implementations if method already exists.
if class_addMethod(targetClass, originalSelector,
method_getImplementation(swizzledMethod),
method_getTypeEncoding(swizzledMethod)) {
class_replaceMethod(targetClass, newSelector,
method_getImplementation(originalMethod),
method_getTypeEncoding(originalMethod))
} else {
method_exchangeImplementations(originalMethod, swizzledMethod)
}
}
extension UIViewController {
// This function is getting called automatically by the runtime,
// when this class is loaded to perform some additional intiialization.
// However, this has now been deprecated in Swift, so only option is to
// declare a static function which you need to remember to call from
// somewhere, preferably early in your app initialization, like your
// didFinishLaunching function in AppDelegate or even AppDelegate's init
// function. I kept the initialize function in the code as a reference,
// however you would probably want to write it like in the comment
// below, to silence the warning.
//
// class func swizzle()
//
open override class func initialize() {
if self != UIViewController.self { return }
let swizzlingClosure: () = {
swizzle(selector: #selector(UIViewController.viewDidLoad),
with: #selector(UIViewController.swizzled_viewDidLoad),
on: UIViewController.self)
}()
swizzlingClosure
}
#objc private func swizzled_viewDidLoad() {
// Calls the original implementation,
// because implementations are switched.
swizzled_viewWillAppear(animated)
// Do whatever you need
addGesture()
}
#objc func addGesture() {
// Add your gesture
}
}

Trigger UIAlertAction on UIAlertController programmatically?

There are a couple of existing questions on this topic but they aren't quite what I'm after. I've written a little Swift app rating prompt for my app which presents two UIAlertController instances, one triggered by the other.
I'm now trying to unit test this, and trying to reach that second alert in the tests. I've written a simple spy to check the first controller, but I'd like a way to trigger one of the actions on the first alert, which in turn shows the second.
I've already tried alert.actions.first?.accessibilityActivate(), but it didn't seem to break inside the handler of that action – that's what I'm after.
A solution that doesn't involve changing the production code to allow programmatic tapping of UIAlertActions in unit tests, which I found in this SO answer.
Posting it here as well as this question popped up for me when Googling for an answer, and the following solution took me way more time to find.
Put below extension in your test target:
extension UIAlertController {
typealias AlertHandler = #convention(block) (UIAlertAction) -> Void
func tapButton(atIndex index: Int) {
guard let block = actions[index].value(forKey: "handler") else { return }
let handler = unsafeBitCast(block as AnyObject, to: AlertHandler.self)
handler(actions[index])
}
}
Here's roughly what I did:
Created a mocked version of my class that would present the alert controller, and in my unit tests, used this mock.
Overrode the following method that I'd created in the non-mocked version:
func alertActionWithTitle(title: String?, style: UIAlertActionStyle, handler: Handler) -> UIAlertAction
In the overridden implementation, stored all the details about the actions in some properties (Handler is just a typealias'd () -> (UIAlertAction))
var didCreateAlert = false
var createdTitles: [String?] = []
var createdStyles: [UIAlertActionStyle?] = []
var createdHandlers: [Handler?] = []
var createdActions: [UIAlertAction?] = []
Then, when running my tests, to traverse the path through the alerts, I implemented a callHandlerAtIndex method to iterate through my handlers and execute the right one.
This means that my tests look something like this:
feedback.start()
feedback.callHandlerAtIndex(1) // First alert, second action
feedback.callHandlerAtIndex(2) // Second alert, third action
XCTAssertTrue(mockMailer.didCallMail)
I took a slightly different approach based on a tactic I took for testing UIContextualAction—it's very similar to UIAction but exposes its handler as a property (not sure why Apple wouldn't have done the same for UIAction). I injected an alert actions provider (encapsulated by a protocol) into my view controller. In production code, the former just vends the actions. In unit tests, I use a subclass of this provider which stores the action and the handler in two dictionaries—these can be queried and then triggered in tests.
typealias UIAlertActionHandler = (UIAlertAction) -> Void
protocol UIAlertActionProviderType {
func makeAlertAction(type: UIAlertActionProvider.ActionTitle, handler: UIAlertActionHandler?) -> UIAlertAction
}
Concrete object (has typed titles for easy retrieval later):
class UIAlertActionProvider: UIAlertActionProviderType {
enum ActionTitle: String {
case proceed = "Proceed"
case cancel = "Cancel"
}
func makeAlertAction(title: ActionTitle, handler: UIAlertActionHandler?) -> UIAlertAction {
let style: UIAlertAction.Style
switch title {
case .proceed: style = .destructive
case .cancel: style = .cancel
}
return UIAlertAction(title: title.rawValue, style: style, handler: handler)
}
}
Unit testing subclass (stores actions and handlers keyed by ActionTitle enum):
class MockUIAlertActionProvider: UIAlertActionProvider {
var handlers: [ActionTitle: UIAlertActionHandler] = [:]
var actions: [ActionTitle: UIAlertAction] = [:]
override func makeAlertAction(title: ActionTitle, handler: UIAlertActionHandler?) -> UIAlertAction {
handlers[title] = handler
let action = super.makeAlertAction(title: title, handler: handler)
actions[title] = action
return action
}
}
Extension on UIAlertAction to enable typed action title lookup in tests:
extension UIAlertAction {
var typedTitle: UIAlertActionProvider.ActionTitle? {
guard let title = title else { return nil }
return UIAlertActionProvider.ActionTitle(rawValue: title)
}
}
Sample test demonstrating usage:
func testDeleteHandlerActionSideEffectTakesPlace() throws {
let alertActionProvider = MockUIAlertActionProvider()
let sut = MyViewController(alertActionProvider: alertActionProvider)
// Do whatever you need to do to get alert presented, then retrieve action and handler
let action = try XCTUnwrap(alertActionProvider.actions[.proceed])
let handler = try XCTUnwrap(alertActionProvider.handlers[.proceed])
handler(action)
// Assert whatever side effects are triggered in your code by triggering handler
}
I used Luke's guidance above to create a subclass of UIAlertAction that saves its completion block so it can be called during tests:
class BSAlertAction: UIAlertAction {
var completionHandler: ((UIAlertAction) -> Swift.Void)?
class func handlerSavingAlertAction(title: String?,
style: UIAlertActionStyle,
completionHandler: #escaping ((UIAlertAction) -> Swift.Void)) -> BSAlertAction {
let alertAction = self.init(title: title, style: style, handler: completionHandler)
alertAction.completionHandler = completionHandler
return alertAction
}
}
You could customize this to save more information (like the title and the style) if you like. Here's an example of an XCTest that then uses this implementation:
func testThatMyMethodGetsCalled() {
if let alert = self.viewController?.presentedViewController as? UIAlertController,
let action = alert.actions[0] as? BSAlertAction,
let handler = action.completionHandler {
handler(action)
let calledMyMethod = self.presenter?.callTrace.contains(.myMethod) ?? false
XCTAssertTrue(calledMyMethod)
} else {
XCTFail("Got wrong kind of alert when verifying that my method got called“)
}
}