Issue with types in swift? - iphone

I did a Team Treehouse code challenge, and I had an issue that I wanted to ask. Basically I created a func inside an enum that takes the specific case of the enum and initializes a given object. It told me to create it inside the enum, but unfortunately when I do this, I came across some errors. I was able to finish the challenge by doing it outside the enum, but I don't think that was exactly what I was supposed to do.
Anyways, my problem was that when it returned the object UIBarButtonItem, if it was assigned to a constant, the type of the constant is "() -> UIBarButtonItem" or depending on how the switch is setup, it could also come out like "(Button) -> UIBarButtonItem". I'm not really sure what the '->' symbol means in this case. Why does it not completely change into the class? What is going on here? In the tutorial right before this, it seems we did the same exact thing, so I'm not sure why it doesn't work.
enum Button {
case Done(String)
case Edit(String)
func toUIBarButtonItem () -> UIBarButtonItem {
switch self {
case .Done: return UIBarButtonItem(title: "Done", style: .Done, target: nil, action: nil)
case .Edit: return UIBarButtonItem(title: "Plain", style: .Plain, target: nil, action: nil)
}
}
}
let done = Button.Done("Done")
let doneButton = Button.toUIBarButtonItem(done)
This is the code that I am running, not including the UIBarButtonItem class. I hope this is enough information to understand what I am asking.

You are calling the toUIBarButtonItem() method on the Button type instead of a Button instance; this is why it does something special: it returns a curried function, which you then bind to done. The result, however, is still a function.
What you probably mean to do is invoke the method on your done object:
let done = Button.Done("Done")
let doneButton = done.toUIBarButtonItem()
A more in-depth explanation can be found under http://oleb.net/blog/2014/07/swift-instance-methods-curried-functions/

Related

Swift:"Unrecognized selector sent to instance", Xcode 9.4.1

Can anyone help me with this?
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate=self
tableView.dataSource=self
searchBar.autocorrectionType = .yes
searchBar.delegate=self
searchBarView.translatesAutoresizingMaskIntoConstraints=false
let tap:UIGestureRecognizer = UITapGestureRecognizer(target: self, action: Selector(("tapCancel:")))
searchBarView.addGestureRecognizer(tap)
tableView.addGestureRecognizer(tap)
tableView.isUserInteractionEnabled=true
}
...
func tapCancel(sender:UITapGestureRecognizer){
hideSearchBarView()
isSearchon=false
print("Tap cancel!")
}
Every time I tap the View, it crash.
"unrecognized selector sent to instance 0x7feb85d109e0"
I will appreciate any help!
Thanks!
I believe the reason is due to how method names get mapped between Swift and Objective-C, which underlies the Cocoa implementation and the whole target/action mechanism.
In your case, the Swift method:
#objc func tapCancel(sender:UITapGestureRecognizer)
...corresponds to the Objective-C selector:
-tapCancelWithSender:
Note: In order to work with the target/action paradigm (i.e., called by means of a selector), the method needs to be declared as #objc. The alternative attribute #IBOutlet (for use in conjunction with Interface Builder) also supports this. (tip of the hat to #rmaddy)
In order to remove the "withSender" part and get a selector that matches tapCancel:, you need to tell Swift to remove the argument label sender, like this:
func tapCancel(_ sender:UITapGestureRecognizer) // Notice the underscore (_)
Also, in line with the comment by #dan, perhaps you can use:
#selector(self.tapCancel(_:))
or more succinctly, as pointed by (thanks again) #rmaddy, just:
#selector(tapCancel)
(Xcode will try to autocomplete it to #selector(tapCancel(_:)), but the shorter syntax works as well and the method name is highlighted)
I was not familiar with the Selector() syntax you used, so I tried playing a bit with it, and behold:
(Selector does not match any method the compiler can "see").
(After adding "withSender", the compiler can match the method, but it suggests using the better #selector(... syntax).
As #rmaddy also pointed out in the comments, using the shorter #selector(doSomething) syntax (no colons, no underscore, no self) also does away with the problem of whether "withSender" is needed or not.
use
let tap:UIGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(tapCancel))
instead of
let tap:UIGestureRecognizer = UITapGestureRecognizer(target: self, action: Selector(("tapCancel:")))
And add #objc before method tapCancel()

Understanding Swift 2.2 Selector Syntax - #selector()

I am switching over the syntax of my project toward Swift 2.2 (which xCode helps me do automatically); however, I do not understand the new #selector() syntax.
As an example:
timer = NSTimer.scheduledTimerWithTimeInterval(1.0, target: self,
selector: #selector(MyVC.timerCalled(_:)), //new selector syntax!
userInfo: nil, repeats: true)
This has the selector #selector(MyVC.timerCalled(_:))
What does the _: signify? Can you add other variables into this selector? Say, #MyVC.timerCalled(_:whateverVar).
General info on what is different in this syntax as opposed to the string based implementation from earlier versions of Swift are greatly appreciated.
The bit in parenthesis is a mechanism for identifying the argument list for the selector that you want.
I recommend you look at the Generalized Naming proposal from Swift Evolution. It covers cases where you have a number of functions that differ only by their parameter labels and need to refer to them. The example from that document is:
extension UIView {
func insertSubview(view: UIView, at index: Int)
func insertSubview(view: UIView, aboveSubview siblingSubview: UIView)
func insertSubview(view: UIView, belowSubview siblingSubview: UIView)
}
If you wanted to get a function value for one of those the result is ambiguous:
let fn = someView.insertSubview // ambiguous: could be any of the three methods
The solution implemented is to add the argument labels, without any type information to the code that generates the function value to disambiguate which you want:
let fn = someView.insertSubview(_:at:)
let fn1 = someView.insertSubview(_:aboveSubview:)
See how the labels are added in the parens?
This proposal played a role in the one that most directly applies to your question:
Referencing the Objective-C selector of a method
In this particular case the selector you want to refer to is timerCalled: which is a function of one parameter that has no label. Hence (_:). The underscore means the label is not specified and the colon.
Swift 2.2 has deprecated Stringified selectors: In swift 2.0, we use to write the selector as a String i.e "buttonClicked". The disadvantage with this approach is that the compiler can't check whether the method really exists or not at compile time(Even if you have misspelled it).
EX:1
func buttonClicked(){
}
So the above method in the new approach can be called as #selector(buttonClicked)
EX:2
func buttonClicked(stringValue : String){
}
So the above method in the new approach can be called as #selector(buttonClicked(_:))
EX:3
func buttonClicked(stringValue : String, indexValue : Int){
}
So the above method with parameters in the new approach can be called as #selector(buttonClicked(_:indexValue:))
Consider below code for adding target to button in swift 3 using #selector
button.addTarget(self, action: #selector(self.buttonAction(sender:)),
for: UIControlEvents.touchUpInside)
func buttonAction(sender:UIButton!){
}
This syntax worked for me when migrating to swift 3
This is the way Swift method signatures are represented in documentation, and it is now beginning to be used in new language features such as the #selector() syntax to express methods by their argument lists.
The each colon (:) represents a method parameter. For named parameters, the colon is preceded by the external parameter name; for unnamed parameters, an underscore (_) is used.
So for example, MyVC.timerCalled(_:)) indicates a method on the MyVC type with one unnamed parameter, which might be declared like this:
func timerCalled(timer: NSTimer) { ... }
(Note that timer is the internal parameter name, since by default the first parameter of a method is unnamed)
The type (MyVC in your example) can also be omitted if it is within the same scope of the #selector() declaration.
A more complex example might look like this:
let sel = #selector(aMethodWithSeveralParameters(_:secondParam:thirdParam:))
...
func aMethodWithSeveralParameters(firstParam: Int, secondParam: Int, thirdParam: Int) { ... }

Swift Selector with default argument

I have Write simple Code here
self.navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.Cancel, target: self, action: Selector("cancelClick"))
Actual Function
func cancelClick(isAlert:String = "yes"){
self.dismissViewControllerAnimated(true, completion: { () -> Void in
if isAlert == "yes" {
Functions.displayAlert("called")
}
})
}
self.cancelClick() - Worked but if i didn't pass the argument
self.cancelClick(isAlert:"no") - Crashed
So what should be my selector if i have to pass argument in default perameter
tried with both Selector("cancelClick") and Selector("cancelClick:") but no luck.
The thing is that the parameter is not up to you. It is always the button (the "sender"), and that is the only thing it can be.
In other words, if you want this function to have a parameter, then by all means you will need to set your selector string as "cancelClick:" - the colon means that it takes a parameter. But that parameter must be the button:
func cancelClick(bbi:UIBarButtonItem?) {
However, you will notice that I have cleverly made this UIBarButtonItem parameter an Optional. Why do you think I did that? Because now you can also call it directly and pass nil:
self.cancelClick(nil)
Thus, cancelClick: now has a way to know whether the call comes from the tapping of a button or by a direct call - if bbi is not nil, the button was tapped; if bbi is nil, we were called directly from code. Sneaky, eh?
Another sneaky approach is to make the parameter an AnyObject:
func cancelClick(sender:AnyObject) {
The beauty of this is that you can call it with any kind of class instance. cancelClick can check the type of the sender. If it is a UIBarButtonItem (sender is UIBarButtonItem), then we were called by tapping the button. Otherwise, if called in code, you can pass in a string or anything else that this function might be prepared to deal with.
Don't use Selector("cancelClick:"), try instead just "cancelClick:" with the colon so you can pass an argument.
And as Matt said, the argument you must pass is the sender (the button) itself.

extra argument userinfo in call

Get the compile error message from xCode with Swift language: "extra argument userinfo in call". the question is how to use userInfo data from timer to argument "userInfo" in notification center.
func onSetAudioWithTrackIndex(CurrentTrackIndex:Int, urlAudio: String){
........
//try to pass currentTrackIndex data to timerfire
playTimeClock = NSTimer.scheduledTimerWithTimeInterval(0.4, target: self, selector: "onSetPlayProgressTime:", userInfo: CurrentTrackIndex, repeats: true)
}
//Timerfire
func onSetPlayProgressTime(timer: NSTimer){
var currentTime = mediaPlayer.currentPlaybackTime
var playDuration = mediaPlayer.duration
var trackIndexDict = ["trackIndex" : timer.userInfo]
................
if currentTime == playDuration{
NSNotificationCenter.defaultCenter().postNotificationName(MPMoviePlayerPlaybackDidFinishNotification, object: self, userInfo: trackIndexDict)
}
return
}
Swift can give you slightly strange compiler errors sometimes, which amount to “there were multiple possible options, none of them worked, so here’s what’s wrong with one of them”. When the one it complains about is not the one you were trying for, this can be very strange
This is why Swift tells you so often that something is “not a Int8” even though that has nothing to do with what you were trying to do (huh, I was trying to concatenate two strings, what have Int8s got to do with anything? – it’s because one of the possible options for the + operator is to work on Int8s so that’s the one it chooses to complain about).
In this case, postNotificationName has multiple overloaded versions, one with 1 argument, one with 2, and one with 3 (the one you want). None of them fit the types you’ve supplied so it’s saying “one of the options is a call with 2 arguments, and you supplied 3, so that won’t work, there’s an extra argument”.
Unfortunately this is really quite confusing and throws you off the scent of what is actually wrong. Assuming you cut and pasted your actual code, looks like there’s a spelling error in MPMoviePlayerPlaybackDidFinishNotification, and the userInfo argument label is missing a colon after it.
(p.s. you don’t need to explicitly put a return at the end of functions that return void, though it doesn’t do any harm)
In swift 3 , I got this same error. When I was converting the swift 2.2 to swift 3, as the syntax was changed so it throws this error.
Swift 3:
NotificationCenter.default.post(name: NSNotification.Name(rawValue: MPMoviePlayerPlaybackDidFinishNotification), object: self, userInfo: trackIndexDict)
The problem is
that the userInfo property of NSTimer is an optional:
var userInfo: AnyObject? { get }
so that trackIndexDict has the type [String : AnyObject?] which is not convertible
to [NSObject : AnyObject] as expected by the last parameter of postNotificationName().
With optional binding you can "test and unwrap" the property:
if currentTime == playDuration {
if let timerInfo: AnyObject = timer.userInfo {
let trackIndexDict = ["trackIndex" : timerInfo]
NSNotificationCenter.defaultCenter().postNotificationName(MPMoviePlayerPlaybackDidFinishNotification,
object: self,
userInfo: trackIndexDict)
}
}
thanks, Martin, this(userInfo of NSTimer is optional) is root cause. with the below change. this can be fixed
if let timerUserInfo: AnyObject = timer.userInfo! {
NotificationCenter.default.post(name: NSNotification.Name(rawValue: MPMoviePlayerPlaybackDidFinishNotification), object: self, userInfo: ["trackIndex":timerUserInfo])
}

Default Parameters

I have a function with one parameter. This parameter has a default value. (true)
func disableButton(animated: Bool = true) {
if (animated) {
...
} else {
...
}
}
So i can call the function as:
disableButton(animated: true)
or:
disableButton()
and they give me the same result.
Now I have an NSTimer running a selector on completion like this:
buttonFadeTimer = NSTimer.scheduledTimerWithTimeInterval(NSTimeInterval(1.5), target: self, selector: Selector("disableButton"), userInfo: nil, repeats: false)
It will send the disableButton() message on completion. However on completion it causes the app to crash.
If i do:
func disableButton() {
disableButton(animated: true)
}
Then the timer successfully sends that message and the app does not crash.
Of course this is really ugly and kind of limits the great Swift feature of default parameters.
Is this a bug (that I should report) or am I doing it wrong?
Thanks for any help!
There's a difference between -disableButton and -disableButton: selectors. But you can't just use your desired selector like this:
buttonFadeTimer = NSTimer.scheduledTimerWithTimeInterval(NSTimeInterval(1.5), target: self, selector: Selector("disableButton:"), userInfo: nil, repeats: false)
because NSTimer here assumes selector with no arguments or selector with single argument (which will be used to pass firing NSTimer instance).
I believe, you should migrate from using selectors, as you want to use all that great features Swift delivers, as far as selectors is outdated pattern.
Just a note: it could work if you use "disableButton:" selector, maybe NSTimer's pointer will be interpreted as true and maybe not, not sure what will happen (maybe it could crash due to Swift strong typing stuff). But depending on that behaviour is a bad practice which could lead to bugs which are extremely hard to exterminate.