While writing an iOS app I noticed a weird error with CGRects and #objc protocols.
I managed to strip my code down to this, which produces the exact same stack trace :
import Foundation
#objc protocol TestClassDelegate {
optional func doSomethingWith(rect: CGRect)
}
class TestClass: NSObject {
var delegate: TestClassDelegate?
func callMyDelegate(myRectangle: CGRect) {
if let delegate = delegate {
delegate.doSomethingWith?(myRectangle)
}
}
}
print("I'LL NEVER COMPILE, SO YOU'LL NEVER SEE ME")
Just put this code in a main.swift file and try to compile. I intended to raise the issue on bugs.swift.org, but perhaps there's something I don't understand going on... Any idea ?
EDIT : Here's the issue on Swift's bug tracker (you'll find a stack trace there) : https://bugs.swift.org/browse/SR-2268
FOLLOWUP : Seems to be still broken in Swift 3 (see the issue on Swift's JIRA)
Related
We are using YPDrawSignature from here to draw a signature on the IOS App.
We are getting the following error on the UIViewController.
Non-'#objc' method 'didFinish' does not satisfy requirement of '#objc' protocol 'YPSignatureDelegate'
The UIViewController is this.
class SignatureViewController: UIViewController , UICollectionViewDelegate, UICollectionViewDataSource, YPSignatureDelegate {
func didStart(_ view : YPDrawSignatureView) {
// print("Started Drawing")
}
func didFinish(_ view : YPDrawSignatureView){
// func didFinish (){
}
}
UPDATE.
We tried the following.
#objc func didStart(_view : YPDrawSignatureView) {
// print("Started Drawing")
}
But still get the same error.
The YPDrawSignatureView has the following Delegate
#objc
public protocol YPSignatureDelegate: class {
func didStart(_ view : YPDrawSignatureView)
func didFinish(_ view : YPDrawSignatureView)
}
extension YPSignatureDelegate {
func didStart(_ view : YPDrawSignatureView) {}
func didFinish(_ view : YPDrawSignatureView) {}
}
I've downloaded YPSignatureView, and did similar to what you have. All I implemented was:
func didStart(_ view : YPDrawSignatureView) {}
func didFinish(_ view : YPDrawSignatureView) {}
and of-coarse assigning a delegate to the view, with those methods implemented by the delegate. And I don't get any errors. Make sure you haven't accidentally changed the YPSignatureView.swift file by clicking on the red button that says Fix as a suggestion. Before I implemented didStart and didFinish, I did get the same error as you have, with a button that says Fix in YPSignatureView. Clicking on that silently changes code within YPSignatureView.swift. Make sure YPSignatureView.swift is pristine and implement the above two functions and you should be fine. Re-download the file to be safe, implement those two methods as above, and thats it.
My didStart() and didFinish() functions are called when touches begins and finishes respectively. Let me know how you go.
Update: as mentioned above, you probably clicked on these two error messages here:
Do not do that. If you did then get a fresh copy of that file then just implement the protocol methods without any #objc. Hit Command + Shift + k to clean build folder and then build again. I did not get any errors after that, runs fine, and yours should too.
In your implementation, you forgot to put the space between _ and view;
Change
func didFinish(_view : YPDrawSignatureView) to
func didFinish(_ view : YPDrawSignatureView)
Trying to setup validation for a few text fields in a new (and very small) Swift Mac app. Following various other topics here on SO and a few other examples, I can still not get controlTextDidChange to propagate (to my ViewController).
E.g: How to live check a NSTextField - Swift OS X
I have read at least a dozen variations of basically that same concept. Since none of the accepted answers seem to work I am just getting more and more confused by something which is generally a fairly simple task on most platforms.
I have controlTextDidChange implemented to just call NSLog to let me know if I get anything.
AppDelegate should be part of the responder chain and should eventually handle controlTextDidChange but I see nothing there either.
Using the current Xcode I start a new project. Cocoa app, Swift, Storyboard and nothing else.
From what I can gather the below isolated example should work. In my actual app I have tried some ways of inserting the ViewController into the responder chain. Some answers I found suggested it was not always there. I also tried manually adding the ViewController as the delegate in code theTextField.delegate = self
Nothing I have done seems to get text changed to trigger any events.
Any ideas why I have so much trouble setting up this delegation?
My single textfield example app
Storyboard is about as simple as it gets:
AppDelegate
import Cocoa
#NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate, NSTextFieldDelegate, NSTextDelegate {
func applicationDidFinishLaunching(_ aNotification: Notification) {
// Insert code here to initialize your application
}
func applicationWillTerminate(_ aNotification: Notification) {
// Insert code here to tear down your application
}
func controlTextDidChange(notification: NSNotification) {
let object = notification.object as! NSTextField
NSLog("AppDelegate::controlTextDidChange")
NSLog("field contains: \(object.stringValue)")
}
}
ViewController
import Cocoa
class ViewController: NSViewController, NSTextFieldDelegate, NSTextDelegate {
#IBOutlet var theTextField: NSTextField!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override var representedObject: Any? {
didSet {
// Update the view, if already loaded.
}
}
func controlTextDidChange(notification: NSNotification) {
let object = notification.object as! NSTextField
NSLog("ViewController::controlTextDidChange")
NSLog("field contains: \(object.stringValue)")
}
}
I think the samples you're following are a bit out-of-date.
Try...
override func controlTextDidChange(_ notification: Notification) {
...as the function definition for your method in your NSTextFieldDelegate.
I'm very new to Swift and programming in general, a bit of Fortran 77 way back, and more recently some simple programming of microcontrollers. I'm working through the basics and all was well until i came across something that i just can't quite get to grips with - delegates. All the online posts don't quite get the concept across, at least for me, so to give myself something that i can refer back to, i've set up a basic template shown below in playground. If i run the code below it works and prints "Something done" to the terminal, but if i make the protocol a "class" protocol ie "protocol SomeDelegate: class {" and make the "var delegate" a "weak var delegate" as recommended in various posts, it doesn't work - what am i doing wrong?
import UIKit
protocol SomeDelegate {
func DoSomething()
}
class MyViewcontroller: UIViewController, SomeDelegate {
func DoSomething() {
print("Something done")
}
}
class OtherClass {
var delegate: SomeDelegate?
func DoSomething() {
delegate?.DoSomething()
}
}
var myVar = OtherClass()
myVar.delegate = MyViewcontroller()
myVar.DoSomething()
It doesn't print because the delegate is nil right after you set it. The reason for this is simple: no instance owns it (the reference count is zero). No one owns delegate because you declared it a weak property of OtherClass. Try establishing an ownership, e.g.
var myVar = OtherClass()
let viewController = MyViewController()
myVar.delegate = viewController
Even though delegate is weak, it will now print Something done again.
Declaring delegates as weak makes sense because it prevents circular references causing delegate to never be release in memory ā that's a whole different story though ā check how reference counting works, then you will understand why this is a good practice.
I can't get didReceiveApplicationContext to be called. Any ideas?
InterfaceController:
import WatchKit
import Foundation
import WatchConnectivity
class InterfaceController: WKInterfaceController, WCSessionDelegate {
#IBOutlet var colorLabel: WKInterfaceLabel!
private let session: WCSession? = WCSession.isSupported() ? WCSession.defaultSession() : nil
override init() {
super.init()
session?.delegate = self
session?.activateSession()
}
override func awakeWithContext(context: AnyObject?) {
super.awakeWithContext(context)
}
func session(session: WCSession, didReceiveApplicationContext applicationContext: [String : AnyObject]){
let colors : String = applicationContext["color"] as! String
colorLabel.setText(colors)
NSLog("session did receive application context")
}
}
I've been following along with this tutorial: http://www.kristinathai.com/watchos-2-how-to-communicate-between-devices-using-watch-connectivity/
No NSLog or setting of the colorLabel happens. No idea what I'm missing. Thanks!
This seems to be a typical 'development phase' problem!
The WCSession.defaultSession.applicationContext is buffered on the iOS device, so it is only transferred to the watch (extension) once if it doesn't change.
This lead to the strange finding, that watch extensions 'didReveiveApplicationContext:' seems not to be called, when WCSession.defaultSession.updateApplicationContext is called in the iOS app again. (Try to call WSSession.defaultSession.receivedApplicationContext in the extension to find, that the earlier transferred context is in fact available)!
In test situations, it is very helpful to add a 'changer' object to the context dictionary (like an UUID object, or - maybe even better - a NSDate.date). This will ensure, that the context has changed (compared to the buffered one) and gets transferred again (leading to a call to didReceiveApplicationContext) :-)
NSError* error = nil;
[WCSession.defaultSession updateApplicationContext:#{ #"yourKey" : #"your content",
#"forceTransfer" : NSDate.date }
error:&error];
And: Don't forget to remove this in the production version of your app, as - of course - this leads to unneeded data transfer between your app and your watch extension!
PS: The checked answer solves this problem by creating a new app. And flushing all buffers this way...
I had the same problem. In my case it helped to just close both simulators and then run the Watch scheme. This opens up both simulators again in connected state.
Hoping it helps!
I copied the above code into a new watch app and it works fine. The error must lay on the sending side. Are you certain the code in the iOS app is being called? I presume you are using Xcode and two simulators, one for the iOS app and one for the WatchApp.
The code on the iOS side is not run unless you open the app on the phone simulator. Where and how on the iOS side are you issuing the updateAppContext call?
In my test, this is all that I added to my ViewController.swift on the iOS side (This code will not be triggered until I start the iOS app on my iPhone.)
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let session = WCSession.defaultSession()
session.delegate = self
session.activateSession()
do {
try session.updateApplicationContext( ["color" : "Red" ])
} catch _ {
}
}
For me, when doing some testing/debugging i tried to fire updateApplicationContext in AppDelegate. This caused the didReceiveApplicationContext not being called.
Moving this logic to a later point, like in a UIViewController made it work for me at least.
Check the session to make sure its isPaired and watchAppInstalled properties are both YES. It seems like updating the shared context while these are NO will not work.
I was having this issue. Made a change to not update the context when either condition was NO. Added an implementation of sessionWatchStateDidChange:, and if both conditions were YES, updated the context. It worked.
I suspect this in combination with another issue where the phone will not send the context if the data is not different causes the "never updating" issue. A workaround of passing a "uuid" did help but I suspect the above is a better fix.
In my case, I used the following code to send my application context:
do {
try session.updateApplicationContext(applicationContext)
} catch let error {
throw error
}
and neither didReceiveApplicationContext was called, nor an error was thrown.
My problem was that applicationContext contained a custom object, whereas only property list items are allowed.
The strange point is that no error was thrown.
Here is my AppDelegate.swift. I implement the applicationShouldTerminate protocol from NSApplication. Which answer I give depends on the status of is.Started in the mainWindowController. (This is the SpeakLine example from Cocoa Programming for OS X: The Big Nerd Ranch Guide 5/eāI'm trying to take the example one step further and keep the program from being allowed to quit while the talking is going on.)
What I want to do is change TerminateReply.TerminateCancel to TerminateReply.TermianteLater and then send NSApplication the replyToApplicationShouldTerminate(true) signal when the talking is done. As it stands now in the MainControllerWindow.swift class I have a function set up to handle state changes in the Speech Synthesizer and that's where I want to call it.
import Cocoa
#NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
var mainWindowController: MainWindowController?
func applicationDidFinishLaunching(aNotification: NSNotification) {
let mainWindowController = MainWindowController()
mainWindowController.showWindow(self)
self.mainWindowController = mainWindowController
}
func applicationShouldTerminate(sender: NSApplication) -> NSApplicationTerminateReply {
if (mainWindowController!.isStarted) {
return NSApplicationTerminateReply.TerminateCancel
} else {
return NSApplicationTerminateReply.TerminateNow
}
}
}
The trouble is, when I put it here, I get an error.
var isStarted: Bool = false {
didSet {
updateButtons()
NSApplication.replyToApplicationShouldTerminate(true)
}
}
it tells me I can't use a bool. It also tells me I can't use an Objective C bool when I try to put YES. How do I tell NSApplication it's OK to quit now?
I believe you should change
NSApplication.replyToApplicationShouldTerminate(true)
to
NSApplication.sharedApplication().replyToApplicationShouldTerminate(true)
since replyToApplicationShouldTerminate is a instance method rather then a class method.
my tow cents for swift 5.x:
NSApplication.shared.reply(toApplicationShouldTerminate: true)