How to reference and modify subview UILabel from parent UIStackView - ios

I have a UIStackView object to which I have added a UILabel as the first subview. Here is what my code looks like:
func createStackView() -> UIStackView? {
var displayLabel = UILabel()
// Set properties of displayLabel
...
let myStackView = UIStackView(arrangedSubviews: [displayLabel, stackViewRow1, stackViewRow2]
return myStackView
}
Now, I wish to obtain a reference to the displayLabel object later on so I can modify it's text field.
The only way I could do this was somewhat of a hack:
var displayPanel = topStackView.arrangedSubviews[0]
if let displayLabel = displayPanel as? UILabel {
displayLabel.text = "Ready"
}
Is there a better way to do this ?

Store a reference to the label in a property:
var displayLabel: UILabel?
// then when you create the stack view
self.displayLabel = displayLabel
// to access it later
if let displayLabel = displayLabel {
displayLabel.text = "Ready"
}

If displayLabel should be not instance variable, then setting a tag value for it might be useful:
An integer that you can use to identify view objects in your
application.
What you can do:
// declaring a global constant for holding the label tag value
let displayLabelTageValue = 101
func createStackView() -> UIStackView? {
var displayLabel = UILabel()
// setting tag
displayLabel.tag = displayLabelTageValue
// Set properties of displayLabel
...
let myStackView = UIStackView(arrangedSubviews: [displayLabel, stackViewRow1, stackViewRow2]
return myStackView
}
And for getting identified displayLabel label:
if let displayLabel = view.viewWithTag(displayLabelTageValue) as? UILabel {
// displayLabel is wanted label!
} else {
// make sure that the tag value is matched
}

It depends on what your goals are.
If you want to set the text of the view in position 0 of your stack view to a specific string, the code you posted is the right way to go.
If you have a label that you want to be able to change regardless of it's position in the stack view then you should save a reference to it in an instance variable of your view controller, and then you can set it's text to a new string at any time.

Related

How to assign a String value to button.tag [duplicate]

Is there any way of sending string value to UIButton tag? I know tag is Int type but I need to send string value.
let myButton: UIButton = {
let button = UIButton()
button.tag = "123"
return button
}()
I need to send button.tag as a string
Create a custom class of UIButton and create one String type property like customTag
class CustomButton: UIButton {
var customTag:String = ""
}
Assign the custom class to your UIButton and use String type tag by following code.
let myButton: CustomButton = {
let button = CustomButton()
button.customTag = "ABC"
return button
}()
Instead of tag you can do it using accessibilityLabel.
btn.accessibilityLabel = "ABC"
One more answer.
Instead of accessibilityLabel / accessibilityIdentifier or any other related to accessibility , let us go for layer.name
Button.layer.name = "abc123"
#IBAction func wasPressed(_ sender: UIButton) {
print("was Pressed \(sender.layer.name)")
}
Reason:
Incase, we are in need to set accessibility for each and every screen, at that time it might affect.
But, if we are in need to add another layer for UIButton, it will not affect
Unfortunatly, the tag is just an integer value.
What you could do is:
Subclass UIButton and add your own property
Use associated objects, like in https://gist.github.com/mjjimenez/7956352
Use enums for the tag; maybe add an extension to access them
(Mis)use the accessibilityIdentifier
Just create extension of UIButton or NSObject.
extension NSObject {
private struct AssociatedKeys {
static var DescriptiveName = "strCustomTag"
}
var strCustomTag: String? {
get {
return objc_getAssociatedObject(self, &AssociatedKeys.DescriptiveName) as? String
}
set {
if let newValue = newValue {
objc_setAssociatedObject(
self,
&AssociatedKeys.DescriptiveName,
newValue as NSString?,
.OBJC_ASSOCIATION_RETAIN_NONATOMIC
)
}
}
}
}
Refer below code to set the value of strCustomTag
let myButton: UIButton = {
let button = UIButton()
button.strCustomTag = "ABC"
return button
}()
Get Value
print(view.strCustomTag)
If you create an extension of NSObject
strCustomTag is accessible from any class or any UI Elements such as
UIButton, UITextField, UIView etc.
No need to change in exsting storyboard OR XIB file
There is no way to set the tag to String because its type is Int.
Most probably you will no need to set it anyway because using the tag of UIView is not a common technique.
If you still want to use similar solution (I recommend not to) then you may use the accessibilityIdentifier property which is String.
Also you can create a custom subclass of UIButton with custom String property and to use this custom subclass instead of UIButton
You can create custom class for button then you can pass any kind of values by just define the property in the custom class.
Example: I want to pass the String and Int both values in Button So I will pass Int in to button tag and string will be passed in custom button property.
class CustomButton: UIButton {
var stringToPass: String = ""
}
ViewController Class
class HomeVC: UIViewController {
let customButton = CustomButton()
override func viewDidLoad() {
super.viewDidLoad()
customButton.stringToPass = "Your String"
}
}

UIelement programmatically created. Can I take the name of new created UIelement from variable?

I edited exactly what I want to achieve, this time different example without taken parameter in function.
What I want to achieve is:
var array = [String]()
func create() {
var arrayCount = array.count // will be 0
var nameForUI = "view/(arrayCount)" // will be view0
let nameForUI: UIVIew = {
let view = UIVIew()
return view
}()
array.append(nameForUI)
view.addSubview(nameForUI)
//
}
next time if I call create() func , the next view will be called "view1" So my question is, how to achieve this result? every time function will called it will create new element with new name.
Edit
To directly answer your question: No, you cannot do that.
Code written in Swift is compiled -- it is not an interpreted / scripted language.
So you cannot write code that creates a button named "littleButton" and then have a line of code littleButton.backgroundColor = .red
You can sort of do this by creating a Dictionary that maintains the "name" of the element as the key, and a reference to the element as the value.
Start with initializing an empty dictionary:
var buttonsDict: [String : UIButton] = [String : UIButton]()
Your "create" func can start like this:
func createButton(named str: String) -> Void {
// create a button
let b = UIButton()
// give it a default title
b.setTitle("Button", for: .normal)
// add it to our Dictionary
buttonsDict.updateValue(b, forKey: str)
}
When you want to create a button:
createButton(named: "littleButton")
When you want to access that button by name:
// use guard to make sure you get a valid button reference
guard let btn = buttonsDict["littleButton"] else { return }
view.addSubview(btn)
Edit 2
Another option, which is perhaps more similar to your edited question:
// initialize empty array of views
var viewsArray: [UIView] = [UIView]()
override func viewDidLoad() {
super.viewDidLoad()
// create 5 views
for _ in 0..<5 {
create()
}
// ...
// then, somewhere else in your code
viewsArray[0].backgroundColor = .red
viewsArray[1].backgroundColor = .green
viewsArray[2].backgroundColor = .blue
viewsArray[3].backgroundColor = .yellow
viewsArray[4].backgroundColor = .orange
}
func create() -> Void {
// create a view
let v = UIView()
// add it to our array
viewsArray.append(v)
// add it as a subview
view.addSubview(v)
}
As you see, instead of trying to reference the created views by name (which you cannot do), you can reference them by array index.
Just remember that arrays are zero-based... so the first element added to the array will be at [0] not [1].

Access TextField from another class or method without storyboard

Okay, this might be one of the most basic questions ever, but all answers I find use storyboard to declare an outlet for a label, textfield or whatever element that needs to be changed. I, however, don't use storyboards and write everything in code. Now I have a function setupViews, where I define a textfield:
let usernameInput = UITextField()
Now, I can perfectly set the text or placeholder or whatever inside this setupViews() class, but how can I access it outside? For example, if I have a function logIn(), I want to call usernameInput.text and use it in this function.
Someone who can point me in the right direction? Do I need to declare this textfield globally, in another file, or something else?
When I create my views in code I always associate a property with the view that has all those various display values.
I have not tested this code to see but hopefully the following will give you an idea.
import UIKit
struct {
var name: String
}
class CustomViewController : UIViewController {
// some struct which contains data for view
var customViewData : ViewDataInfo? {
didSet {
labelOnScreen.text = customViewData.name
}
}
var labelOnScreen: UILabel = {
let label = UILabel()
label.text = "Placeholder information..."
// stuff auto layout
label.translateAutoresizingMaskIntoConstraints = false
return label
}()
override func viewDidLoad() {
super.viewDidLoad()
setupView()
}
private func setupView() {
view.addSubview(label)
// set your constraints here
}
}

When using Computed variable and Snapkit: No common superview between views

So here's the thing, I'm declaring a property like this:
var aNameLabel: UILabel {
guard let foo = Applicant.sharedInstance.realName else {
return UILabel(text: "获取姓名失败", color: .whiteColor())
}
return UILabel(text: foo, color: .whiteColor())
}
And when I try to add constraint to the aNameLabel after I did someView.addSubView(aNameLabel), the app would crash every time at this constraint-adding thing, and says No common superview between views
However, when I change the variable into a let constant like this:
let aNameLabel = UILabel(text: "Allen", color: .whiteColor())
The constraint will be added with no complaint. Somebody can help me with this?
UPDATE
With the help of #par , I've changed my code into this:
var aNameLabel: UILabel = {
guard let foo = Applicant.sharedInstance.realName else {
return UILabel(text: "BAD", color: .whiteColor())
}
return UILabel(text: foo, color: .whiteColor())
}()
And then the aNameLabel would always be assigned with value "BAD", while actually my guard let is successful. How do I fix this?
The problem is that you are creating a new UILabel every time you access the aNameLabel variable (a computed property function runs every time you access it). Presumably you are doing the same thing for the superview of this view (when you access someView in someView.addSubview() in your example above). If so, that's why there's no common superview and you are crashing.
You should create only one instance of each UIView used by your view controller, so creating a variable as a constant as you've shown is a great approach, or you can use a closure-initializer pattern like so:
var aNameLabel: UILabel = {
return UILabel(...)
}()
Notice in the above example the parentheses after the closing brace. Because it's a closure-initializer it will only be called once just like a let constant.
Often a UIView created with let isn't appropriate because constant properties need to be initialized before init() returns, and if you're creating views in a view controller you won't have a chance to add views until loadView() is called. In this case, declare your UIViews as implicitly-unwrapped optionals. They will be set to nil by init() which meets the initialization requirement, then you can set them to actual views later when viewDidLoad() is called, for example:
class MyViewController: UIViewController {
var someSubview: UIView!
override func viewDidLoad() {
super.viewDidLoad()
someSubview = UIView()
view.addSubview(someSubview)
// now set constraints with SnapKit
}
}

Add a string property to a UIButton in Swift

How can I associate a string property with a UIButton in Swift? I don't want the string to appear as the button text, simply to be assigned to the button as an identifier or a key. Here is what I have so far:
func createAnswerButtons() {
var index:Int
for index = 0; index < self.currentQuestion?.answers.count; index++ {
// Create an answer button view
var answer:AnswerButtonView = AnswerButtonView()
selection.setTranslatesAutoresizingMaskIntoConstraints(false)
// Place into content view
self.scrollViewContentView.addSubview(answer)
// Add a tapped gesture recognizer to the button
let tapGesture:UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: Selector("answerTapped:"))
answer.addGestureRecognizer(tapGesture)
// Add constraints etc
// Set the answer button text
let answerText = self.currentQuestion!.answers[index]
answer.setAnswerText(answerText)
// Set the identifier for each answer button
self.identifier = self.currentQuestion!.answerIdentifier[index]
// Add to the selection button array
self.answerButtonArray.append(answer)
}
So I think I need something after
// Set the identifier for each answer
self.identifier = self.currentQuestion!.answerIdentifier[index]
To assign the identifier to the button.
The reason for this is I'm trying to implement a decision tree logic so that I can keep track of each answer button that is tapped to generate a code string that will correspond to a final result.
Using the Objective-C runtime, we can add properties to classes at runtime:
extension UIButton {
private struct AssociatedKeys {
static var DescriptiveName = "nsh_DescriptiveName"
}
#IBInspectable var descriptiveName: String? {
get {
return objc_getAssociatedObject(self, &AssociatedKeys.DescriptiveName) as? String
}
set {
if let newValue = newValue {
objc_setAssociatedObject(
self,
&AssociatedKeys.DescriptiveName,
newValue as NSString?,
UInt(OBJC_ASSOCIATION_RETAIN_NONATOMIC)
)
}
}
}
}
Adding #IBInspectable also lets us set the descriptiveName property through Interface Builder.
For more about the Objective-C runtime, I recommend you check out this NSHipster article.
You can use the accessibility identifier (button.accessibilityIdentifier), if the button you want to identify should have a unique identifier (this matters if you're ever writing UI tests).
You can also subclass UIButton and add a variable buttonIdentifier.
class IdentifiedButton: UIButton {
var buttonIdentifier: String?
}
You can use accessibilityIdentifier property of UIButton.
#IBOutlet weak var button: UIButton!
button.accessibilityIdentifier = "Some useful text"
Use
button.accessibilityIdentifier = "some text"
istead of tag.
You can create an array with the strings you want associated with the button. Then set the buttons tag to the index of the string you want associated with the button. Hence:
var myStrings = ["First","Second","Third"]
button.tag = //insert a number corresponding to the string index in myStrings that you want for the button
func buttonPressed(sender: UIButton){
var selectedString = myString[sender.tag]
}

Resources