'#selector' refers to a method that is not exposed to Objective-C - xcode

The new Xcode 7.3 passing the parameter via addTarget usually works for me but in this case it's throwing the error in the title. Any ideas? It throws another when I try to change it to #objc
Thank you!
cell.commentButton.addTarget(self, action: #selector(FeedViewController.didTapCommentButton(_:)), forControlEvents: UIControlEvents.TouchUpInside)
The selector it's calling
func didTapCommentButton(post: Post) {
}

In my case the function of the selector was private. Once I removed the private the error was gone. Same goes for fileprivate.
In Swift 4
You will need to add #objc to the function declaration. Until swift 4 this was implicitly inferred.

You need to use the #objc attribute on didTapCommentButton(_:) to use it with #selector.
You say you did that but you got another error. My guess is that the new error is that Post is not a type that is compatible with Objective-C. You can only expose a method to Objective-C if all of its argument types, and its return type, are compatible with Objective-C.
You could fix that by making Post a subclass of NSObject, but that's not going to matter, because the argument to didTapCommentButton(_:) will not be a Post anyway. The argument to an action function is the sender of the action, and that sender will be commentButton, which is presumably a UIButton. You should declare didTapCommentButton like this:
#objc func didTapCommentButton(sender: UIButton) {
// ...
}
You'll then face the problem of getting the Post corresponding to the tapped button. There are multiple ways to get it. Here's one.
I gather (since your code says cell.commentButton) that you're setting up a table view (or a collection view). And since your cell has a non-standard property named commentButton, I assume it's a custom UITableViewCell subclass. So let's assume your cell is a PostCell declared like this:
class PostCell: UITableViewCell {
#IBOutlet var commentButton: UIButton?
var post: Post?
// other stuff...
}
Then you can walk up the view hierarchy from the button to find the PostCell, and get the post from it:
#objc func didTapCommentButton(sender: UIButton) {
var ancestor = sender.superview
while ancestor != nil && !(ancestor! is PostCell) {
ancestor = view.superview
}
guard let cell = ancestor as? PostCell,
post = cell.post
else { return }
// Do something with post here
}

Try having the selector point to a wrapper function, which in turn calls your delegate function. That worked for me.
cell.commentButton.addTarget(self, action: #selector(wrapperForDidTapCommentButton(_:)), forControlEvents: UIControlEvents.TouchUpInside)
-
func wrapperForDidTapCommentButton(post: Post) {
FeedViewController.didTapCommentButton(post)
}

As you know selector[About] says that Objective-C runtime[About] should be used. Declarations that are marked as private or fileprivate are not exposed to the Objective-C runtime by default. That is why you have two variants:
Mark your private or fileprivate method declaration by #objc[About]
Use internal, public, open method access modifier[About]

Related

Executing NSApplicationDelegate Code Before ViewController viewDidLoad

My Swift 3, Xcode 8.2 MacOS app loads several tables through web services calls. Since the tables are used by one or more of my seven view controllers, I placed them in the AppDelegate.
The problem is that the AppDelegate methods applicationWillFinishLaunching and applicationDidFinishLaunching run after the ViewController viewDidLoad methods.
As a result the table views show no data. I was able to get it to work correctly by calling the appDelegate method that loads the data from one of the ViewController viewDidLoad methods. Since any of the ViewControllers could be invoked on application start up, I would have to add the call to all of them and some sort of flagging method to prevent redundant loads.
My question is: where can I place code that will execute prior to the ViewControllers loading? The code loads data into multiple arrays of dictionary. These arrays are in the AppDelegate.
I read up on #NSApplicationMain and replacing it with a main.swift. I assume none of application objects would have been instantiated at that point so I couldn't call their methods and don't think my code would be valid outside of a class.
The pertinent part of my appDelegate:
class AppDelegate: NSObject, NSApplicationDelegate {
var artists: [[String:Any]]? = nil
var dispatchGroup = DispatchGroup() // Create a dispatch group
func getDataFromCatBox(rest: String, loadFunction: #escaping ([[String: Any]]?) -> Void) {
let domain = "http://catbox.loc/"
let url = domain + rest
var request = URLRequest(url: URL(string: url)!)
request.httpMethod = "Get"
let session = URLSession.shared
var json: [[String:Any]]? = nil
dispatchGroup.enter()
session.dataTask(with: request) { data, response, err in
if err != nil {
print(err!.localizedDescription)
return
}
do {
json = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as? [[String: Any]]
}
catch {
print(error)
}
loadFunction(json)
self.dispatchGroup.leave()
}.resume()
}
func loadArtistTable(array: [[String: Any]]?) {
artists = array
}
}
The ViewController code:
override func viewDidLoad() {
super.viewDidLoad()
appDelegate = NSApplication.shared().delegate as! AppDelegate
appDelegate.getDataFromCatBox(rest: "artists.json", loadFunction: appDelegate.loadArtistTable)
appDelegate.dispatchGroup.wait()
artistTable.reloadData()
}
The code works in that the TableView is populated when the window appears. While it's not a lot of code, I would have to duplicate across all my View Controllers.
This a prototype. The production version will have 14 tables and invocations.
I guess my comment should be an answer. So. Why not just make the window containing the table views not be visible on launch? Then in didFinishLaunching, load the table data and then show the window.
I don't think there is any way to do what I want the way it is structured in the question. The ViewController code could be reduced to
appDelegate = NSApplication.shared().delegate as! AppDelegate
appDelegate.getDataFromCatBox(rest: "artists.json", loadFunction: appDelegate.loadArtistTable
by creating a wrapper function in AppDelegate that had the wait in it. It also could contain a flag that indicated that a given table had already been loaded so as not to make a redundant call.
I ended up going with a different approach: I created a super class with singleton subclasses for each table. Now my viewDidLoad method looks like this:
artists.loadTable() // The sublass
artistTable.reloadData()
If any one comes up with a cleaner solution to the original problem, I'll accept their answer in place of mine.

NSView with a KVC property in Swift

I have a custom NSView class defined as:
class MyView: NSView
{
var someText: NSString
override func didChangeValueForKey(key: String)
{
println( key )
super.didChangeValueForKey( key )
}
// other stuff
}
What I want to be able to do is from outside of this class change the value of someText and have didChangeValueForKey notice that someText has changed so I can, for example, set needsDisplay to true for the view and do some other work.
How an I do this?
Are you sure you need KVC for this? KVC works fine in Swift, but there’s an easier way:
var SomeText: NSString {
didSet {
// do some work every time SomeText is set
}
}
There is no KVC mechanism for this because this isn't what KVC is for.
In Objective-C, you would implement the setter explicitly (or override if the property is originally from a superclass) and do your work there.
In Swift, the proper approach is the didSet mechanism.
didChangeValueForKey() is not part of KVC, it's part of KVO (Key-Value Observing). It is not intended to be overridden. It's intended to be called when one is implementing manual change notification (as a pair with willChangeValueForKey()).
More importantly, though, there's no reason to believe that it will be called at all for a property which is not being observed by anything. KVO swizzles the class in order to hook into the setters and other mutating accessors for those properties which are actually being observed. When such a property is changed (and supports automatic change notification), KVO calls willChangeValueForKey() and didChangeValueForKey() automatically. But for non-observed properties, those methods are not called.
Finally, in some cases, such as the indexed collection mutation accessors, KVO will use different change notification methods, such as willChange(_:valuesAtIndexes:forKey:) and didChange(_:valuesAtIndexes:forKey:).
If you really don't want to use didSet for some reason, you would use KVO to observe self for changes in the someText property and handle changes in observeValueForKeyPath(_:ofObject:change:context:). But this is a bad, clumsy, error-prone, inefficient way of doing a simple thing.
KVO and didSet are not mutually exclusive:
import Foundation
class C: NSObject {
dynamic var someText: String = "" {
didSet {
print("changed to \(someText)")
}
}
}
let c = C()
c.someText = "hi" // prints "changed to hi"
class Observer: NSObject {
init(_ c: C) {
super.init()
c.addObserver(self, forKeyPath: "someText", options: [], context: nil)
}
override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {
print("observed change to \(object!.valueForKeyPath(keyPath!))")
}
}
let o = Observer(c)
c.someText = "test" // prints "changed to test" and "observed change to test"
I would add to Jaanus's answer that to make the property KVC compliant, you should declare it as dynamic var someText: NSString.
But if you don't need all the bells and whistles oh KVC, didSet is the way to go.
Update
As for didChangeValueForKey: – it is intended for the opposite, for you to notify value for key has changed (if it is not due to one of the cases covered by Foundation). You should use addObserver(_:forKeyPath:options:context:) and override observeValueForKeyPath(_:ofObject:change:context:) to be notified of changes.
Alternatively you can use one of many 3rd party solutions such as ReactiveCococa or Facebook's KVOController

NSCollectionView does show nothing

I've tried to follow this guide:
Quick Start for Collection Views
using an NSImageView in the Collection View Item.
Nothing shows up, neither if i set the image with a Image Well neither if i set the array via code.
So i tried to do it programmatically, using
func representedObject(representedObject: AnyObject)
{
super.representedObject = representedObject
photoImageView.image = (representedObject as! NSImage)
println("\(representedObject)")
}
in the Collection View Item (subclassed).
If I don't subclass Collection View Item Xcode tells me that there is no prototype set, if i subclass it it tells that "could not load the nibName"... (it's in the storyboard with correct identity set)
I can't have this Collection View to work :-(
Anyway, i like the bindings... so i'd like to achieve the correct result with bindings..
I checked and rechecked every passage in the document at the link and everything seems fine. the main difference is that the document uses the app delegate, i'm using a view controller.
i translated KVC methods in swift, i think they are correct since i know them have been called. Here them are:
func insertObject(p: ClientPhoto, inClientPhotoArrayAtIndex index: Int) {
images.insertObject(p, atIndex: index)
}
func removeObjectFromClientPhotoArrayAtIndex(index: Int) {
images.removeObjectAtIndex(index)
}
func setClientPhotoArray(a: NSMutableArray) {
images = a
}
func clientPhotoArray() -> NSArray {
return images
}
Their are basically 2 ways to work with NSCollectionView. 1 is to set the itemPrototype property and the other is to override newItemForRepresentedObject. The override method is more flexible and has the advantage that you using the technique below you can create the nscollectionviewitem in storyboard and all the outlets will be set correctly. Here is an example of how I use it:
class TagsCollectionView: NSCollectionView {
// ...
override func newItemForRepresentedObject(object: AnyObject!) -> NSCollectionViewItem! {
let viewItem = MainStoryboard.instantiateControllerWithIdentifier("tagCollectionViewItem") as! TagCollectionViewItem
viewItem.representedObject = object
return viewItem
}

Method 'setChecked' with Objective-C selector 'setChecked:' conflicts with setter for 'checked' with the same Objective-C selector

I have created custom class that inherits from UIButton.
In that class I have created a function defined as:
func setChecked(checked:Bool){
self.checked = checked
if checked {
buttonImageView.image = UIImage(named: "radioSelected.png")
} else {
buttonImageView.image = UIImage(named: "radioUnselected.png")
}
}
which was working fine until I updated my xCode to 6.1.3.
Now I keep getting the error message on the function definition line:
Method 'setChecked' with Objective-C selector 'setChecked:' conflicts
with setter for 'checked' with the same Objective-C selector
I already tried to make a override it but then I get a error saying that "Method does not override any method from its superclass".
Anyone know how can I correctly solve it?
(I don't want to change the name of my function.)
You have function name conflicting with your property. What about to implement it in a more elegant way with property observing? This explicitly shows how value changes, as well as a side effects for the value changes.
class RadioButton: UIButtom {
var checked: Bool = false {
didSet {
buttonImageView.image = UIImage(named: checked ? "radioSelected.png" : "radioUnselected.png")
}
}
}
It seems that there is a name collision happening. There a few more people with the same issue:
Compiler error: Method with Objective-C selector conflicts with previous declaration with the same Objective-C selector
https://stackoverflow.com/questions/30006724/method-setplayer-with-objective-c-selector-setplayer-conflicts-with-setter
Try renaming your function to something like:
func checkedSetter(checked:Bool){

Swift weak delegate runtime error (bad access). Bug?

I have a problem with delegates in Swift (OSX). I have a view, connected to a delegate through a weak reference. Simplified code could be like this:
protocol MyProtocol: class {
func protocolFunc() -> Int
}
class MyController : MyProtocol {
func protocolFunc() -> Int { return 2 }
}
class MyView : NSView {
weak var delegate: MyProtocol?
func grabData {
var data = delegate?.protocolFunc()
}
}
When delegate?.protocolFunc() is called, the app crashes saying "bad access". It's like if the MyController instance had disappeared... But it has not. The MyController instance lives in a NSDocument subclass; and view's delegate is properly set.
The crash goes away if I declare the delegate to be strong. But the thing is I want the delegate to be weak. What's going on? To my eyes, the weak reference should work.
At the time of writing (Xcode 6 Beta 5), there's a bug with weak delegates. For the time being, all you can do until it is fixed is to change protocol MyProtocol: class to #objc protocol MyProtocol and avoid using any pure Swift classes in your protocol.
A temporary alternate solution would be to change this:
weak var delegate: MyProtocol?
to this:
weak var delegate: MyController?
Of course it defeats the purpose of MyProtocol, however, it allows you to use pure Swift classes while we wait for a proper fix for this.

Resources