Swift: Creating a function and calling it multiple times? - swift

I am very new to Swift and I can't find an answer to my qustion.
I am working on an app that will do a lot of the same functions but on separate button pushes. I could reduce my code and time updating greatly if I am able to write a function or action and just call it by name on a button push.
Does Swift have a way to do something like this:
Func doMath {
var answer = 1+1
var answer2 = 2+2
}
Func buttonPush {
call doMath
}

What you need is an IBAction. Using the Assistant view hold the control key and drag it into your code. Then change the connection type to Action and give it a useful name.
From inside your IBAction you can do any maths or call any maths function you need. Using functions to minimise code duplication is a good idea.
Here's an example of an Action that starts a timer.
#IBAction func play(sender: AnyObject) {
timer = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: Selector("result"), userInfo: nil, repeats: true)
}
I sugguest you do some examples that use IBAction.
I liked the Ray Wenderlich ones myself.
Here is some code for creating a function that takes two integer parameters. It's taken from the Apple developer documentation.
apple docs hopefully that's what you need.
func halfOpenRangeLength(start: Int, end: Int) -> Int {
return end - start
}

Related

SwiftUI - KV Observe completion from Combine does not get triggered

I am trying to build a VOIP app using lib called VailerSIPLib. As the library was built using Obj-C and heavily using NotificationCenter to to publish the changes the active states all over the place.
I currently at the CallView part of the project, I can manage to start, end, reject calls. However, I need to implement connectionStatus in the view which will give information about the call like duration, "connecting..", "disconnected", "ringing" etc.
The below code is all in CallViewModel: ObservableObject;
Variables:
var activeCall: VSLCall!
#Published var connectionStatus: String = ""
Initializer:
override init(){
super.init()
NotificationCenter.default.addObserver(self, selector: #selector(self.listen(_:)), name: Notification.Name.VSLCallStateChanged, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(self.buildCallView(_:)), name: Notification.Name.CallKitProviderDelegateInboundCallAccepted, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(self.buildCallView(_:)), name: Notification.Name.CallKitProviderDelegateOutboundCallStarted, object: nil)
}
Methods:
func setCall(_ call: VSLCall) {
self.activeCall = call
self.activeCall.observe(\.callStateText) { (asd, change) in
print("observing")
print("\(String(describing: change.oldValue)) to \(String(describing: change.newValue)) for \(call.callId)")
}
}
#objc func listen(_ notification: Notification) {
if let _ = self.activeCall {
print(self.activeCall.callStateText)
}
}
#objc func buildCallView(_ notification: Notification) {
print("inbound call")
self.isOnCall = true
}
Problem:
It prints out every thing except the completionBlock in setCall(_:). listen(_:) function validates that the state of the activeCall is changing and I would want to use that directly, however it does not work correct all the time. It should be triggered when the call is answered with callState value of .confirmed but sometime it does. This how I will know that it is time start the timer.
Other point is, in the example project of the VialerSIPLib they used self.activeCall.addObserver(_:) and it works fine. The problem for that is it throws a runtime error at the method something like didObservedValueChange(_:) and logs An -observeValueForKeyPath:ofObject:change:context: message was received but not handled.
Finally there is yellow warning at the activeCall.observe(_:) says
Result of call to 'observe(_:options:changeHandler:)' is unused
which I could not find anything related to it.
Finally there is yellow warning at the activeCall.observe(_:) says
Result of call to 'observe(_:options:changeHandler:)'
This is telling you what the problem is. The observe(_:options:changeHandler:) method is only incompletely documented. It returns an object of type NSKeyValueObservation which represents your registration as a key-value observer. You need to save this object, because when the NSKeyValueObservation is destroyed, it unregisters you. So you need to add a property to CallViewModel to store it:
class CallViewModel: ObservableObject {
private var callStateTextObservation: NSKeyValueObservation?
...
And then you need to store the observation:
func setCall(_ call: VSLCall) {
activeCall = call
callStateTextObservation = activeCall.observe(\.callStateText) { _, change in
print("observing")
print("\(String(describing: change.oldValue)) to \(String(describing: change.newValue)) for \(call.callId)")
}
}
You could choose to use the Combine API for KVO instead, although it is even less documented than the Foundation API. You get a Publisher whose output is each new value of the observed property. It works like this:
class CallViewModel: ObservableObject {
private var callStateTextTicket: AnyCancellable?
...
func setCall(_ call: VSLCall) {
activeCall = call
callStateTextTicket = self.activeCall.publisher(for: \.callStateText, options: [])
.sink { print("callId: \(call.callId), callStateText: \($0)") }
}
There's no specific reason to use the Combine API in your sample code, but in general a Publisher is more flexible than an NSKeyValueObservation because Combine provides so many ways to operate on Publishers.
Your error with addObserver(_:forKeyPath:options:context:) happens because that is a much older API. It was added to NSObject long before Swift was invented. In fact, it was added before Objective-C even had blocks (closures). When you use that method, all notifications are sent to the observeValue(forKeyPath:of:change:context:) method of the observer. If you don't implement the observeValue method, the default implementation in NSObject receives the notification and raises an exception.

Using an instance in a function

I’m new to Swift so I’ve been using the Swift Playgrounds app. On level 2 “Two Experts”, I initialized two experts:
let expert1 = Expert()
let expert2 = Expert()
What I wanted to do was create a function and pass whichever instance into it, access it’s methods etc, something like:
func actions(who: Item, distance: Int, turn: String) {
for 0 to distance {
who.moveforward()
}
ff turn == “Left” {
who.turnleft()
} else if turn == “Right” {
who.turnright()
}
}
Where who is expert1 or expert2.
I couldn’t find a way of doing this so had to write the same actions twice:
Func actions(who: String, distance: Int, turn:String) {
if who == “expert1” {
for 0 to distance {
expert1.moveforward()
} Etc
if who == “expert2” {
for 0 to distance {
expert2.moveforward()
} Etc
Is there a way of passing an instance into a function then perform certain actions if it’s of a particular class?
Since your experts is of type Expert, then the who parameter in your actions method should be of type Expert, if I understand the code correctly. Then you don't need two functions for each of the Experts. Let me know if I understood you correctly, and if it worked out.
Update
#Alexander mentioned that you can also have these methods in an extension on Expert, like so:
extension Expert {
func actions(distance: Int, turn: String) {
// Add method code here
}
}
When adding the method in an extension, every Expert object can use the method. So you could write expert1.actions(1, "Left") or something like that. Here's a link to the official Swift Programming Language guide about extensions.

Change value of type Bool by using string

I am making a little game, by improving my Swift language, so I am not familiar with all the functions of Swift. That's why I am asking this:
I have:
var Playedcardone: Bool = false
var Playedcardtwo: Bool= false
ect ect ect
my IBAction, this is hooked up with 52 cards:
#IBAction func Tappedoncard(card: UIButton) {
if tapped == true{ //nvm this
checkcard(card)
}
}
In a function, I have:
func checkcard(Card: UIButton) {
("Playedcard" + Card.currentTitle!) = true
}
Well as you may guess this function doesn't work. How can I reference to a var I made depending on the card that I called? When the card is ticked, the bool needs to change to true. I can do this by creating 52 vars, and 51 else if statements, but there should be a quicker option right? You help is welcome :).
/offtopic: I know I could used classes, to define my card value, UIIMageview and the bool itself. I just don't understand classes optimal, maybe for my next app I will use. If you want to see my code, and you want to give me a hand, just ask and I will drop the code here.
You cannot access a variable using a string as you are trying to do. You will need to preform some type of conditional checking to update the variables like so.
func checkcard(Card: UIButton) {
if Card.currentTitle! == "one" {
Playedcardone = true
} else if Card.currentTitle! == "two" {
Playedcardtwo = true
}
}
There is a plethora of different solutions you could use - for example why not make two seperate IBActions for each card? Also as a matter of convention I would suggest using lowercase letters to start your variables.

Writing better Swift code

The title is on how to write better Swift code but, my real question is really what is better if I create a function, then call it when the button is clicked vs I write what I want to happen once the button is clicked .
Eg.
var thing = 0
func hi(){
// Do something
thing++
}
#IBAction func somethingHi(sender: AnyObject) {
println(hi)
}
vs
var thing = 0
#IBAction func othersomethingHI(sender: AnyObject) {
thing++
println(thing)
}
I know both do the same thing but, is one "better" written than the other?
If an IBAction does something that you might want to do at some other time then it should call a function that performs that action, so that "others" can effect the same thing without duplicating code. If not, implement it solely in the action.
If you're code is short and won't be reused, you can just put it inside the #IBAction func function.

Alternate way (using Selector) to set up notification observer

I have successfully setup notification observer using the code below:
func setupNotification {
NSNotificationCenter.defaultCenter().addObserver(self, selector: "action:", name: notificationString, object: nil)
}
func action(notification: NSNotification) {
// Do something when receiving notification
}
However, I am not interested in the coding style above since there might be a chance that I could type or copy/paste wrong method name action:.
So I tried to addObserver in a different way: NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector(/* What to pass in here??? */), name: notificationString, object: nil), I don't know what to pass in selector: Selector(...).
XCode hints me: Selector(action(notification: NSNotification), but this is illegal.
In Objective C, I can easily pick up a method at this phase but I don't know how in Swift.
Have you tried this syntax? Let me know.
Thanks,
The syntax for a Selector is Selector("action:")
Not the direct answer for your question but I have an idea.
In swift instance methods are curried functions that take instance as first argument. Suppose you have a class like this.
class Foo: NSObject {
func bar(notification: NSNotification) {
// do something with notification
}
}
And somewhere in code you have an instance of Foo.
let foo = Foo()
Now you can get bar method as a variable like this
let barFunc = Foo.bar(foo)
Now imagine in the future NSNotificationCenter has an api that lets you assign functions to it instead of selectors.
NSNotificationCenter.defaultCenter().addObserverFunction(barFunc, name: "notificationName", object: nil)
Though I don't like to use NSNotificationCenter but It would be nice to see this kind of api in the future.