Does exist any solution to make reusable protocol extension for more classes with selectors which would point to itself?
For example I am trying to make extension TimerHelper which adds appropriate functions to work with NSTimer. I found this:
https://forums.developer.apple.com/thread/26983
https://forums.developer.apple.com/message/49465#49465
But solution seems a bit twisty...
What I am trying to make in code, which doesn't work of course, is something like this:
protocol TimerHelper {
var timer:NSTimer { get set }
}
extension TimerHelper {
func startTimer() {
timer = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: self.updateTimer(), userInfo: nil, repeats: true)
}
func updateTimer() {
print("Timer updated.")
}
}
class ViewController: UIViewController, TimerHelper {
var timer: NSTimer = NSTimer()
func start() {
startTimer()
}
}
Thanks
Never do:
... = NSTimer()
Instead create the variable as an optional. You want to invalidate and destroy the timer, and you never want a timer that hasn't been configured properly or invalidated.
Your extension is inappropriate because it deals with aspects not covered by the protocol itself. You should have 2 protocols, where the second protocol extends the first and is called something like TimerActivation. It defines the functions and the extension implements them.
This doesn't change what you need to do in the VC to use the timer, but it makes your type system clean, effective, reusable and extensible.
For the self referential part you need to look at #selector, I haven't tried it inside a protocol before, should be interesting...
It may be wiser to supply the selector, or an invocation, to the start function, because there is little point in a protocol extension implementing the selector when the protocol is so general. But, I suppose you may want to add other child protocols with extensions which provide other implementations so you can mixin functionality, interesting idea...
Related
I am trying to use a selector to see if a certain protocol can perform an action. When I try it like this:
protocol Test {
func hello()
func goodBye(a: String)
}
class Tester: NSObject, Test {
override init() {}
func hello() { }
func goodBye(a: String) { }
}
let a: Test = Tester()
let result = a.responds(to: Selector("goodByeWithA:"))
In this case, result evaluates to false.
But if I add the #objc tag to the protocol, it evaluates as true.
#objc protocol Test {
func hello()
func goodBye(a: String)
}
Why is this?
On a side note, I know that it is now recommended to use the #selector syntax and to move away from using strings, but for various reasons, I have to use a string in this case.
EDIT: This only started happening once I migrated my project to Swift 4.2
By default Swift generates code that is only available to other Swift code, but if you need to interact with the Objective-C runtime – all of UIKit, for example – you need to tell Swift what to do.
That’s where the #objc attribute comes in: when you apply it to a class or method it instructs Swift to make those things available to Objective-C as well as Swift code. So, any time you want to call a method from a UIBarButtonItem or a Timer, you’ll need to mark that method using #objc so it’s exposed – both of those, and many others, are Objective-C code.
Don’t worry: if you forget to add #objc when it’s needed, your code simply won’t compile – it’s not something you can forget by accident and introduce a bug.
I have a very general view that is created and used by multiple view controllers with 2 buttons, one of them sometimes is hidden depending on the needs.
This view delegates the tap of the two buttons.
protocol TheViewsDelegate: class {
func button1Tapped()
func button2Tapped()
}
Let's put that ViewControllerA creates this view and needs both buttons, this view controller will have to implement both delegate functions and do something inside it.
Now let's say that ViewControllerB creates the same view but just needs one of the buttons. This view controller will have to still implement button2Tapped() even though it will never be called and used.
Is there a way to handle this nicely? I imagine there's a nice solution where I don't need to implement this button2Tapped() if I don't need it.
I thought about making it optional by giving a default implementation but I don't like this solution, I like (and I think it's a good practice) the compiler giving me an error when a method it's not implement. Someone can jump into the project and not realising that he/she hasn't implement button2Tapped when needs to be implemented.
Note: This is a very simple example just to illustrate my question, but the question is more broad as in what to do when a function in a delegate is defined by controller that don't need to implement it.
I believe you want to use:
optional func
There are a couple of ways of declaring a protocol method as optional, one is using optional func which requires using #objc syntax, which a lot of programmers apparently don't like, and the other requires declaring an empty body in the extension of a protocol (which makes it optional by default).
protocol TheViewsDelegate: AnyObject {
func button1Tapped()
}
extension TheViewsDelegate {
func button2Tapped() {}
}
class SomeViewController: UIViewController, TheViewsDelegate {
func button1Tapped() {
// implement
}
}
By giving the protocol an empty body inside an extension of the protocol, that method is optional and does not need to be implemented by conforming objects.
For comparison, the alternative:
#objc protocol TheViewsDelegate: AnyObject {
func button1Tapped()
#objc optional func button2Tapped()
}
class SomeViewController: UIViewController, TheViewsDelegate {
func button1Tapped() {
// implement
}
}
Example: I have a SpeechSynthesizer class that needs to update something in my UIView when it’s done uttering a piece of text. Since the SpeechSynthesizer class conforms to protocol AVSpeechSynthesizerDelegate, it is the one that receives the didFinish signal when the uttering has been completed. The idea here is to keep the ViewController from having too many delegate methods and a long list of protocols to conform to. The workaround I found was to have the ViewController passed in as a SpeechSynthesizer initialization parameter. This way I get to access the ViewController connected to the UIView I want to update from inside the SpeechSynthesizer class. The thing I don’t like about it is that it looks kind of ugly to have the ViewController passed in as a parameter to every single class that needs to use it. So I wonder, which other way I could accomplish this.
I suppose another way to ask the question is: How can I make the function
private func speechSynthesizer(_ synthesizer: AVSpeechSynthesizer, didFinish utterance: AVSpeechUtterance)
return something to a ViewController since it's not "called" by it?
I added a reply on Quora. Copying it here:
After doing some research and testing on code of my own here are 2 solutions to this problem.
Solution 1: The Delegate Pattern:
Create a custom delegate protocol in the ViewController
protocol ViewControllerDelegate:class {
func getViewLayer() -> CALayer
}
The ViewController must conform to this newly created protocol and therefore implement all the functions defined by it, so somewhere in the class ViewController you add:
public func getViewLayer() -> CALayer {
return self.view.layer
}
Then on my custom class, ReadTextMachine, I added a variable of the ViewControllerDelegate type
private weak var viewControllerDelegate: ViewControllerDelegate?
The variable must be weak and protocol must be of type class in order to solve a “retain cycle” problem (since both the custom class and the ViewController will point to each other)
You’ll notice now that the function call inside the ViewController is already “callable” from the custom class, so in my ReadTextMachine I added:
let viewLayer = self.viewControllerDelegate?.getViewLayer()
self.cameraPreview = CameraPreview(session: self.camera.getSession(), container: viewLayer!)
self.cameraPreview?.addPreview()
In the above case, my CameraPreview (yes, a 3rd class in this example) simply adds a camera preview layer on the UIView. For that it needed access to the main View’s layer.
The above code still doesn’t work because our original viewController’s instance hasn’t been passed as reference anywhere in our code. For that we add the following function in ReadTextMachine:
public func setViewControllerDelegate(viewController: ViewController) { // call this from the ViewController so that ViewController can be accessed from here.
self.viewControllerDelegate = viewController
}
The above piece of code will have to be called from the ViewController, after we instantiate our custom class (ReadTextMachine), so that the viewControllerDelegate inside it points to the ViewController. So in our ViewController.swift:
operatingMode = ReadTextMachine()
operatingMode.setViewControllerDelegate(viewController: self)
Another example and explanation can be found in this video from LetsBuildThatApp. I derived my solution mostly from it.
My current app in development applying the above solution can be found here: agu3rra/World-Aloud
Solution 2: Notifications and Observers pattern
This one is a bit easier to understand and follow. The general idea is to have your custom class broadcast a message which triggers a function call on your ViewController since it has an observer setup, waiting to hear that message.
So to give an example, in the context I used it, I have a CameraCapture class which uses AVFoundation to capture a photo. The capture photo trigger cannot immediately return an image, since iOS has a set of steps to execute before actually generating an image. I wanted my ReadTextMachine to resume an activity after CameraCapture had a photo available. (To apply this in the context of the CustomClass triggers ViewController event is basically the same, since both are actual classes in an iOS app as well).
So the 1st thing I did was create a broadcast function since I would use it in many places in my app. I simply placed it in a Utilities.swift file in the Xcode project.
public func broadcastNotification(name: String) {
let notification = Notification.Name(rawValue: name)
NotificationCenter.default.post(name: notification, object: nil)
}
The above function takes a string, which must be a unique notification identifier, and broadcasts it thru NotificationCenter.
In my CameraCapture class, I added a static constant to reference the unique identifier of the message:
static let NOTIFY_PHOTO_CAPTURED = "agu3rra.worldAloud.photo.captured"
For those who know AVFoundation, a photo is available when event didFinishProcessingPhoto gets executed, so at the end of that I added:
broadcastNotification(name: CameraCapture.NOTIFY_PHOTO_CAPTURED)
The above is a call to my previously defined utility function.
For my ReadTextMachine class to be able to catch that notification, I added the following on its init() and deinit routines:
override init() {
super.init()
// Setup event observers
let notification1 = Notification.Name(rawValue: CameraCapture.NOTIFY_PHOTO_CAPTURED)
NotificationCenter.default.addObserver(self,
selector: #selector(self.processingDoneTakingPhoto),
name: notification1,
object: nil)
}
deinit {
NotificationCenter.default.removeObserver(self) // cleanup observer once instance no longer exists
}
Removing the observer is important at deinit so that when your object is deallocated from memory, the observer isn’t left lingering around. The above configured observer triggers a function call inside ReadTextMachine:
#IBAction private func processingDoneTakingPhoto() {
// does my stuff
}
That’s it! Again, the entire Xcode project can be downloaded from my project’s Git repository: agu3rra/World-Aloud
Hope this can be of use to others.
Cheers!
In my code file MyItemVC.swift I have defined the following class and method:
class MyItemVC: UIViewController, UITextViewDelegate {
var timer = NSTimer()
func cycleTimer(toggleOn: Bool) {
if toggleOn == true {
// Timer calls the replaceItem method every 3 seconds
timer = NSTimer.scheduledTimerWithTimeInterval(3, target: self, selector: Selector("replaceItem"), userInfo: nil, repeats: true)
} else {
timer.invalidate() // stop the timer
}
}
}
Elsewhere in this class, I call cycleTimer(true) to start the timer and cycleTimer(false) to stop it.
Now, I also want to use the usual methods in my AppDelegate.swift code file to start and stop the timer when the app moves from active to inactive state. But I'm having trouble calling the cycleTimer method from that class.
I found an answer on Stack Overflow that suggested I could call it like this:
func applicationWillResignActive(application: UIApplication) {
MyItemVC.cycleTimer()
}
But I also need to pass in an argument. So I tried calling it like this:
func applicationWillResignActive(application: UIApplication) {
MyItemVC.cycleTimer(true)
}
But I got this error:
Cannot invoke 'cycleTimer' with an argument list of type '(Bool)'
How can I call this method from the AppDelegate methods while passing in an argument?
Thanks for the help. I realize this must be a very basic question but I'm new to programming and trying to teach myself using Swift. An answer using Swift rather than Obj-C would be greatly appreciated.
You need to use class function to be able to use it this way.
class func cycleTimer(toggleOn: Bool) {
However, I'm not sure about thread safety.
The function you have specified is not a class function. Add class keyword before func keyword.
The changed code:
class func cycleTimer
Note: In the previous versions of Swift you must use the following code (and also in C or other languages):
static func cycleTimer
Is it possible to access and run a specific method/function from another class that can change dynamically as the app is run?
I’ll try to simplify the problem as much as possible.
SelectionPage.swift
Choose which class needs to be selected and accessed using an UIPickerView - 10 possible selections (Class1, Class2, Class3,…, Class10).
Class1.swift, Class2.swift, … Class10.swift
Each of the 10 classes has a single method that has exactly the same name but is programmed differently:
func runOnUpdate() { }
GameSceneViewController.swift
When a selection is made on the SelectionPage, the app segues to a GameSceneViewController where the specific selected function is run every time the update function is run:
override func update(currentTime: CFTimeInterval)
{
// run runOnUpdate() function here from selected class
}
Inside the update function, I would like to execute the runOnUpdate( ) function depending on which class was selected on the SelectionPage. Is this possible? Ideally I'd like to be able to assign the specific class/method in the:
override func didMoveToView(view: SKView)
so that I can access in other functions as well.
I’ve looked into lazy instantiation, creating delegates for each of the classes, #objc(Class1), arrays of [AnyClass], typealias, global variables in structs, singletons etc. but I’m unable to figure out how to make this work.
It seems like a fairly common problem so any help would be greatly appreciated! Thank you in advance!
You were correct in trying delegates as this is a case where you should make a protocol and a delegate. The protocol requires the function. From there you set the delegate property to an instance of a class that conforms to that protocol and then you call delegate?.someFunction() to call the function on the given object.
class ViewController: UIViewController {
var delegate: Updatable?
override func viewDidLoad() {
super.viewDidLoad()
let foo = Foo()
delegate = foo
delegate?.runOnUpdate() // prints do something
}
}
protocol Updatable {
func runOnUpdate()
}
class Foo: NSObject, Updatable {
func runOnUpdate() {
println("do something")
}
}