Swift and #objc methods: How do I transform a method so that it can be represented by #objc? - objective-c

As Swift is my first programming language and also seeing that I have no Objective C experience...
I'm having difficulty understanding #objc in relation to methods.
How do I use the #objc syntax to conform to my methods?
Is there another way to select a method without using the #selector syntax?
Here is the code that I'm having difficulty with(mainly the #objc attempt at the startGame method):
import UIKit
#objc class ViewController: UITableViewController {
var allWords = [String]()
var usedWords = [String]()
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.rightBarButtonItem =
UIBarButtonItem(barButtonSystemItem: .add, target: self, action:
#selector(promptForAnswer))
navigationItem.leftBarButtonItem = UIBarButtonItem(title: "New
Word", style: .plain, target: self, action: #selector(startGame))
if let startWordsURL = Bundle.main.url(forResource: "start",
withExtension: "txt") {
if let startWords = try? String(contentsOf: startWordsURL) {
allWords = startWords.components(separatedBy: "\n")
}
}
if allWords.isEmpty {
allWords = ["silkworm"]
}
#objc func startGame() {
title = allWords.randomElement()
usedWords.removeAll(keepingCapacity: true)
tableView.reloadData()
{
startGame()
}

A few observations:
You do not need #objc in your view controller declaration.
The two action/selector methods should bear #objc qualifier.
I would suggest that you give these two methods descriptive names that clearly indicate that they are called when the user taps on a particular button, e.g.:
#objc func didTapNewWord(_ sender: UIBarButtonItem) {
...
}
#objc func didTapAdd(_ sender: UIBarButtonItem) {
...
}
Note, I also added a parameter to these methods. That makes it entirely unambiguous that they are button handlers. You do not need to do that, but now you can glance at the code and immediately grok what the method is for.
Obviously, you will change the code that adds these target actions accordingly:
navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .add,
target: self,
action: #selector(didTapAdd(_:)))
navigationItem.leftBarButtonItem = UIBarButtonItem(title: "New Word",
style: .plain,
target: self,
action: #selector(didTapNewWord(_:)))
Be careful with the placement of braces. Swift allows you to declare functions inside functions. So make sure that these selector methods are instance methods of the view controller, and not, for example, private functions declared inside another function (i.e. viewDidLoad).
If you start to lose track of the braces, you can select all the code in this file and press control+i (or in Xcode menus, “Editor” » “Structure” » “Re-Indent”). If you have missing braces somewhere, the re-indentation of the code will make this jump out at you.
So pulling that together, you get something like:
// ViewController.swift
import UIKit
class ViewController: UITableViewController {
var allWords = [String]()
var usedWords = [String]()
override func viewDidLoad() {
super.viewDidLoad()
configureButtons()
fetchData()
}
}
// MARK: - Actions
extension ViewController {
#objc func didTapNewWord(_ sender: UIBarButtonItem) {
startGame()
}
#objc func didTapAdd(_ sender: UIBarButtonItem) {
...
}
}
// MARK: - UITableViewDataSource
extension ViewController {
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
...
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
...
}
}
// MARK: - Private utility methods
private extension ViewController {
func configureButtons() {
navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .add,
target: self,
action: #selector(didTapAdd(_:)))
navigationItem.leftBarButtonItem = UIBarButtonItem(title: "New Word",
style: .plain,
target: self,
action: #selector(didTapNewWord(_:)))
}
func fetchData() {
guard
let startWordsURL = Bundle.main.url(forResource: "start", withExtension: "txt"),
let startWords = try? String(contentsOf: startWordsURL).components(separatedBy: "\n"),
!startWords.isEmpty
else {
allWords = ["silkworm"]
return
}
allWords = startWords.filter { !$0.isEmpty }
}
func startGame() {
title = allWords.randomElement()
usedWords.removeAll(keepingCapacity: true)
tableView.reloadData()
}
}
A few final observations on my code sample (not directly related to your question, but just to explain why structured it like I did):
I like to put methods into extensions, so that they are in logical groups. This makes it easier to follow what is going on at a glance. You can also collapse/expand these extensions so that while you are editing, you can focus on the relevant code.
The MARK comments just puts nice section headers in the Xcode jump bar, again, making it easier to jump about in one’s code.
I personally don't put anything in the action methods except a call to some method with the “business logic”. This separates the “view” code (the handling of the button) from the business logic. Some day, you may start using view models or presenter objects, so embracing this separation of responsibilities now will make that eventual transition easier. It will also make it easier to write unit tests when you get around to that (e.g. you write unit tests for the "start game" logic, not not the tapping of a button).

I think you have syntax error in the #objc method. It should be:
#objc
func functionName() {
}
for you it will be:
#objc
func startGame() {
title = allWords.randomElement()
usedWords.removeAll(keepingCapacity: true)
tableView.reloadData()
}

Related

Can't get a Navigation item with CalendarKit

I've tried to get it to work like so:
class CalendarViewController: DayViewController {
override func viewDidLoad() {
super.viewDidLoad()
title = "CalendarKit Demo"
navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Dark",
style: .done,
target: self,
action: #selector(changeStyle))
navigationItem.leftBarButtonItem = UIBarButtonItem(title: "Change Date",
style: .plain,
target: self,
action: #selector(presentDatePicker))
navigationController?.navigationBar.isTranslucent = false
dayView.autoScrollToFirstEvent = true
reloadData()
}
#objc func changeStyle() {
print("clicked change style")
}
#objc func presentDatePicker() {
print("clicked date picker")
}
override func eventsForDate(_ date: Date) -> [EventDescriptor] {
let models = [Happening(startDate: Date(), endDate: Date(timeInterval: 3600, since: Date()), title: "Test Event", location: "on mother earth")]
var events = [Event]()
for model in models {
let event = Event()
event.startDate = model.startDate
event.endDate = model.endDate
let info = [model.title, model.location]
event.text = info.reduce("", {$0 + $1 + "\n"})
events.append(event)
}
return events
}
}
struct Happening {
let startDate: Date
let endDate: Date
let title: String
let location: String
init (startDate: Date, endDate: Date, title: String, location: String) {
self.startDate = startDate
self.endDate = endDate
self.title = title
self.location = location
}
}
Calendar shows up but I'm neither getting a title nor navigation items.
Looks like this for me:
What am I doing wrong here?
Many thanks for your help!
Question on the side:
Didn't yet figure out how (or if possible at all) to work with it in interface builder, to e.g. add a custom navigation element at the top when integrating it into another app. Is that possible?
starting from the last question: the Interface Builder is not currently supported. I recommend creating your CalendarController in code. Some features might work with the Interface Builder, although their support is not guaranteed.
The missing link is that the CalendarController is not embedded inside an instance of the UINavigationController. You can do so in your AppDelegate.swift or SceneDelegate.swift file:
import UIKit
import CalendarKit
#UIApplicationMain
final class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
window = UIWindow(frame: UIScreen.main.bounds)
window?.backgroundColor = UIColor.white
window?.makeKeyAndVisible()
let dayViewController = CalendarViewController() // Create a view controller
// Now create a NavigationController with CalendarController embedded inside
let navigationController = UINavigationController(rootViewController: dayViewController)
window?.rootViewController = navigationController
return true
}
}
Let me know, if this helps you integrate the CalendarKit. Also, I'd appreciate having a reproducible test project, so that I could investigate the problem myself.

NSAttributedString swift extension is not accessable in Objective C [duplicate]

I just finished upgrading a mixed language project (objective-c and Swift) from Swift 3 to Swift 4.
Everything seemed to go well except all of my Swift extensions are no longer accessible in objective-c. I can't figure out how to get any Swift extension to show up in objective-c. I've tried searching, but I can't find any mention of changes to extensions in Swift 4 except for loosening up the private scope.
All of these extensions were accessible from Objective-c in Swift 3, so there are no incompatible types (structs or non-raw enums).
The extensions are marked public.
The extensions are part of the same target and in the same project as the objective-c files.
Yes, I have imported "ProjectName-Swift.h" in the relevant objective-c files.
Other compatible Swift classes do show up. It seems just the extensions are missing.
I've tried marking each func public as well.
For example, the following extension used to be available to objective-c code when I was using Swift 3:
public extension UIAlertController {
class func alert(_ title: String? = nil, message: String? = nil) -> UIAlertController {
return UIAlertController(title: title, message: message, preferredStyle: .alert)
}
#discardableResult func action(_ action: String) -> UIAlertController {
self.addAction(UIAlertAction(title: action, style: .default, handler: nil))
return self
}
#discardableResult func action(_ action: String, style: UIAlertActionStyle = .default, onSelected: ((UIAlertAction) -> Void)? = nil) -> UIAlertController {
self.addAction(UIAlertAction(title: action, style: style, handler: onSelected))
return self
}
#discardableResult func cancel(_ action: String? = "Cancel", onCancel: ((UIAlertAction) -> Void)? = nil) -> UIAlertController {
self.addAction(UIAlertAction(title: action, style: .cancel, handler: { alert in
onCancel?(alert)
}))
return self
}
#discardableResult func destructive(_ action: String, onDestruct: #escaping ((UIAlertAction) -> Void)) -> UIAlertController {
self.addAction(UIAlertAction(title: action, style: .destructive, handler: { action in
onDestruct(action)
}))
return self
}
func presentOn(_ viewController: UIViewController, animated: Bool = true) {
viewController.present(self, animated: animated, completion: nil)
}
}
A super simple solution is to just add #objc public extension to the declaration of each Swift extension.

how to change dock icon using setContentView to display one big character in mac os x

I want to change the dock icon of an app into one big character like an "A" or "B" for example using swift or objective C
import Cocoa
#NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
#IBOutlet weak var window: NSWindow!
#IBOutlet weak var dockView: NSView!
#IBOutlet weak var dockText: NSTextField!
let appDockTile = NSApplication.sharedApplication().dockTile
func prepareDock(){
appDockTile.contentView = dockView
appDockTile.display()
}
func changeText(){
dockText.stringValue = "B"
appDockTile.display()
}
func applicationDidFinishLaunching(aNotification: NSNotification) {
// Insert code here to initialize your application
prepareDock()
}
func applicationWillTerminate(aNotification: NSNotification) {
// Insert code here to tear down your application
}
#IBAction func btnChangeText(sender: AnyObject) {
changeText()
}
}
my two cents for OSX swift 4.x:
(make it flash..)
...
self.HeartBeatTimer = Timer.scheduledTimer(withTimeInterval: DELTA_T, repeats: true, block: { (t: Timer) in
let name = colored ? "heartbeat" : "heartbeat_red"
let image = NSImage(named: name)
let appDockTile = NSApplication.shared.dockTile
appDockTile.contentView = NSImageView(image: image!)
appDockTile.display()
}

What is the Swift equivalent of respondsToSelector?

I've googled but not been able to find out what the swift equivalent to respondsToSelector: is.
This is the only thing I could find (Swift alternative to respondsToSelector:) but isn't too relevant in my case as its checking the existence of the delegate, I don't have a delegate I just want to check if a new API exists or not when running on the device and if not fall back to a previous version of the api.
As mentioned, in Swift most of the time you can achieve what you need with the ? optional unwrapper operator. This allows you to call a method on an object if and only if the object exists (not nil) and the method is implemented.
In the case where you still need respondsToSelector:, it is still there as part of the NSObject protocol.
If you are calling respondsToSelector: on an Obj-C type in Swift, then it works the same as you would expect. If you are using it on your own Swift class, you will need to ensure your class derives from NSObject.
Here's an example of a Swift class that you can check if it responds to a selector:
class Worker : NSObject
{
func work() { }
func eat(food: AnyObject) { }
func sleep(hours: Int, minutes: Int) { }
}
let worker = Worker()
let canWork = worker.respondsToSelector(Selector("work")) // true
let canEat = worker.respondsToSelector(Selector("eat:")) // true
let canSleep = worker.respondsToSelector(Selector("sleep:minutes:")) // true
let canQuit = worker.respondsToSelector(Selector("quit")) // false
It is important that you do not leave out the parameter names. In this example, Selector("sleep::") is not the same as Selector("sleep:minutes:").
There is no real Swift replacement.
You can check in the following way:
someObject.someMethod?()
This calls the method someMethod only if it's defined on object someObject but you can use it only for #objc protocols which have declared the method as optional.
Swift is inherently a safe language so everytime you call a method Swift has to know the method is there. No runtime checking is possible. You can't just call random methods on random objects.
Even in Obj-C you should avoid such things when possible because it doesn't play well with ARC (ARC then triggers warnings for performSelector:).
However, when checking for available APIs, you can still use respondsToSelector:, even if Swift, if you are dealing with NSObject instances:
#interface TestA : NSObject
- (void)someMethod;
#end
#implementation TestA
//this triggers a warning
#end
var a = TestA()
if a.respondsToSelector("someMethod") {
a.someMethod()
}
Update Mar 20, 2017 for Swift 3 syntax:
If you don't care whether the optional method exists, just call delegate?.optionalMethod?()
Otherwise, using guard is probably the best approach:
weak var delegate: SomeDelegateWithOptionals?
func someMethod() {
guard let method = delegate?.optionalMethod else {
// optional not implemented
alternativeMethod()
return
}
method()
}
Original answer:
You can use the "if let" approach to test an optional protocol like this:
weak var delegate: SomeDelegateWithOptionals?
func someMethod() {
if let delegate = delegate {
if let theMethod = delegate.theOptionalProtocolMethod? {
theMethod()
return
}
}
// Reaching here means the delegate doesn't exist or doesn't respond to the optional method
alternativeMethod()
}
If the method you are testing for is defined as an optional method in a #objc protocol (which sounds like your case), then use the optional chaining pattern as:
if let result = object.method?(args) {
/* method exists, result assigned, use result */
}
else { ... }
When the method is declare as returning Void, simply use:
if object.method?(args) { ... }
See:
“Calling Methods Through Optional Chaining”
Excerpt From: Apple Inc. “The Swift Programming Language.”
iBooks. https://itun.es/us/jEUH0.l
It seems you need to define your protocol as as subprotocol of NSObjectProtocol ... then you'll get respondsToSelector method
#objc protocol YourDelegate : NSObjectProtocol
{
func yourDelegateMethod(passObject: SomeObject)
}
note that only specifying #objc was not enough. You should be also careful that the actual delegate is a subclass of NSObject - which in Swift might not be.
For swift3
If you just want to call the method, run the code below.
self.delegate?.method?()
Functions are first-class types in Swift, so you can check whether an optional function defined in a protocol has been implemented by comparing it to nil:
if (someObject.someMethod != nil) {
someObject.someMethod!(someArgument)
} else {
// do something else
}
In Swift 2,Apple introduced a new feature called API availability checking, which might be a replacement for respondsToSelector: method.The following code snippet comparison is copied from the WWDC2015 Session 106 What's New in Swift which I thought might help you,please check it out if you need to know more.
The Old Approach:
#IBOutlet var dropButton: NSButton!
override func awakeFromNib() {
if dropButton.respondsToSelector("setSpringLoaded:") {
dropButton.springLoaded = true
}
}
The Better Approach:
#IBOutlet var dropButton: NSButton!
override func awakeFromNib() {
if #available(OSX 10.10.3, *) {
dropButton.springLoaded = true
}
}
For swift 3.0
import UIKit
#objc protocol ADelegate : NSObjectProtocol {
#objc optional func hi1()
#objc optional func hi2(message1:String, message2:String)
}
class SomeObject : NSObject {
weak var delegate:ADelegate?
func run() {
// single method
if let methodHi1 = delegate?.hi1 {
methodHi1()
} else {
print("fail h1")
}
// multiple parameters
if let methodHi2 = delegate?.hi2 {
methodHi2("superman", "batman")
} else {
print("fail h2")
}
}
}
class ViewController: UIViewController, ADelegate {
let someObject = SomeObject()
override func viewDidLoad() {
super.viewDidLoad()
someObject.delegate = self
someObject.run()
}
// MARK: ADelegate
func hi1() {
print("Hi")
}
func hi2(message1: String, message2: String) {
print("Hi \(message1) \(message2)")
}
}
Currently (Swift 2.1) you can check it using 3 ways:
Using respondsToSelector answered by #Erik_at_Digit
Using '?' answered by #Sulthan
And using as? operator:
if let delegateMe = self.delegate as? YourCustomViewController
{
delegateMe.onSuccess()
}
Basically it depends on what you are trying to achieve:
If for example your app logic need to perform some action and the delegate isn't set or the pointed delegate didn't implement the onSuccess() method (protocol method) so option 1 and 3 are the best choice, though I'd use option 3 which is Swift way.
If you don't want to do anything when delegate is nil or method isn't implemented then use option 2.
As I started to update my old project to Swift 3.2, I just needed to change the method from
respondsToSelector(selector)
to:
responds(to: selector)
I just implement this myself in a project, see code below. As mentions by #Christopher Pickslay it is important to remember that functions are first class citizens and can therefore be treated like optional variables.
#objc protocol ContactDetailsDelegate: class {
optional func deleteContact(contact: Contact) -> NSError?
}
...
weak var delegate:ContactDetailsDelegate!
if let deleteContact = delegate.deleteContact {
deleteContact(contact)
}
another possible syntax by swift..
if let delegate = self.delegate, method = delegate.somemethod{
method()
}
I use guard let else, so that can do some default stuff if the delegate func is not implemented.
#objc protocol ViewController2Delegate: NSObjectProtocol {
optional func viewController2(controller: ViewController2, didSomethingWithStringAndReturnVoid string: String)
optional func viewController2(controller: ViewController2, didSomethingWithStringAndReturnString string: String) -> String
}
class ViewController2: UIViewController {
weak var delegate: ViewController2Delegate?
#IBAction func onVoidButtonClicked(sender: AnyObject){
if (delegate != nil && delegate!.respondsToSelector(Selector("viewController2:didSomethingWithStringAndReturnVoid:"))) {
NSLog("ReturnVoid is implemented")
delegate!.viewController2!(self, didSomethingWithStringAndReturnVoid: "dummy")
}
else{
NSLog("ReturnVoid is not implemented")
// Do something by default
}
}
#IBAction func onStringButtonClicked(sender: AnyObject){
guard let result = delegate?.viewController2?(self, didSomethingWithStringAndReturnString: "dummy") else {
NSLog("ReturnString is not implemented")
// Do something by default
return
}
NSLog("ReturnString is implemented with result: \(result)")
}
}
I guess you want to make a default implementation for delegate. You can do this:
let defaultHandler = {}
(delegate?.method ?? defaultHandler)()
Swift 3:
protocol
#objc protocol SomeDelegate {
#objc optional func method()
}
Object
class SomeObject : NSObject {
weak var delegate:SomeObject?
func delegateMethod() {
if let delegateMethod = delegate?.method{
delegateMethod()
}else {
//Failed
}
}
}
The equivalent is the ? operator:
var value: NSNumber? = myQuestionableObject?.importantMethod()
importantMethod will only be called if myQuestionableObject exists and implements it.

Handle Double-click Mouse Event and Return Pressed for NSTableView

OK, what I need is pretty straightforward, though I can still find nothing specific.
I want to be able to :
track double-click events
track when the NSTableView is in focus, and the "Return" key is pressed.
How would you go about it?
P.S. I've had a look into NSTableViewDelegate specification, but I can't find anything useful.
For double click you need to do just these :
-(void)awakeFromNib{
[self.tableView setDoubleAction:#selector(thisMethod)];
//And if you wish to take selector dynamically, I guess you know how to do :)
}
-(void)thisMethod{
NSLog(#"double clicked");
}
For the return event, subclass your NSTableView and override keyDown:
Swift 5.x:
override func keyDown(with event: NSEvent) {
if event.characters?.count == 1 {
let character = event.keyCode
switch (character) {
// 36 is return
case UInt16(36):
print("return: \(event)")
default:
print("any other key: \(event)")
}
} else {
super.keyDown(with: event)
}
}
There is a way to handle the Return key without having to manually check for its key code.
I'll show the answer in Swift, but it can be applied in Objective-C as well.
First, override keyDown(with:) in your view controller subclass that controls the table view and call interpretKeyEvents(_:):
override func keyDown(with event: NSEvent) {
interpretKeyEvents([event])
}
Second, in the same view controller subclass, override insertNewLine(_:). This is called when the user presses the Return key:
override func insertNewLine(_ sender: Any?) {
// Add your logic to handle the Return key being pressed
}
Here's an example:
class TableViewController: NSViewController {
#IBOutlet var tableView: NSTableView!
override func keyDown(with event: NSEvent) {
interpretKeyEvents([event])
}
override func insertNewLine(_ sender: Any?) {
guard tableView.selectedRow >= 0 else { return }
print("Pressed Return on row \(tableView.selectedRow)")
}
}