Consider this example of configuring MKMapView's map type. Should it be done in viewDidLoad()
override func viewDidLoad() {
super.viewDidLoad()
mapView.mapType = MKMapType.Hybrid
}
or in the var's didSet?
#IBOutlet weak var mapView: MKMapView! {
didSet {
mapView.mapType = MKMapType.Hybrid
}
}
Both work, what's the Swift preferred way?
They each have a different use.
If you want the mapType set every time the property is set, use didSet.
If you only want the mapType set once when the view is loaded, use viewDidLoad.
Given what you are doing I would say that didSet is the more correct choice here.
BTW - this has nothing to do with "the Swift preferred way". The same logic applies regardless of language.
Related
I have seen code where IBOutlets modify their properties using a didSet like so..
#IBOutlet private weak var tableView: UITableView! {
didSet {
tableView.dataSource = self
tableView.delegate = self
}
}
Is this considered good practice, or should we create a configure method in viewDidLoad?
Actually didSet here
outer part
didSet {
// refresh
}
makes more sense if the outer var you observe is rapidly changing / real time so you need to react to this change , but for the current case which is the table is set only once from IB inner init using didSet has no bounce over putting the code inside viewDidLoad
usually I do this:
func setupTableView() {
self.tableView.delegate = self
self.tableView.dataSource = self
}
So i call this method on my viewDidLoad()
I have the following Protocol:
protocol SoundEventDelegate{
func eventStarted(text:String)
}
which I call in this class:
class SoundEvent {
var text:String
var duration:Double
init(text: String, duration: Double){
self.text = text
self.duration = duration
}
var delegate : SoundEventDelegate?
func startEvent(){
delegate?.eventStarted(self.text)
}
func getDuration() -> Double{
return self.duration //TODO is this common practice?
}
}
Which I have my ViewController conform to:
class ViewController: UIViewController, SoundEventDelegate {
//MARK:Properties
#IBOutlet weak var beginButton: UIButton!
#IBOutlet weak var kleinGrossLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
//DELEGATE method
func eventStarted(text:String){
kleinGrossLabel.text = text
}
//MARK: actions
#IBAction func startImprovisation(sender: UIButton) {
var s1:Sentence = Sentence(type: "S3")
var s2:Sentence = Sentence(type: "S1")
var newModel = SentenceMarkov(Ult: s1, Penult: s2)
s1.start()
beginButton.hidden = true
}
}
But when I run the app kleinGrossLabel.text does not change. Am I referring to the label in the wrong way? Or is it the way that I do delegation that is incorrect?
Here are links to the complete Class definitions of Sentence and SentenceMarkov
https://gist.github.com/anonymous/9757d0ff00a4df7a29cb - Sentence
https://gist.github.com/anonymous/91d5d6a59b0c69cba915 - SentenceMarkov
You never set the delegate property. It's nil. It will never be called.
First off it's not common practice to have a setter in swift. if you want to have a readonly property you can use private(set) var propertyName
in other cases simply access the property like mentioned in the comment
Also i don't see a reason why you eventArray in sentence is of type [SoundEvent?] not [SoundEvent] as SoundEventdoes not seem to have a failable initialiser
Like mentioned before you need to not only implement the SoundEventDelegate protocol but also set the delegate
the problem is that you can't really access the SoundEventDelegate from the viewcontroller because you instantiate the SoundEvents inside Sentence
var soundEventDelegate: SoundEventDelegate?
the easiest way to do this would be adding a soundEventDelegate property for sentence and setting it like this:
let s1:Sentence = Sentence(type: "S3")
let s2:Sentence = Sentence(type: "S1")
s1.soundEventDelegate = self
s2.soundEventDelegate = self
and inside sound you would need the set the delegate for every event to the soundEventDelegate of Sentence
you could do it like this:
var soundEventDelegate: SoundEventDelegate? = nil {
didSet {
eventArray.forEach({$0.delegate = soundEventDelegate})
}
}
or write another initialiser that takes the delegate
hope this helps
p.s: you shouldn't inherit form NSObject in swift excepts it's really necessary
I'm developing simple app, where the user logins and can read articles from web. I recently added a code which sets a title at a second view while processing a segue, but my title at the second page is unfortunately nil. I have an object in storyboard connected properly to the variable in view controller, I checked this twice. I have no idea what to do, maybe I have unwrapped something not properly.
Code:
import UIKit
class MainViewController: UIViewController, UITabBarDelegate, UITabBarControllerDelegate, UIToolbarDelegate {
#IBOutlet var titleLabel: UILabel!
#IBOutlet var newsBar: UIToolbar!
#IBOutlet var accountBar: UITabBar!
override func viewDidLoad() {
super.viewDidLoad()
accountBar.delegate = self
newsBar.delegate = self
titleLabel.backgroundColor = UIColor(netHex: 0x00B0E4)
titleLabel.textColor = UIColor.whiteColor()
titleLabel.font = UIFont.boldSystemFontOfSize(16.0)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
override func shouldPerformSegueWithIdentifier(identifier: String, sender: AnyObject?) -> Bool {
return true
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
let next: SectionViewController = segue.destinationViewController as! SectionViewController
switch segue.identifier! {
case "hardwareSectionSegue":
next.titleLabel.text = "Rubrika o hardwaru"
case "softwareSectionSegue":
next.titleLabel.text = "Rubrika o softwaru"
case "webSectionSegue":
next.titleLabel.text = "Rubrika o webových technologiích"
case "programmerSectionSegue":
next.titleLabel.text = "Rubrika o programování"
case "mobileSectionSegue":
next.titleLabel.text = "Rubrika o mobilních zažízeních"
default:
next.titleLabel.text = "Rubrika nenalezena"
next.titleLabel.backgroundColor = UIColor.redColor()
}
}
#IBAction func unwindSegue(unwindSegue: UIStoryboardSegue) {}
}
The exception occurs below the first case where I'm trying to assign a value to next.titleLabel.text. It says the titleLabel is nil, which it souldn't be.
The SectionViewController:
import UIKit
class SectionViewController: UIViewController {
#IBOutlet var titleLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
I'm pretty sure that it's caused by that type cast, but then how do I properly set the attribute, if the segue doesn't even know what type will the next view have?
I can see what you're trying to do, but I'm afraid iOS doesn't let you do things that way. In your prepareForSegue() method you're trying to modify the view controller that is being created by setting a text value directly.
iOS lazy loads as much as it can, which means at this point in your program the label hasn't actually been created – it really is nil. If you put a breakpoint on that line you should be able to run po next.titleLabel in the debugger window to see nil come back. If you run po next.view first it will force iOS to create the view and all its subviews, at which point running po next.titleLabel again will work as you expect.
If you try creating a template Master-Detail Application project in Xcode you'll see the correct solution: set a property in your new view controller, then transfer that value to your label in viewDidLoad().
Summary: When you're navigating from view controller A to view controller B, don't make A try to configure B's UI. Instead, send B the values it needs, and have B configure itself.
Is it possible to have an #IB Action function inside of viewDidLoad() ?
The action is a simple one - a Stepper that increases other label.text values accordingly. However, the values that the stepper needs to work with depend on the return content of a url - which are only known after the viewDidLoad() of course.
So I think I can't have the IBaction way up on top before the viewDidLoad(), and the error I get if I try to do my IB action inside of the viewDidLoad() is:
"Only instance methods can be declared ‘IBAction' ”
EDIT
Let me clarify myself, sorry for the confusion. I know I need an outlet to get the UIStepper values from. I have that:
#IBOutlet weak var stepper: UIStepper!
I then have an action also connected to same UIStepper that will increase/decrease value of a label's text (new_total) accordingly:
#IBOutlet weak var new_total: UILabel!
#IBAction func step_up_pass(sender: AnyObject) {
new_total.text = "\(Int(stepper.value))"
}
However, I want to start out with a value (todays_price) I'm getting back from a json request and use that as a starting point, to multiply it using the stepper and put the multiplied value into the label's text.
I have a struct in a separate file that defines my object so:
struct PassengerFromOtherBus {
var fname: String?
var lname: String?
var todays_price: Int?
init(json: NSDictionary) {
self.fname = json["fname"] as? String
self.lname = json["lname"] as? String
self.todays_price = json["todays_price"] as? Int
}
}
So later on in the view controller, inside of the viewDidLoad(), after connecting to the URL and then parsing it using NSJSONSerialization and a bunch of other code here (that I don't need to confuse you with) I finally have my value todays_price. So my question is, how do I get my action to use that value when it's only known inside of my viewDidLoad()? Xcode will not even let me connect the IBAction to anywhere inside the viewDidLoad function!
This is not done with an Action but with an Outlet. Connect the Stepper from IB as an Outlet to your ViewController. Then just set the values of the Stepper in ViewDidLoad.
I would never go directly from a UIStepper.value to UILabel.text.
Use an intermediary variable to store the value.
Do the same for the return from the JSON. By setting a didSet function on those variables you can update the UI when any of the values is updated.
class FirstViewController: UIViewController {
var todays_price: Int = 0 {
didSet { // didSet to trigger UI update
myLabel.text = "\(stepperValue * todays_price)"
}
}
var stepperValue : Int = 1 {
didSet { // didSet to trigger UI update
myLabel.text = "\(stepperValue * todays_price)"
}
}
#IBOutlet weak var myStepper: UIStepper!
#IBOutlet weak var myLabel: UILabel!
override func viewDidLoad() {
//
let returnValueFromJson = 10
todays_price = returnValueFromJson
}
#IBAction func stepperUpdate(sender: AnyObject) {
stepperValue = Int(myStepper.value)
}
}
Just add a variable to the top of your view controller to hold the value from your json request. Then in viewDidLoad you update that variable, and then you can use it to set your label and inside the IBAction (that doesn't have to be inside viewDidLoad).
So you would do something like this:
class WhateverViewController: UIViewController {
var todays_price: Int!
override func viewDidLoad() {
super.viewDidLoad()
todays_price = // The value you got from json goes here
new_total.text = "\(todays_price)"
}
#IBAction func step_up_pass(sender: AnyObject) {
new_total.text = "\(Int(stepper.value) * todays_price)"
}
}
In my application I have a textbox that should be filled with a Double and the number should be saved into a variable but there's an error.
I dragged and dropped the textbox into ViewController.swift so it should be linked. I created a #IBOutlet. I called the textbox mmolText and the variable mmol.
I tried something like: var mmol = mmolText.text but it shows an error:
'ViewController.Type' does not have a member named 'mmolText'.
What's the problem? How can I solve it? Besides the type of the content of the textbox is a string but I should convert it into Double.
Here the code of ViewController.swift is:
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var mmolText: UITextField!
var mmol = mmolText.text
#IBOutlet weak var mmolLabel: UILabel!
#IBOutlet weak var mgLabel: UILabel!
#IBAction func convertBM(sender: AnyObject) {
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
It seems like we probably simply want mmol to exist as a convenient way for getting the text property out of the mmolText textfield, right? So why not use a computed property:
var mmol: String {
get {
return mmolText.text ?? ""
}
set {
mmolText.text = newValue
}
}
The get makes use of the nil coalescing operator. UITextField's text property hasn't been updated with the Objective-C nullability annotations yet, so we need to handle the case of it potentially returning nil.
If we want this to be readonly, we can simply omit the set part.
If we want this as a Double, we can modify the above computed property to look more like this:
var mmol: Double {
get {
return ((mmolText.text ?? "0") as NSString).doubleValue
}
set {
mmolText.text = String("%f", newValue)
}
}
And again, if we want this to be readonly, we can simply omit the set half. And of course, the format string can be played around with to get the string version of the double to show up exactly as you intend when using this set method.
class ViewController: UIViewController {
#IBOutlet weak var mmolText: UITextField!
var mmol: String!
override func viewDidLoad() {
super.viewDidLoad()
mmol = mmolText.text
}
}
This way it works. I can remember something like because at that stage, the properties can exist. Which means, it can be there or it isn't. That's why you can't do it like that.
Don't pin me on this explanation though, I'm not very sure.
mmolText is a property on self. You can't refer to it there because self has not been initialized yet.
You'll have to assign it within awakeFromNib, viewDidLoad, etc.