Looping an object in Swift - ios

I'm having a problem with looping an object in Swift.
I want to declare the UITextfield as an instance variable so I can access it anywhere inside the class.
So if I put it inside the loop it works fine, but I put the variable of UITextField outside the loop, it doesn't show the first textfield in the view and the username textfield would be missing.
Here's my code
var placeHolderArr:[String] = ["Username", "Password"]
let loginField = UITextField() // <----- this one
override func viewDidLoad() {
super.viewDidLoad()
let logoHolder = UIView(frame: CGRect(x: 0, y: 0, width: self.view.frame.width, height: self.view.frame.height / 2))
logoHolder.backgroundColor = self.utils.hexStringToUIColor(hex: "#ff8f00")
for index in 0...1 {
let height = index * 60
// When I declare the textfield variable in this line, it works fine.
loginField.frame = CGRect(x: 0 , y: Int((logoHolder.frame.maxY + 30) + CGFloat(height)), width: Int(self.view.frame.width - 80), height: 50)
loginField.placeholder = placeHolderArr [index]
loginField.font = UIFont.systemFont(ofSize: 15)
loginField.borderStyle = UITextBorderStyle.roundedRect
loginField.autocorrectionType = UITextAutocorrectionType.no
loginField.keyboardType = UIKeyboardType.default
loginField.returnKeyType = UIReturnKeyType.done
loginField.clearButtonMode = UITextFieldViewMode.whileEditing;
loginField.contentVerticalAlignment = UIControlContentVerticalAlignment.center
loginField.center.x = self.view.center.x
loginField.delegate = self
loginField.tag = index
innerY = Int(loginField.frame.maxY)
print("index\(index)" )
self.view.addSubview(loginField)
}
}
Please see the screenshot below
Textfield when declared inside the loop:
Textfield when declared outside the loop:

Obviously you use the same UITextField. Judt put initialization inside the loop
for index in 0...1 {
let height = index * 60
// When I declare the textfield variable in this line, it works fine.
loginField = UITextField()
loginField.frame = CGRect(x: 0 , y: Int((logoHolder.frame.maxY + 30) + CGFloat(height)), width: Int(self.view.frame.width - 80), height: 50)
loginField
And to use it later use array:
var fieldArr:[UITextField] = []
Then later:
fieldArr.append(loginField)
Summarise:
var placeHolderArr:[String] = ["Username", "Password"]
let loginFields:[UITextField] = []
override func viewDidLoad() {
super.viewDidLoad()
let logoHolder = UIView(frame: CGRect(x: 0, y: 0, width: self.view.frame.width, height: self.view.frame.height / 2))
logoHolder.backgroundColor = self.utils.hexStringToUIColor(hex: "#ff8f00")
for index in 0...1 {
let loginField = UITextField()
let height = index * 60
// When I declare the textfield variable in this line, it works fine.
loginField.frame = CGRect(x: 0 , y: Int((logoHolder.frame.maxY + 30) + CGFloat(height)), width: Int(self.view.frame.width - 80), height: 50)
loginField.placeholder = placeHolderArr [index]
loginField.font = UIFont.systemFont(ofSize: 15)
loginField.borderStyle = UITextBorderStyle.roundedRect
loginField.autocorrectionType = UITextAutocorrectionType.no
loginField.keyboardType = UIKeyboardType.default
loginField.returnKeyType = UIReturnKeyType.done
loginField.clearButtonMode = UITextFieldViewMode.whileEditing;
loginField.contentVerticalAlignment = UIControlContentVerticalAlignment.center
loginField.center.x = self.view.center.x
loginField.delegate = self
loginField.tag = index
innerY = Int(loginField.frame.maxY)
print("index\(index)" )
self.view.addSubview(loginField)
loginFields.append(loginField)
}
}

When you declare the UITextfield subclass outside your loop, than every time when you set it up, you set up the same UITextfield instance. This means, you are overriding the first set of properties with the next one, every time you iterate on it.
Instead, lets create two instances, and set up both independently:
// declare this outside your loop
let usernameField = UITextField()
let passwordField = UITextField()
for (index, loginField) in [usernameField, passwordField].enumerated() {
// do your setup
}

You can create two properties:
let usernameField = UITextField()
let passwordField = UITextField()
and loop every text field like:
for (index, loginField) in [usernameField, passwordField].enumerated() {
//do what you need
}

Related

Swift iOS: How to assign a value to a Label created with Tag system

After creation of a list of Label created with this func:
func addElongationlabel() {
let elongationLabel = UITextView(frame: CGRect(x: 100, y: ordinata, width: 90, height: 35))
elongationLabel.textAlignment = NSTextAlignment.center
elongationLabel.tag = ordinata
elongationLabel.textColor = UIColor.blue
elongationLabel.backgroundColor = UIColor.red
view.addSubview(elongationLabel)
}
I'm obtaining a list of label using this cycle of For:
for i in 1...Int(numberOfDiesText.text!)!{
ordinata = 220 + 50*i
addElongationlabel()
}//end of for
Now I want to assign to my Labels a value calculated. I'm trying with this code but it is not working:
for i in 1...Int(numberOfDiesText.text!)!{
ordinata = 220 + 50*i
if let eloLabel = (self.view.viewWithTag(ordinata)as? UILabel){
// here I think I have to insert the correct command
self.view.viewWithTag(ordinata) = eloLabel // this is not working...of course
}
}//end of for
Thank you to help me!

How to add multiple textfields programatically in swift ios

I am trying to show multiple textFields by running a loop, that whether the number of textfields noTextField is greater than zero (which is an input value), it should display the multiple textFields on ViewController.
Here is what I have one textfield and prints multiple hello but could not display multiple textfields.
What I have
enter image description here
What I want
enter image description here
if self.noTextFields! > 0 {
for _ in 0..<self.noTextFields! {
// self.createForm()
print ("hello")
let sampleTextField = UITextField(frame: CGRect(x: 20, y: 100, width: 300, height: 40))
sampleTextField.placeholder = "Enter text here"
sampleTextField.font = UIFont.systemFont(ofSize: 15)
sampleTextField.borderStyle = UITextBorderStyle.roundedRect
sampleTextField.autocorrectionType = UITextAutocorrectionType.no
sampleTextField.keyboardType = UIKeyboardType.default
sampleTextField.returnKeyType = UIReturnKeyType.done
sampleTextField.clearButtonMode = UITextFieldViewMode.whileEditing;
sampleTextField.contentVerticalAlignment = UIControlContentVerticalAlignment.center
sampleTextField.delegate = self as? UITextFieldDelegate
self.view.addSubview(sampleTextField)
}
}
Mistakes and Corrections:
Check the SubView items contents for the self.view by self.view.items values printing in logs.
You have not assigned the X / Y position for the +1 counter text fields you are allocating to any incremental position so even if the new object will get created it will be overlapped on the same previous item.
I would always recommend to put the textfield object in array at same time when you do addSubview call, immediately after this self.view.addSubview(sampleTextField) call. This will help me to save the reference for each text field item when required to modify / access / deallocate.
You also set the tag for each textfield object so its better to get the object with that tag from the subviews with [self.view viewWithTag:tagNo] call if you dont want an array to be used.
Your snippet will look like
var xValue = 0,yValue =0
var tagNo = 0
var textObjects = allocate new array.
if self.noTextFields! > 0 {
for _ in 0..<self.noTextFields! {
// self.createForm()
print ("hello")
let sampleTextField = UITextField(frame: CGRect(x: xValue, y: yValue, width: 300, height: 40)) //Note that I have set xValue and yValue
sampleTextField.placeholder = "Enter text here"
sampleTextField.font = UIFont.systemFont(ofSize: 15)
sampleTextField.borderStyle = UITextBorderStyle.roundedRect
sampleTextField.autocorrectionType = UITextAutocorrectionType.no
sampleTextField.keyboardType = UIKeyboardType.default
sampleTextField.returnKeyType = UIReturnKeyType.done
sampleTextField.clearButtonMode = UITextFieldViewMode.whileEditing;
sampleTextField.contentVerticalAlignment = UIControlContentVerticalAlignment.center
sampleTextField.delegate = self as? UITextFieldDelegate
sampleTextField.tag = tagNo //Note this line for tagNo
self.view.addSubview(sampleTextField)
textObjects.addObject:sampleTextField //Note this line for adding textfield to array for reference to get total items added
yValue = yValue + sampleTextField.frame.size.height + 20; //Added yValue to place another text view at bottom of initial one and 20px buffer for gap.
}
}
print "Total Textobjects in array:textObjects.count
If required to get object from view its self.view.viewwithTag(tagNo) will return exact text field.
You just add dynamic value of y(add x if required).
var counter = 30
var yValue = 0
in for loop you just need to add below code
let sampleTextField = UITextField(frame: CGRect(x: 20, y: yValue, width: 300, height: 40))
yValue = Counter
//(space each textfield if 30 because height of textfield is 40)
counter = counter + 70

Programmatically display UIScrollView in Swift

I have view controller with views: top level View Contains Container View which contains two views: bottomView, topView, as shown.
Scene
I want to display from: to: date range in the topView. In the bottomView I want to display a UIScrollView that contains two columns which I can scroll. I did that but the topView and BottomView overlap when I introduce scrollView. When I scroll I can see the views getting separated and as soon as i Let go the scrollbar they overlap again.
can someone tell me how to fix it? I just don't seem to understand how the scrollView and bottomView are to be associated.
Code below:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
scrollView.frame = view.bounds
//scrollView.frame = innerView.bounds
innerView.frame = CGRect(x:0, y:0, width:scrollView.contentSize.width, height:scrollView.contentSize.height)
}
func buildBottomView () {
let screenSize = UIScreen.main.bounds
let screenWidth = screenSize.width
let ht:Int = 21
let incrX:Int = 5
let incrY:Int = 5
let gapCol1:Int = 5
let col1Width:Int = 65
let col2Width:Int = 65
let startY:Int = 5
let col1StartX:Int = 10
let col2StartX:Int = col1StartX + col1Width + gapCol1
var loadRowStartY: Int = 0
// column headers
categoryColumnLabel.text = "Interval"
categoryColumnLabel.font = UIFont.preferredFont(forTextStyle:UIFontTextStyle.subheadline)
//categoryColumnLabel.font = UIFont.systemFont(ofSize: 14)
categoryColumnLabel.frame = CGRect(x: col1StartX, y:startY, width: col1Width, height: ht)
categoryColumnLabel.textAlignment = NSTextAlignment.left
categoryColumnLabel.tag = 1
innerView.addSubview(categoryColumnLabel)
valueColumnLabel.text = "Values"
valueColumnLabel.font = UIFont.preferredFont(forTextStyle:UIFontTextStyle.subheadline)
//valueColumnLabel.font = UIFont.systemFont(ofSize: 14)
valueColumnLabel.frame = CGRect(x: col2StartX, y:startY, width: col2Width, height: ht)
valueColumnLabel.textAlignment = NSTextAlignment.center
valueColumnLabel.tag = 3
innerView.addSubview(valueColumnLabel)
let sepLine:UIView = UIView()
sepLine.frame = CGRect(x: col1StartX, y:startY+ht+incrY, width: Int(screenWidth-20), height: 2)
sepLine.backgroundColor = UIColor.darkGray
sepLine.tag = 60
loadRowStartY = startY+ht+incrX+ht
innerView.addSubview(sepLine)
for i in 0 ..< 24 {
let timeIntervalLabel = UILabel()
let value2Label = UILabel()
print("display load profile")
let loadStruct = loadDict[String(i)] as! CommercialProfile
print (loadStruct.timeInterval)
timeIntervalLabel.text = loadStruct.timeInterval
timeIntervalLabel.font = UIFont.preferredFont(forTextStyle:UIFontTextStyle.caption1)
//valueColumnLabel.font = UIFont.systemFont(ofSize: 14)
timeIntervalLabel.frame = CGRect(x: col1StartX, y:loadRowStartY, width: col1Width, height: Int(timeIntervalLabel.font.lineHeight))
timeIntervalLabel.textAlignment = NSTextAlignment.center
innerView.addSubview(timeIntervalLabel)
print(loadStruct.value)
value2Label.text = loadStruct.value
value2Label.font = UIFont.preferredFont(forTextStyle:UIFontTextStyle.caption1)
//value2Label = UIFont.systemFont(ofSize: 14)
value2Label.frame = CGRect(x: col2StartX, y:loadRowStartY, width: col2Width, height: Int(value2Label.font.lineHeight))
value2Label.textAlignment = NSTextAlignment.center
innerView.addSubview(value2Label)
loadRowStartY = loadRowStartY + incrY + Int(value2Label.font.lineHeight)
}
you are setting the bounds of the scrollView to the size of the whole view with this code: scrollView.frame = view.bounds.
The scrollView only needs to scroll the content in the bottom view. Scroll Views have their own content, that is normally larger than the viewable area of the screen/view. The scroll view just allows you to pan the viewport of that view.
So add the bottom view and setup your constraints on that. add the scrollView to the bottom view and then add your content into the scrollView.
Make sure that your bottom view has clipToBounds set to true and then you should be able to keep the headers in place and just scroll the content.
I'll try and put an example together for you shortly.
EDIT:
I've just created this simple example which shows the scroll behaviour you need. This works in a playground or just as a simple view controller. I've intentionally not used auto layout or setup constraints due to time, but you will see what you need to solve your issue
class ViewController: UIViewController {
var topView: UIView!
var bottomView: UIView!
var scrollView: UIScrollView!
var contentView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
let screenSize = UIScreen.main.bounds
self.topView = UIView(frame: CGRect(x: 0, y: 0, width: screenSize.width, height: 100))
self.bottomView = UIView(frame: CGRect(x: 0, y: 100, width: screenSize.width, height: screenSize.height - 100))
self.scrollView = UIScrollView(frame: CGRect(x: 0, y: 100, width: screenSize.width, height: screenSize.height - 100))
self.contentView = UIView(frame: CGRect(x: 0, y: 0, width: screenSize.width, height: screenSize.height * 3))
self.view.backgroundColor = .white
self.view.addSubview(self.topView)
self.view.addSubview(self.bottomView)
self.bottomView.addSubview(self.scrollView)
self.scrollView.addSubview(self.contentView)
self.bottomView.clipsToBounds = true
self.scrollView.contentSize = CGSize(width: screenSize.width, height: screenSize.height * 3)
self.contentView.backgroundColor = .gray
}
}

Create an "add-player" view - Swift

I'm creating a game where the player has the possibility to add several players on a view controller :
Actually, I can add a new player and remove it :
The problem appears when I remove a player :
I want player 2 and player 3 goes below the textField "Name when I remove a player. How can I do this ?
ViewDidLoad :
// MARK: ADD BUTTON
let addButtonSize = CGSize(width: topPartSize.height, height: topPartSize.height)
let addButton = UIButton(frame: CGRect(x: enterPlayerPartSize.width - addButtonSize.width, y: 0, width: addButtonSize.width, height: addButtonSize.height))
addButton.titleLabel!.font = UIFont.fontAwesomeOfSize(addButtonSize.height / 2)
addButton.setTitle(String.fontAwesomeIconWithName(.Plus), forState: .Normal)
addButton.setTitleColor(UIColor.whiteColor(), forState: .Normal)
addButton.setTitleColor(UIColor(red: 1, green: 1, blue: 1, alpha: 0.5), forState: .Highlighted)
addButton.titleLabel?.textAlignment = .Center
addButton.layer.zPosition = 3
addButton.addTarget(self, action: #selector(PlayersViewController.addPlayer(_:)), forControlEvents: .TouchUpInside)
enterPlayerPart.addSubview(addButton)
// MARK: TEXT FIELD
let textFieldSize = CGSize(width: enterPlayerPartSize.width - playerIconSize.width - addButtonSize.width, height: enterPlayerPartSize.height)
textField = UITextField(frame: CGRect(x: playerIconSize.width, y: 0, width: textFieldSize.width, height: textFieldSize.height))
textField.font = UIFont(name: "Avenir-Light", size: textFieldSize.height / 2)
textField.attributedPlaceholder = NSAttributedString(string:"Name", attributes:[NSForegroundColorAttributeName: UIColor(red: 1, green: 1, blue: 1, alpha: 0.5)])
textField.textColor = UIColor.whiteColor()
textField.textAlignment = NSTextAlignment.Left
textField.returnKeyType = UIReturnKeyType.Done
textField.delegate = self
enterPlayerPart.addSubview(textField)
Functions :
// MARK: ADD PLAYER - FUNCTION
func addPlayer(sender: UIButton) {
if textField.text == nil || textField.text == "" {
print("Enter player name")
} else {
createNewPlayer(textField.text!)
}
}
// MARK: CREATE NEW PLAYER - FUNCTION
func createNewPlayer(playerName: String) {
let playerPartSize = CGSize(width: screenWidth, height: screenHeight * 0.1)
let playerPart = UIView(frame: CGRect(x: 0, y: playerPartSize.height * CGFloat(playersCount), width: playerPartSize.width, height: playerPartSize.height))
scrollView.addSubview(playerPart)
// PLAYER ICON
let playerIconSize = CGSize(width: playerPartSize.height, height: playerPartSize.height)
let playerIcon = UILabel(frame: CGRect(x: 0, y: 0, width: playerIconSize.width, height: playerIconSize.height))
playerIcon.font = UIFont.fontAwesomeOfSize(topPartSize.height / 2)
playerIcon.text = String.fontAwesomeIconWithName(.User)
playerIcon.textAlignment = .Center
playerIcon.textColor = UIColor.whiteColor()
playerPart.addSubview(playerIcon)
// REMOVE BUTTON
let removeButtonSize = playerIconSize
let removeButton = UIButton(frame: CGRect(x: playerPartSize.width - removeButtonSize.width, y: 0, width: playerIconSize.width, height: playerIconSize.height))
removeButton.titleLabel!.font = UIFont.fontAwesomeOfSize(removeButtonSize.height / 2)
removeButton.setTitle(String.fontAwesomeIconWithName(.Trash), forState: .Normal)
removeButton.setTitleColor(UIColor.whiteColor(), forState: .Normal)
removeButton.setTitleColor(UIColor(red: 1, green: 1, blue: 1, alpha: 0.5), forState: .Highlighted)
removeButton.titleLabel?.textAlignment = .Center
removeButton.addTarget(self, action: #selector(PlayersViewController.removePlayer(_:)), forControlEvents: .TouchUpInside)
playerPart.addSubview(removeButton)
// PLAYER NAME
let playerNameSize = CGSize(width: playerPartSize.width - playerIconSize.width - removeButtonSize.width, height: playerPartSize.height)
let playerNameLabel = UILabel(frame: CGRect(x: playerIconSize.width, y: 0, width: playerNameSize.width, height: playerNameSize.height))
playerNameLabel.font = UIFont(name: "Avenir-Light", size: playerNameSize.height / 2)
playerNameLabel.text = playerName
playerNameLabel.textAlignment = .Left
playerNameLabel.textColor = UIColor.whiteColor()
playerPart.addSubview(playerNameLabel)
// BOTTOM LINE
let bottomLineHeight = playerPartSize.height / 50
let bottomLine = UIView(frame: CGRect(x: 0, y: playerPartSize.height - bottomLineHeight, width: playerPartSize.width, height: bottomLineHeight))
bottomLine.backgroundColor = UIColor(hexColor: 0x1B1B1B)
playerPart.addSubview(bottomLine)
// PLAYERS COUNT ++
playersCount += 1
}
Thanks for your help !
You have two ways to perform it :
You seem using reusable components, so you should have a look on UITableView / UICollectionView. It will manage memory usage, and components layout
If you dont want to migrate to theses components, it recommend you to store all your player views in an array, and relayout them after an add/remove operation
something like :
var players : [UIView] = []
func createNewPlayer(playerName: String) {
//Do your stuff here
players.append(newCreatedPalyerView)
layout()
}
func layout() {
var yPosition = 0
for view in players {
view.frame.origin.y = yPosition
yPosition += view.frame.size.height
}
}
func removePlayer(player : UIView) {
self.players.remove(player)
self.layout()
}
You need to manage your scenario something like below :
First take all the UI of one player in one UIView. So you should have all your one player's stuff(playername label, remove button etc) in one view. (i think you have it in playerPart - that`s cool)
Now you are adding that playerPart directly in scrollview i think, If it is like that then you should not do like this. You should have one UIView of same size of scrollview in scrollview(same as content size of scrollview) and you should add playerPart to that view.
Now you can get all subviews of that view in array something like :
NSArray *viewArr = [tempView subviews];
Assume tempView is vied over scrollview as i have mentioned above,
you can get subview of scrollview also by calling same method.
Now for example you have four player(four view) in view (temp view or scrollview if tempview is not added on it) and you click remove on player 3 then you can get index of that object from that view array. for example,
you have array of total view which you have get from
NSArray *viewArr = [tempView subviews];
then you can get index of player3 i.e view3 like,
int index = [viewArr indexOfObject:player3];
now make one method or function which is called after every removed call and pass that index as parameter in that method.
In that method take one for loop, which start from this index and run till last object (view) of this viewArr. Decrease y of every coming view in this for loop so your all view which was belowed removed view will goes up.
Here is one my method i was using in my one project to remove view and manage other accordingly :
#pragma mark UpdateFrameOfViews
-(void)updateFrameOfViewWithTag : (int) myTag increament : (CGFloat)increament fromIndex : (int)index{
CustomSubClassView *tempView = [self.view viewWithTag:myTag];
UIScrollView *tempScroll = (UIScrollView*)[tempView superview];
NSArray *subViews = [tempView subviews];
BOOL status = NO;
for (int i = index; i <= subViews.count; i++) {
if (i < subViews.count) {
UIView *tempView1 = [subViews objectAtIndex:i];
CGFloat newY = tempView1.frame.origin.y + increament;
CGRect newFrame = CGRectMake(tempView1.frame.origin.x, newY, tempView1.frame.size.width, tempView1.frame.size.height);
tempView1.frame = newFrame;
status = YES;
}
if (index == subViews.count && i == subViews.count) {
// UIView *tempView1 = [subViews lastObject];
//
// CGFloat newY = tempView1.frame.origin.y + increament;
//
// CGRect newFrame = CGRectMake(tempView1.frame.origin.x, newY, tempView1.frame.size.width, tempView1.frame.size.height);
//
// tempView1.frame = newFrame;
status = YES;
}
// tempView1.backgroundColor = [UIColor orangeColor];
}
if (status) {
CGRect containerFrame = CGRectMake(0, 0, self.view.frame.size.width, tempView.frame.size.height+increament);
tempView.frame = containerFrame;
tempScroll.contentSize = CGSizeMake(tempView.frame.size.width, tempView.frame.size.height);
}
}
This method is for reference just, because here scenario is little bit different.
My code is in objective and is for reference only convert in swift required!!
This was how you manage it!!
Suggestion : it is better to use UITableView in your case!!!
Hope this will help :)

UITextField beginningOfDocument property returning nil

I have created a UITextField programatically. Now when I try to get its beginningOfDocument property, it is becoming nil.
Code is as follows:
public func addTextField(title:String?=nil) -> UITextField {
kWindowHeight -= 80.0
let txt: UITextField = UITextField(frame: CGRect(x: 13, y: 67, width: 213.00, height: 35));
txt.addTarget(self, action: "textFieldDidChange", forControlEvents: UIControlEvents.EditingChanged)
txt.borderStyle = UITextBorderStyle.RoundedRect
txt.layer.cornerRadius = 3
txt.layer.borderColor = UIColorFromRGB(0x1E8ADB).CGColor
txt.font = UIFont(name: kDefaultFont, size: 14)
txt.autocapitalizationType = UITextAutocapitalizationType.Words
txt.clearButtonMode = UITextFieldViewMode.WhileEditing
txt.layer.masksToBounds = true
txt.layer.borderWidth = 1.0
if title != nil {
txt.placeholder = title!
}
contentView.addSubview(txt)
return txt
}
In some other class I am calling:
let textField = AlertView().addTextField("Send SMS")
var begining = textField.beginingOfDocument
If you merely create an UITextField then immediately try to access beginningOfDocument property OR even after trying to add it to a superview, you will always get an uninitialized value.
Try to retrieve that property after the UITextField becomes the first responder and see the result :-)
Good luck.

Resources