Let's say I have created a UI with some views using an interface builder (not in code, so not programmatically).
How can I assign human readable ids / tags to these views, so that I could reference them in code?
I know that I can assign an integer tag to a view using attribute inspector and then make a dictionary (or enum) to store the mapping of tags to the views. However, this is an error-prone method which also scales really badly (imaging assigning integer tags to hundred of views in a complex app...).
Is there a better solution for this problem? Is there a way to directly assign a human readable tag / id to a view, like "resumeButton"?
UPDATE:
Here is an example scenario of what I want to achieve:
UI with five different buttons; the buttons have image and no title
all five buttons are connected to the same IBAction in code
in IBAction I have a switch statement, so that depending on which button is clicked, different versions of code are executed
UPDATE 2:
SOLUTION
I ended up implementing a simple custom view:
#IBDesignable
class CustomButton: UIButton {
#IBInspectable var stringTag: String = defaultID
}
This way I can see an additional property stringTag in Interface Builder and can simply add a value to it directly in Interface Builder.
You could create an extension property on UIView to store an identifier string. If you made that extension property IBInspectable, you could set and view it from the storyboard directly.
More detail on setting up such a property in this answer:
https://stackoverflow.com/a/37166043/1830999
The built-in tag property for UIView is just an integer, so it isn't very descriptive for humans who read it.
From what you're describing sounds to me that you are looking for an: Outlets
You are right about tags. Every connection with Interface Builder should be handled using IBAction and IBOutlet.
Since you say that every button has a different action, the simplest solution is to create a separate IBAction for each of them:
#IBAction private func onResumeButtonTapped() {
...
}
#IBAction private func onPauseButtonTapped() {
...
}
If you, for some reason, want to keep them connected to one function, you can use outlets:
#IBOutlet private var resumeButton: UIButton!
#IBOutlet private var pauseButton: UIButton!
#IBAction private func onButtonPressed(_ sender: UIButton) {
switch sender {
case resumeButton:
...
case pauseButton:
...
default:
break
}
}
Related
Now I'm learning swift using Xcode, but I don't know where to drag and drop and why drag and drop "label" to somewhere
should I drop no.1? or no.2? or no.3? and why I should drop there?
If you drop it to 1, Xcode automatically generate an #IBOutlet for you. If you drop it to 2 or 3, then #IBAction. There's no other reason for that except that Xcode tries to be smart and help you organize code more nicely: properties with properties in the top of the class, methods – in the methods area. And you can also move declarations to another place later: except for code style matters, it doesn't matter, where exactly within your class you put declarations.
as you are trying to take an IBOutlet for a label , its nice to keep it on top of the class (where you mention 1).
normally Xcode gives suggestion you , if you drug on top side it will be #IBOutlet
or you on bottom like 2 or 3 it will be #IBAction like you already took a button action in your code .
Your Solution On Code:
import UIKit
class CodePresentViewController: UIViewController {
#IBOutlet weak var YourLabel: UILabel! // insert #IBOutlet type property here
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func tapBackButton(_ sender: UIButton) {
//Yout Button COde
}
}
If you want to own the label in your code, and then make some configuration to the label by code, use "1". It will give you a #IBOutlet label object in your code.
If you want to set the label's action, use "2" or "3".
I have an app I am working on where it shows a balance for users that changes. I would like to display these values as they change and the only way I know how to do that would be with a button. But the values often change on a different view controller so I was wondering if I can set labels equal to a variable that update along with those variables.
Since the values change outside of the view controllers with the labels, the normal way I change labels, using a button, does not apply.
Thanks in advance!
As a general solution, you could achieve this by declaring a property observer in your view controller, example:
class ViewController: UIViewController {
var updatedData = "" {
didSet {
lblData.text = "Data: \(updatedData)"
}
}
#IBOutlet weak var lblData: UILabel!
}
At this point, each time updatedData value is edited, the lblData label text will be updated.
Note that there is also willSet option which is called just before the value is stored, for more information, you could check Swift Properties Documentation - Property Observers.
I'm new to Swift and I find myself in the situation where I have a view controller that should manage 36 textfields and 15 labels (update with some math performed from the textfield).
Now my issue is that seems too much to connect all those outlets in the view controller.
I wanted to create a data model which will store into an Array the data and then perform the math and update the UI.
Like I said, I'm new to Swift and it seems I'm not able to create a model using outlets.
This is what I've done:
All the textfields and labels are contained in a subview inside the main view. So I've associated the subview with the class of the model (EnergyModel) which it already gives me an error right away.
Here's the code of my model:
class EnergyCalcModel {
#IBOutlet weak var lightstextfield1: UITextField!
#IBOutlet weak var lightstextfield2: UITextField!
private var _lights1: String
private var _lights2: String
var lights2: Double {
if lightstextfield2.text != nil {
_lights2 = (lightstextfield2.text!).doubleValue
}
return _lights2
}
var _lights1: String {
if lightstextfield1.text != nil {
_lights1 = (lightstextfield1.text!).doubleValue
}
return _lights1
}
init(lights1: Double, lights2: Double) {
self._lights1 = lights1
self._lights2 = lights2
}
}
and in my main VC:
var energyModel: EnergyCalcModel!
func calculate() {
label.text = energyModel.lights1 * energyModel.lights2
}
Could you please advise?
I'm not sure what you're concerned about "overloading". If it's memory you're concerned about, that memory is already used when you put all these UI elements on the screen. Keeping references to them in your view controller won't make things any worse. It might make the code ugly and repetitive, for example if you end up with instance variables named textField1 through textField36 instead of something descriptive.
If it makes sense for your app and for the purpose of this view, go ahead and include them all. There's nothing to "overload" in that regard.
Some things that might improve the code-- depending very heavily on how you use these text fields and labels-- might include:
Using a dynamic structure like a table view. If each table view cell has one label and one text field, you can have as many as you need easily. You can also have the number change depending on your data, if that makes sense for your needs.
Instead of using 36 different outlets, use an outlet collection (#IBOutletCollection) that contains all of them. Finding the right outlet in the array may require a little work, though, because you can't rely on the array order. You might do that using the tag property on the view, or you might sort it somehow.
I am attempting to learn Apple's Swift. I was recently trying to build a GUI app, but I have a question:
How do I interact with GUI elements of my app? For instance, I used interface builder to make a UILabel, and I connected it to my viewcontroller by control-clicking, so that I get the #IBOUTLET thing. Now, how do I, while in my view controller, edit the text of this UILabel? To state it another way, what code can I use to programatically control the text of something on my storyboard? All methods I have found online only appear to work with a button generated in code, not a button generated on a storyboard.
I've seen code like
self.simpleLabel.text = "message"
If this is right, how do I link it with the label in question? In other words, how do I adapt this code to be connected with the IBOutlet (If that's what I do)
If you've successfully linked the control with an IBOutlet on your UIViewController class, then the property is added to it and you would simply replace simpleLabel with whatever you named your IBOutlet connection to be like so:
class MyViewController: UIViewController {
#IBOutlet weak var myLabel: UILabel!
func someFunction() {
self.myLabel.text = "text"
}
}
The outlet you created must've been named by you. The outlet belongs to your view controller. self.simpleLabel means 'fetch the value of my property named 'simpleLabel'.
Since different outlets have different names, using self.simpleLabel here won't work until your outlet is named 'simpleLabel'. Try replacing 'simpleLabel' with the name you gave to the outlet when you created it.
The correct way now would be:
self.yourLabelName.text = "message"
If you have something like this for an IBOutlet:
#IBOutlet var someLabel: UILabel!
then you could set the text property just like in your example:
someLabel.text = "Whatever text"
If you're having problems with this, perhaps you're not assigning the text property in the right place. If it's in a function that doesn't get called, that line won't execute, and the text property won't change. Try overriding the viewDidLoad function, and put the line in there, like this:
override func viewDidLoad() {
super.viewDidLoad()
someLabel.text = "Whatever text"
}
Then, as soon as the view loads, you'll set the text property. If you're not sure if a line of code is executing or not, you can always put a breakpoint there, or add some output. Something like
println("Checkpoint")
inside a block of code you're unsure about could really help you see when and if it runs.
Hope this helps.
You may trying to change a UI component not in the main thread, in that case, do this:
DispatchQueue.main.async {
someLabel.text = "Whatever text"
}
I made these (marked with red border) IBOutlets using ctrl + drag
But i don't like to have the exact same line 9 times (DRY)
How do i put these IBOutlets in an Array?
you can define a generic outlet collection in Swift like this:
#IBOutlet var collectionOfViews: Array<UIView>? // = [UIView]?
or for e.g. UIButton objects:
#IBOutlet var collectionOfButtons: Array<UIButton>? // = [UIButton]?
you can find your collections under the Outlet Collections group as usually are in the File's Owner:
it would look on my console after connecting 5 random buttons:
Follow these steps to create an array of outlets an connect it with IB Elements:
Create an array of IBOutlets
Add multiple UIElements (Views) in your Storyboard ViewController interface
Select ViewController (In storyboard) and open connection inspector
There is option 'Outlet Collections' in connection inspector (You will see an array of outlets there)
Connect if with your interface elements
-
class ViewController2: UIViewController {
#IBOutlet var collection:[UIView]!
override func viewDidLoad() {
super.viewDidLoad()
}
}
Solution here Swift - IBOutletCollection equivalent
#IBOutlet var objectCollection: [Object]
This is for macOS (should be similar for iOS) and I do not find an "Outlet Collections" in my storyboard (looks like they took that option out). So I put all my buttons in an NSStackView and linked the stack from storyboard
#IBOutlet weak var buttons: NSStackView!
and then I looped over them to make changes accordingly
for case let (index, button as NSButton) in buttons.arrangedSubviews.enumerated() {
if(index + 1 != someButtonIndex) {button.state = .off}
else {button.state = .on}
}
you can also use tag instead of index
Start with the two view pane where you see both your code and the storyboard. When you make your first IBOutlet connection from the UI to your code, just look carefully at the Connection drop down field and select the option called "Outlet Collection". This will automatically create an array of IBOutlets. Next just look for the little black circle within a circle that is placed in your code where the array is created. Just drag from this circle to all the other UI objects you want to connect to that same collection (not sure if you can mix types). Similarly you can connect all the objects to one Action by dragging from the first black dot created to all the other objects you want to wire up to that action. Also consider EnumerateSequence() to help in working with this Collection. Sweet right?