Search code examples
iosswiftselectorstackview

swift functions with default parameters also a selector?


I wanted to be able to call this function from two places: When I finish editing a text field, I want to add a new webView when there are none in a stackView, and I also want to be able to use a barButtonItem to do so.

I'm having two problems. when the bar button calls this function, the parameter 'url', becomes an object, type UIBarButtonItem. when it's called from textFieldShouldReturn, it properly comes in as an NSURL. if the user doesn't type anything in the address field, and hits enter, a blank NSURL comes in, and the default value is not used. (i'd like it to be)

what should the call look like from the textfieldShouldReturn function, so that a blank will trigger the default?

how do i handle the fact that either my function or the button will call the function, and why does my named parameter 'url' become what i guess would be 'sender?'

    override func viewDidLoad() {
    super.viewDidLoad()

    setDefaultTitle()

    let add = UIBarButtonItem(barButtonSystemItem: .Add, target: self, action: #selector(ViewController.addWebView))
    let delete = UIBarButtonItem(barButtonSystemItem: .Trash, target: self, action: #selector(ViewController.deleteWebView))
    navigationItem.rightBarButtonItems = [delete, add]
}

   func addWebView(url: NSURL = NSURL(string: "https://www.google.com")!) {
    let webView = UIWebView()
    webView.delegate = self
    stackView .addArrangedSubview(webView)
    webView.loadRequest(NSURLRequest(URL: url))

    webView.layer.borderColor = UIColor.blueColor().CGColor
    selectWebView(webView)

    let recognizer = UITapGestureRecognizer(target: self, action: #selector(ViewController.webViewTapped))
    recognizer.delegate = self
    webView.addGestureRecognizer(recognizer)
}

   func textFieldShouldReturn(textField: UITextField) -> Bool {
    if let webView = activeWebView, address = addressBar.text {
        if let url = NSURL(string: address) {
            webView.loadRequest(NSURLRequest(URL: url))
        }

    } else if stackView.arrangedSubviews.count == 0 {
         let address = NSURL(string: addressBar.text!)!
        addWebView(address)
    }
    textField.resignFirstResponder()
    return true
}

Solution

  • That's right that you are getting sender object which is actually UIBarButtonItem. Have you heard about Target-Action Cocoa pattern? If no, you can read more here: https://developer.apple.com/library/ios/documentation/General/Conceptual/Devpedia-CocoaApp/TargetAction.html

    Especially relevant section to you is "An Action Method Must Have a Certain Form".

    Consider to introduce addWebView overload:

    func addWebView(sender: NSObject) {
    
        addWebView(url: NSURL(string: "https://www.google.com")!)
    }
    
    private func addWebView(url: NSURL) {
        //left as is ...
    }
    

    Here is update per Dave's comments. Have to use different name for actual implementation method. Otherwise Swift compiler is failed to resolve the assigned selector name.

    Useful code, which demonstrates the problem is attached below:

    class Notifier: NSObject {
    
        private var _target: NSObject!
        private var _action: Selector!
    
        func addObserver(target: NSObject, action: Selector) {
            _target = target
            _action = action
        }
    
        func invokeMethod() {
    
            guard let t = _target else {
                print("target must be set")
                return
            }
    
            guard let a = _action else {
                print("action must be set")
                return
            }
            if t.respondsToSelector(a) {
                t.performSelector(a, withObject: self)
            }
        }
    
    }
    
    class Observer: NSObject {
    
        func subscribe(notifier: Notifier) {
            notifier.addObserver(self, action: #selector(Observer.callback))
        }
    
        func callback(sender: NSObject) {
            callbackImpl(NSURL(string: "https://www.google.com")!)
        }
    
        private func callbackImpl(url: NSURL) {
            print("url\(url)")
        }
    }
    
    //client's code
    let n = Notifier()
    let o = Observer()
    o.subscribe(n)
    n.invokeMethod()