Apple Swift Tutorial: Rating buttons layout - ios

I'm currently following this Apple's iOS Swift tutorial for beginner which explain how to create the rating star view and I don't understand something about the layout code. To layout the rating buttons they created from code using a for-in loop, they create another method:
override func layoutSubviews() {
var buttonFrame = CGRect(x: 0, y: 0, width: 44, height: 44)
// Offset each button's origin by the length of the button plus spacing.
for (index, button) in ratingButtons.enumerate() {
buttonFrame.origin.x = CGFloat(index * (44 + 5))
button.frame = buttonFrame
}
}
But in at the initialisation of buttons :
for _ in 0..<5 {
let button = UIButton(frame: CGRect(x: 0, y: 0, width: 44, height: 44))
button.backgroundColor = UIColor.redColor()
// Add event listener
button.addTarget(self, action: #selector(RatingControl.rateButtonTapped(_:)), forControlEvents: .TouchDown)
ratingButtons += [button]
addSubview(button)
}
Can't they simply adapt the code and, instead of using a wildcard ( _ ) in the for-in loop, use an index and do something like this ? :
for i in 0..<5 {
let button = UIButton(frame: CGRect(x: (i * 49), y: 0, width: 44, height: 44))
button.backgroundColor = UIColor.redColor()
// Add event listener
button.addTarget(self, action: #selector(RatingControl.rateButtonTapped(_:)), forControlEvents: .TouchDown)
ratingButtons += [button]
addSubview(button)
}
I am learning Swift and I don't understand why they override the layoutSubviews method instead of doing that at initialisation. Tell me which way is the cleaner please. Here is the tutorial link: https://developer.apple.com/library/ios/referencelibrary/GettingStarted/DevelopiOSAppsSwift/Lesson5.html
Regards,

The issue is that the frame for the containing rect is not known before the viewWillLayoutSubviews method is called. The layoutSubviews method is called after this method, and each time layoutIfNeeded is called for the view.
This means that the layout can be adapted to updates in the view and only called from one method.
Whether one method is cleaner than another is subjective, and really up to each individual to determine, but at least you should write your UI code in a way that follows the lifecycle of the view.

Related

Can I align a button's label with its layout margins?

One way to left-align a UIButton's title is to set the contentHorizontalAlignment to .left (or .leading). But this places the title flush with the left edge of the button with no margin. A common way to add some margin is to set the contentEdgeInstets.
But my button extends from once edge of the screen to the other, so I would like the left and right margins to honor the layoutMargins. These margins might change as the view is resized or the device is rotated.
Is there a way to set the button's insets to observe these margins? Or should I create a button from a custom view where I can use my own label and anchor it to the layoutMarginsGuide?
I guess you can manually set the margin of the button using titleEdgeInsets to match the inset of the Cancel Button.
Have a look at the following, the two buttons are exactly the same aside from the origin.y and the titleEdgeInsets:
let view = UIView(frame: CGRect(x: 0, y: 0, width: 400, height: 400))
view.backgroundColor = .white
let button = UIButton(frame: CGRect(x: 40, y: 40, width: 200, height: 80))
button.setTitle("Some really long title", for: .normal)
button.backgroundColor = .red
button.setTitleColor(.black, for: .normal)
let button2 = UIButton(frame: CGRect(x: 40, y: 160, width: 200, height: 80))
button2.setTitle("Some really long title", for: .normal)
button2.backgroundColor = .red
button2.setTitleColor(.black, for: .normal)
button2.titleEdgeInsets = UIEdgeInsets(top: 20, left: 20, bottom: 20, right: 20)
view.addSubview(button)
view.addSubview(button2)
It gives the following result:
Hope this helps :)
A subclassed button may work for you...
class RespectSuperviewMarginButton: UIButton {
override func layoutSubviews() {
super.layoutSubviews()
if let sv = superview {
contentEdgeInsets.left = sv.layoutMargins.left
}
}
}
My current best answer is: no, this is not possible without subclassing.
The subclass implementation I am currently using is simple enough:
class LayoutMarginRespectingButton: UIButton {
override func layoutSubviews() {
super.layoutSubviews()
contentEdgeInsets = layoutMargins
}
}

Position UIButton in UITextview

I would like to place a "forgot?" Button into my Password Textfield. If nothing is in the Textfield the user should be able to click it and another ViewController should pop up. The only thing I managed to do is what you can see in the picture down below. My problem is that the button is not clickable and that it is not on the same level as the placeholder text. Any ideas on how to solve this two problems?
let button = UIButton(type: .custom)
button.setTitle("vergessen?", for: .normal)
button.frame = CGRect(x: CGFloat(0), y: CGFloat(0), width: CGFloat(100), height: CGFloat(100))
button.addTarget(self, action: #selector(self.refresh), for: .touchUpInside)
passwordTextField.rightView = button
passwordTextField.rightViewMode = .unlessEditing
In the file you have subclassed from my answer add another function in that file
// Modify the values as required
override func rightViewRect(forBounds bounds: CGRect) -> CGRect {
let offset = -20
let width = 100
let height = width
let x = Int(bounds.width) - width - offset
let y = offset
let rightViewBounds = CGRect(x: x, y: y, width: width, height: height)
return rightViewBounds
}
Now you can remove the follow line
button.frame = CGRect(x: CGFloat(0), y: CGFloat(0), width: CGFloat(100), height: CGFloat(100))
Output
And regarding the button click event. Remove your code as you mention its not connected
IBAction func refresh(_ sender: Any) { }
And add the following code in the same file where the button is created.
#objc func refresh() {
// your vc code here
print("in refresh")
}
The above code hooks in with addTarget code you have.
button.addTarget(self, action: #selector(self.refresh), for: .touchUpInside)
Hope this helps.
I suggest to create a xib file and its relevant view for text items that have a button inside it. So you would be able to reuse it elsewhere in the future (this or another projects)
By defining constant height (100) you will experience ugly and misplaced UI in different iOS devices.
Here it is what you should do :
Define a xib file for your custom UITextView
Create constraints for it so it width and height defined by its parent.Also define your forgot UIButton in relative to your UITextView.
Define its (xib) relevant UIView class
Use it in your Storyboard
You can use Storyboard. Make a helper view for the password TextField and Forgot Button.
-Set the Helper view same width and height with the email TextField.
-Add a TextField and a Button inside the helper view and then you can decide for the password TextField width and the Forgot Button width.
-Set constrains for the TextField and Button
I set green color to understand what the helper View does.
Update
I just used your code and it works fine.Check your textfield constrains again. This is what I used.
override func viewDidLoad() {
super.viewDidLoad()
let button = UIButton(type: .system)
button.setTitle("vergessen?", for: .normal)
button.setTitleColor(#colorLiteral(red: 0.3647058904, green: 0.06666667014, blue: 0.9686274529, alpha: 1), for: .normal)
button.frame = CGRect(x: CGFloat(0), y: CGFloat(0), width: CGFloat(100), height: CGFloat(100))
button.addTarget(self, action: #selector(self.refresh), for: .touchUpInside)
textfFeld.rightView = button
textfFeld.rightViewMode = .unlessEditing
}
#objc func refresh(_ sender: Any) {
print("Hello")
}
The only think I changed is the button type from .custom to .system.

Fixing "use of unresolved identifier 'addTarget'" while adding func to button click event

I've created a UIButton programmatically as shown below:
let buttons: [UIButton] = [UIButton(frame: CGRect(x: 0, y: 0, width: 50, height: 50))];
Now if I try to add a function to it programmatically like this:
[buttons[0] addTarget:self action:#selector(buttonClicked:)forControlEvents:UIControlEventTouchUpInside]
I get an error saying that addTarget is not defined.
How do I fix this?
you are try to use the Objective-C syntax in swift, this is entirely wrong, use your code as like
buttons.first?.addTarget(self, action: #selector(self.buttonClicked(_:)), for: .touchUpInside)
and handle the action as like
#objc func buttonClicked( _ sender: UIButton) {
print("buttonClicked Action Found")
}
Ref : Apple Document for UIButton
First of all you are creating [UIButton] which is Array of UIButton and it's not a single Button.
You can not create Array of UIButton that way. You will need a for loop for that and you need to update the frame accordingly.
And you can create a single UIButton this way:
let button = UIButton(frame: CGRect(x: 0, y: 0, width: 50, height: 50))
then you can add it into the UIView this way:
self.view.addSubview(button)
Without above line it your button will not show into your screen.
Next if you want to add action to that button you can do it by adding this line in your button code:
button.addTarget(self, action: #selector(buttonClicked), for: .touchUpInside)
and it will need a helper method which will execute when button will click.
#objc func buttonClicked(_ sender: UIButton) {
//Perform your action when button is clicked.
}
And you also need to apply backgroundColor and setTitle to the button.
and your final code will look like:
let button = UIButton(frame: CGRect(x: 100, y: 100, width: 100, height: 50))
button.backgroundColor = UIColor.green
button.setTitle("Test Button", for: .normal)
button.addTarget(self, action: #selector(buttonClicked), for: .touchUpInside)
self.view.addSubview(button)

Swift 3 add remove subview and its content dynamically

I am writing my first Swift app for quiz. Each question has random way to render on screen as below screenshot.
I am programming app without story board ie. programmatically. I want to create simple pagination flow for each question within single viewcontroller without using Collocationview, Tableview or navigation.
What I did so far? I have simple viewcontroller with UIView() added as subview. I am adding question components dynamically. Now once user click on continue I wanted remove subview and add new subview with new question. I am able to remove subview but contents on subview seems to be still there as I can see its overwriting.
To get more clarification please view my code.
import UIKit
class QuizController: UIViewController {
let subView = UIView()
var currentQuestion:Int = 1;
let questions = ["This is question 1", "Hello to question 2", "Question 3"]
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = .white
setup_layout()
}
func setup_layout(){
let closeBtn = UIButton(frame: CGRect(x: 5, y: 10, width: 200, height: 50))
closeBtn.backgroundColor = UIColor.red
closeBtn.setTitle("Close", for: .normal)
closeBtn.addTarget(self, action: #selector(close), for: .touchUpInside)
view.addSubview(closeBtn)
//dynamic view
create_subview()
}
func nextQuestion(){
print("Show next")
if let viewWithTag = self.view.viewWithTag(currentQuestion) {
viewWithTag.removeFromSuperview()
currentQuestion += 1;
create_subview()
} else {
print("No!")
}
}
func create_subview(){
let heightOfView = view.frame.size.height
let widthOfView = view.frame.size.width
subView.tag = currentQuestion
subView.backgroundColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0.2)
subView.frame = CGRect(x: 0, y: 60, width: self.view.frame.width, height: heightOfView - 60)
self.view.addSubview(subView)
let txtLabel1 = UILabel(frame: CGRect(x: 35, y: 120, width: widthOfView , height: 20))
txtLabel1.text = questions[currentQuestion-1]
txtLabel1.font = txtLabel1.font.withSize(12)
subView.addSubview(txtLabel1)
let nextBtn = UIButton(frame: CGRect(x: 5, y: 300, width: 200, height: 50))
nextBtn.backgroundColor = UIColor.red
nextBtn.setTitle("Continue", for: .normal)
nextBtn.addTarget(self, action: #selector(nextQuestion), for: .touchUpInside)
subView.addSubview(nextBtn)
}
func close(){
self.dismiss(animated: true, completion: nil)
}
}
And this is what I see which I click continue.
NOTE: Initially thought using Collocation or Table view will be more appropriate as I can set to scroll horizontally for each question and fetch questions using REST API and place to each cell. But I want to present next screen to user only once then click on continue. I guess with collectionview user can move to next screen on swipe.
Just found the answer. I assumed removing subview will also remove all components on subview by itself ie. UILable and UIButton in my case.
I have to remove them separately.
for subview in self.subView.subviews {
subview.removeFromSuperview()
}
Now i can add tag to components and remove like this:
for subview in self.subView.subviews {
if (subview.tag == 1) {
subview.removeFromSuperview()
}
}
Why are you removing superviews and adding again and again??
Simply change UILabel.text :)

Square Not Appearing

I am Programatically creating a square in swift. Below is my code, but for some reason nothing appears.
let PB1 = UIButton()
func SetUpPB1() {
PB1.bounds = CGRect(x: 0, y: 0, width: 50, height: 50)
PB1.setBackgroundImage(UIImage(named: "RS"), forState:UIControlState.Normal)
PB1.center = CGPoint(x: 200, y: 200)
PB1.addTarget(self, action: Selector("PB1Pressed"), forControlEvents: UIControlEvents.TouchUpInside)
self.view?.addSubview(PB1)
}
P.S I need the setup function or the app will crash.
You said "...how do I call the function? I thought I did by writing it?" That is wrong. Functions don't get called unless you call them - nested functions as well.
If SetUpPB1() is a nested function inside your viewDidLoad and you only call it once it's probably cleaner to just get rid of it and put that code inline in viewDidLoad.
Otherwise, you need something like this:
func viewDidLoad() {
let PB1 = UIButton()
func setUpPB1() {
PB1.bounds = CGRect(x: 0, y: 0, width: 50, height: 50)
PB1.setBackgroundImage(UIImage(named: "RS"),
forState:UIControlState.Normal)
PB1.center = CGPoint(x: 200, y: 200)
PB1.addTarget(self, action: Selector("PB1Pressed"),
forControlEvents: UIControlEvents.TouchUpInside)
self.view?.addSubview(PB1)
}
setUpPB1() //Add this line to call the function
}

Resources