While practicing programmatic constraints using anchors ,I came across a problem that hides the visibility of the button when using an iPhone 8 or 8 plus simulator as shown in the image below. This is due to the current coordinate values I set for the button. However the button becomes visible when I switch to an iPhone 12. How do I set the co-ordinate values in a way that makes the button visible and in same positions on the iPhone 8 or 8 plus ?
let iosImageView: UIImageView = {
let imageview = UIImageView(image: #imageLiteral(resourceName: "icons8-ios-logo-128"))
imageview.translatesAutoresizingMaskIntoConstraints = false
return imageview
}()
let press: UIButton = {
let buttonPress = UIButton.init(type: .roundedRect)
buttonPress.setTitle("Tap Me", for: .normal)
buttonPress.backgroundColor = .black
buttonPress.frame = CGRect(x: 110, y: 700, width: 200, height: 50)
return buttonPress
}()
override func viewDidLoad() {
super.viewDidLoad()
addSubviews()
setConstraints()
// Do any additional setup after loading the view.
}
func addSubviews(){
view.addSubview(iosImageView)
view.addSubview(press)
}
func setConstraints(){
NSLayoutConstraint.activate([
iosImageView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
iosImageView.topAnchor.constraint(equalTo: view.topAnchor, constant: 200),
iosImageView.widthAnchor.constraint(equalToConstant: 200),
iosImageView.heightAnchor.constraint(equalToConstant: 200),
press.centerXAnchor.constraint(equalTo: view.centerXAnchor),
press.leadingAnchor.constraint(equalTo: view.leadingAnchor),
press.trailingAnchor.constraint(equalTo: view.trailingAnchor)
])
}
Your constraints for press are invalid. You failed to specify a vertical position. And you forgot to set translatesAutoresizingMaskIntoConstraints to be false. I’m surprised the button ever appears.
(Also cut the line about buttonPress.frame. Frame and constraints are opposites; use one or the other.)
Related
I am trying to build a imageview with a button that attached to itself for settings page. I want to tell users to click to image if they want to change it. So what I wanna to do is similar to : this
I was designin my app with storyboard but I couldn't find a way to do it.
So I tried to set constraints programmatically.
For example,
`
editButton.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
editButton.trailingAnchor.constraint(equalTo: imageView.trailingAnchor, constant: -66),
editButton.topAnchor.constraint(equalTo: imageView.bottomAnchor, constant: -60)
])
`
This constraints works for iPad screen but not for any iPhones. There must be another way to design such things. How can I do it?
All of this can be done with storyboards or using constraints in code. Should be the same for iPhone or iPad. For example:
//: A UIKit based Playground for presenting user interface
import UIKit
import PlaygroundSupport
class MyViewController : UIViewController {
override func loadView() {
let view = UIView()
view.backgroundColor = .white
let myImageView = UIImageView()
let myImageViewCornerRadius: CGFloat = 40.0
// Just using a color for the example...
myImageView.backgroundColor = .blue
myImageView.layer.cornerRadius = myImageViewCornerRadius
myImageView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(myImageView)
let myEditButton = UIButton()
// Again using a color...
myEditButton.backgroundColor = .red
myEditButton.translatesAutoresizingMaskIntoConstraints = false
myEditButton.layer.cornerRadius = 10.0
view.addSubview(myEditButton)
NSLayoutConstraint.activate([
myImageView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
myImageView.centerYAnchor.constraint(equalTo: view.centerYAnchor),
myImageView.widthAnchor.constraint(equalToConstant: 80.0),
myImageView.heightAnchor.constraint(equalToConstant: 80.0),
myEditButton.centerYAnchor.constraint(equalTo: myImageView.bottomAnchor,
constant: -myImageViewCornerRadius / 4.0),
myEditButton.centerXAnchor.constraint(equalTo: myImageView.trailingAnchor,
constant: -myImageViewCornerRadius / 4.0),
myEditButton.widthAnchor.constraint(equalToConstant: 20.0),
myEditButton.heightAnchor.constraint(equalToConstant: 20.0)
])
self.view = view
}
}
// Present the view controller in the Live View window
PlaygroundPage.current.liveView = MyViewController()
I would make the origin of the frame of the button reference the frame of the profile photo. So the button is rendered in the same place relative to the photo no matter the size of the photo. Maybe something like this:
editButton.frame = CGRect(x: profileImageView.width - 60, y: profileImageView.height - 66, width: 50, height: 50)
You could also consider adding a tapGestureRecognizer to the photo to trigger the edit option.
I'm trying to create something like this. I've been working with SwiftUI recently so I know I could create that by adding an image, text and button (the I'm flexible text is the label for a button/NavigationLink) to a zstack. but I'm looking around trying to see if there's anyway to do that in UIKit. preferably without using storyboards. I'm open to a cocoapods library or whatever if that's what it takes. I've looked around and explored using SwiftUI to create the desired ZStack and then use it in my UIKit with a UIHostingController but because it involves a button/navigationlink. seeing as how the NavigationLink would require the destination to conform to a View, I wanted to ask around before converting even more of my project to swiftui. I was more hoping this project would be for giving me more experience building views in UIKit without storyboards so I'd prefer to do that instead of using SwiftUI. if that's possible I guess.
I've tried searching around but all my google searches involving UIButtons and images just link to posts about setting the image in a UIButton.
since you wanted to get more experience in creating views using UIKit, I've created a view that inherits from UIView that you can reuse. There's quite a lot of code to get the same result in UIKit. The code and output are provided below.
NOTE: Read the comments provided
Code
class ImageCardWithButton: UIView {
lazy var cardImage: UIImageView = {
let image = UIImageView()
image.translatesAutoresizingMaskIntoConstraints = false // To flag that we are using Constraints to set the layout
image.image = UIImage(named: "dog")
image.contentMode = .scaleAspectFill
return image
}()
lazy var gradientView: UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false // IMPORTANT IF YOU ARE USING CONSTRAINTS INSTEAD OF FRAMES
return view
}()
// VStack equivalent in UIKit
lazy var contentStack: UIStackView = {
let stack = UIStackView()
stack.translatesAutoresizingMaskIntoConstraints = false
stack.axis = .vertical
stack.distribution = .fillProportionally // Setting the distribution to fill based on the content
return stack
}()
lazy var titleLabel: UILabel = {
let label = UILabel()
label.textAlignment = .center
label.numberOfLines = 0 // Setting line number to 0 to allow sentence breaks
label.text = "Let your curiosity do the booking"
label.font = UIFont(name: "Raleway-Semibold", size: 20) // Custom font defined for the project
label.textColor = .white
return label
}()
lazy var cardButton: UIButton = {
let button = UIButton()
button.translatesAutoresizingMaskIntoConstraints = false
button.backgroundColor = .white
button.setTitle("I'm flexible", for: .normal)
button.setTitleColor(.blue, for: .normal)
// button.addTarget(self, action: #selector(someObjcMethod), for: .touchUpInside) <- Adding a touch event and function to invoke
return button
}()
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
private func commonInit() {
self.addSubview(cardImage) // Adding the subview to the current view. i.e., self
// Setting the corner radius of the view
self.layer.cornerRadius = 10
self.layer.masksToBounds = true
NSLayoutConstraint.activate([
cardImage.leadingAnchor.constraint(equalTo: self.leadingAnchor),
cardImage.trailingAnchor.constraint(equalTo: self.trailingAnchor),
cardImage.topAnchor.constraint(equalTo: self.topAnchor),
cardImage.bottomAnchor.constraint(equalTo: self.bottomAnchor),
])
setupGradientView()
addTextAndButton()
}
private func setupGradientView() {
let height = self.frame.height * 0.9 // Height of the translucent gradient view
self.addSubview(gradientView)
NSLayoutConstraint.activate([
gradientView.leadingAnchor.constraint(equalTo: self.leadingAnchor),
gradientView.trailingAnchor.constraint(equalTo: self.trailingAnchor),
gradientView.bottomAnchor.constraint(equalTo: self.bottomAnchor),
gradientView.heightAnchor.constraint(equalToConstant: height)
])
// Adding the gradient
let colorTop = UIColor.clear
let colorBottom = UIColor.black
let gradientLayer = CAGradientLayer()
gradientLayer.colors = [colorTop.cgColor, colorBottom.cgColor]
gradientLayer.locations = [0.0, 1.0]
gradientLayer.frame = CGRect(
x: 0,
y: self.frame.height - height,
width: self.frame.width,
height: height)
gradientView.layer.insertSublayer(gradientLayer, at:0)
print(self.frame)
}
private func addTextAndButton() {
// Adding the views to the stackview
contentStack.addArrangedSubview(titleLabel)
contentStack.addArrangedSubview(cardButton)
gradientView.addSubview(contentStack)
NSLayoutConstraint.activate([
contentStack.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 20),
contentStack.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -20), // Negative for leading and bottom constraints
contentStack.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: -20), // Negative for leading and bottom constraints
cardButton.heightAnchor.constraint(equalToConstant: 60)
])
cardButton.layer.cornerRadius = 30 // Half of the height of the button
}
}
Output
Important pointers
You can create the layout using constraints or frames. In case you are using constraints, it is important to set a views .translatesAutoresizingMaskIntoConstraints to false (You can read the documentation for it).
NSLayoutConstraint.activate([...]) Is used to apply an array of constraints at once. Alternatively, you can use:
cardImage.leadingAnchor.constraint(...)isActivated = true
for individual constraints
Manual layout of the views will sometimes require padding. So for this you will have to use negative or positive values for the padding based on the edge (side) of the view you are in. It's easy to remember to set the value of the padding in the direction of the centre of the view.
E.x., From the leading/left edge, you will need to add a padding of 10 towards the centre of the view or -10 from the right/trailing side towards the centre.
I'm trying to add a number of buttons programmatically in a grid, where the number of rows exceeds the screen size, so I want them in a UIScrollView.
My minimal working example is the following:
class ViewController: UIViewController {
//center label and down a bit
let label = UILabel(frame: CGRect(x: UIScreen.main.bounds.width/2 - 100, y: 50, width: 200, height: 200))
// start scroll view underneath label (why not y: 250?)
let scrollView = UIScrollView(frame: CGRect(x: 0, y: 50, width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height))
override func viewDidLoad() {
super.viewDidLoad()
// add label to view
self.view.addSubview(label)
// add Scrollview
self.view.addSubview(scrollView)
// uncommenting the following also doesn't work
//scrollView.contentSize = CGSize(width: UIScreen.main.bounds.width, height: 4000)
// add buttons to scroll view in 5x21 grid
var idx = 1
for row in 0...20 {
for col in 0...4 {
let button = UIButton(frame: CGRect(x: col*50, y: row*50 + 200, width: 50, height: 50))
button.backgroundColor = .gray
button.setTitle("\(idx)", for: .normal)
button.addTarget(self, action: #selector(buttonAction), for: .touchUpInside)
// add button to scroll view
scrollView.addSubview(button)
idx += 1
}
}
}
#objc func buttonAction(sender: UIButton!) {
label.text = sender.title(for: .normal)
}
}
Unfortunately the scrolling doesn't work. Even when I explicitly set a larger vertical content size (see commented line) //scrollView.contentSize
I'm wondering if it has something to do with setting x and y values for the buttons fixed? But then how else would I align them in a grid?
So, how do I get the button alignment I want while getting a working ScrollView?
You really should be using Auto-Layout!!!
By far, the easiest way to add buttons (or any view) to a scroll view - in a grid layout like you're asking - and have it automatically determine the scrolling area is with stack views.
Here's a quick example:
class ViewController: UIViewController {
let label = UILabel()
let scrollView = UIScrollView()
override func viewDidLoad() {
super.viewDidLoad()
// start with some text in the label
label.text = "Tap a button"
// center the text in the label
label.textAlignment = .center
// so we can see the frames
label.backgroundColor = .yellow
scrollView.backgroundColor = UIColor(white: 0.9, alpha: 1.0)
// create a vertical stack view to hold the rows of buttons
let verticalStackView = UIStackView()
verticalStackView.axis = .vertical
// we're going to use auto-layout
label.translatesAutoresizingMaskIntoConstraints = false
scrollView.translatesAutoresizingMaskIntoConstraints = false
verticalStackView.translatesAutoresizingMaskIntoConstraints = false
// add label to view
self.view.addSubview(label)
// add Scrollview to view
self.view.addSubview(scrollView)
// add stack view to scrollView
scrollView.addSubview(verticalStackView)
// now let's create the buttons and add them
var idx = 1
for row in 0...20 {
// create a "row" stack view
let rowStack = UIStackView()
// add it to the vertical stack view
verticalStackView.addArrangedSubview(rowStack)
for col in 0...4 {
let button = UIButton()
button.backgroundColor = .gray
button.setTitle("\(idx)", for: .normal)
button.addTarget(self, action: #selector(buttonAction), for: .touchUpInside)
// add button to row stack view
rowStack.addArrangedSubview(button)
// buttons should be 50x50
NSLayoutConstraint.activate([
button.widthAnchor.constraint(equalToConstant: 50.0),
button.heightAnchor.constraint(equalToConstant: 50.0),
])
idx += 1
}
}
// finally, let's set our constraints
// respect safe-area
let safeG = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
// constrain label
// 50-pts from top
// 80% of the width
// centered horizontally
label.topAnchor.constraint(equalTo: safeG.topAnchor, constant: 50.0),
label.widthAnchor.constraint(equalTo: safeG.widthAnchor, multiplier: 0.8),
label.centerXAnchor.constraint(equalTo: safeG.centerXAnchor),
// constrain scrollView
// 50-pts from bottom of label
// Leading and Trailing to safe-area with 8-pts "padding"
// Bottom to safe-area with 8-pts "padding"
scrollView.topAnchor.constraint(equalTo: label.bottomAnchor, constant: 50.0),
scrollView.leadingAnchor.constraint(equalTo: safeG.leadingAnchor, constant: 8.0),
scrollView.trailingAnchor.constraint(equalTo: safeG.trailingAnchor, constant: -8.0),
scrollView.bottomAnchor.constraint(equalTo: safeG.bottomAnchor, constant: -8.0),
// constrain vertical stack view to scrollView Content Layout Guide
// 8-pts all around (so we have a little "padding")
verticalStackView.topAnchor.constraint(equalTo: scrollView.contentLayoutGuide.topAnchor, constant: 8.0),
verticalStackView.leadingAnchor.constraint(equalTo: scrollView.contentLayoutGuide.leadingAnchor, constant: 8.0),
verticalStackView.trailingAnchor.constraint(equalTo: scrollView.contentLayoutGuide.trailingAnchor, constant: -8.0),
verticalStackView.bottomAnchor.constraint(equalTo: scrollView.contentLayoutGuide.bottomAnchor, constant: -8.0),
])
}
#objc func buttonAction(sender: UIButton!) {
label.text = sender.title(for: .normal)
}
}
and this is the result:
then we'll scroll down and tap a different button:
If you want spacing between the buttons, you can add something like this at the end of viewDidLoad():
// suppose we want 8-pts spacing between the buttons?
verticalStackView.spacing = 8.0
verticalStackView.arrangedSubviews.forEach { v in
if let stack = v as? UIStackView {
stack.spacing = 8.0
}
}
Now it looks like this:
If you want the grid of buttons centered horizontally, or if you want the spacing (or button sizes) to adjust to fit, there are a few different ways to do that... You didn't describe your ultimate goal in your question, but this should be a good starting point.
I did the same thing with a collection view which is a subclass of scrollview and that would fit your need in terms of rows/columns.
In addition, to manage touches for a scrollview you have to implement a UIScrollViewDelegate for the scrollview, in particular to distinguish between touching and scrolling otherwise you will possibly end up with unpredictable behaviour.
I have a view with a number of subviews. The subviews all have size constraints, built in the storyboard.
part of viewDidLoad() is to programmatically add a number of buttons. And I'm using the subview's bounds to known where to add the buttons to take up available screen real estate. As a simplified example:
override func viewDidLoad()
{
super.viewDidLoad()
let bottom = buttonView.bounds.maxY
let right = buttonView.bounds.maxX
let button = UIButton()
button.frame = CGRect(x: CGFloat(0.0),
y: CGFloat(0.0),
width: CGFloat(right),
height: CGFloat(bottom))
button.addTarget(self, action: #selector(buttonPushed(_:)), for: .touchUpInside)
buttonView.addSubview(button)
print("buttonview frame = \(buttonView.frame)")
}
override func viewWillAppear()
{
super.viewWillAppear()
print("buttonview frame = \(buttonView.frame)")
}
What I see happen in the simulator (and on hardware) is that the views are all sized according to how I had it designed in the storyboard, matching the hardware I had designed it on (iPhone 11). When I run it on an iPhone 8, my subviews all run off screen, and the first print statement indicates a width of 414.0. The constraints are applied sometime before viewWillAppear, and the second print statement indicates a width of 375.0. But my button isn't resized to fit. It still continues off screen.
How do I programmatically add my button subviews (this example only has the 1, code has 20 being built in a loop) to adhere to the constraints of the superview bounds, after I know what they actually are?
you have to add constraint by code, for example.
1) first I will create the button.
lazy var closeButton : UIButton = {
let button = UIButton(type: .custom)
button.translatesAutoresizingMaskIntoConstraints = false
button.backgroundColor = .blue
button.addTarget(self, action: #selector(buttonAction), for: .touchUpInside)
return button
}()
2) with this code I will add the button to the main view, and set the constraint.
self.view.addSubview(closeButton)
NSLayoutConstraint.activate([
closeButton.heightAnchor.constraint(equalToConstant: 20),
closeButton.widthAnchor.constraint(equalToConstant: 20),
closeButton.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: -20),
closeButton.centerYAnchor.constraint(equalTo: self.view.centerYAnchor)]
but if you want to understand really how work this please the follow link
How to add constraints programmatically using Swift
Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 5 years ago.
Improve this question
for my app I implemented a collectionview showing cards, that are rotated in a carousel-like animation. My problem is that the cards themselves resize correctly when swiped, but the fonts stay the same size or resize incorrectly. To be specific, the answers (bottommost 5 labels) are in a stackview.
Things I tried:
AutoLayout
Autoshrink
adjustsFontSizeToFitWidth
any number of different constraints
I attached a few screenshots below, where I colored the labels for better visibility.
To change the size of a view and have all of it's subviews / buttons / labels / etc scale with it - including label fonts, you are better off using CGAffineTransform for scaling.
Here is a simple example. It can be pasted into a Playground page to see the effect:
import UIKit
import PlaygroundSupport
class TestViewController : UIViewController {
let theStackView: UIStackView = {
let sv = UIStackView()
sv.translatesAutoresizingMaskIntoConstraints = false
sv.axis = .vertical
sv.distribution = .equalCentering
sv.alignment = .center
return sv
}()
let theContainerView: UIView = {
let v = UIView()
v.translatesAutoresizingMaskIntoConstraints = false
v.backgroundColor = .green
return v
}()
let btn: UIButton = {
let b = UIButton()
b.setTitle("Tap to Scale", for: .normal)
b.backgroundColor = .red
b.translatesAutoresizingMaskIntoConstraints = false
return b
}()
// on button tap, scale the Container view by 50% both ways
// note that Container view's subviews also scale
func btnTapped(_ sender: Any) {
theContainerView.transform = CGAffineTransform(scaleX: 0.5, y: 0.5)
}
override func viewDidLoad() {
super.viewDidLoad()
// add the button to self.view
self.view.addSubview(btn)
// button position
btn.centerXAnchor.constraint(equalTo: self.view.centerXAnchor).isActive = true
btn.topAnchor.constraint(equalTo: self.view.topAnchor, constant: 20.0).isActive = true
// add a target for the button tap
btn.addTarget(self, action: #selector(btnTapped(_:)), for: .touchUpInside)
// add our "Container" view
view.addSubview(theContainerView)
// add our Stack view to the Container view
theContainerView.addSubview(theStackView)
// add 5 labels to the Stack view
for i in 1...5 {
let label = UILabel()
label.font = UIFont.systemFont(ofSize: 20.0)
label.text = "This is Label \(i)"
label.backgroundColor = .cyan
label.translatesAutoresizingMaskIntoConstraints = false
theStackView.addArrangedSubview(label)
}
// pin Container view 20-pts from the bottom of the button, and 8-pts from left, right and bottom
theContainerView.topAnchor.constraint(equalTo: btn.bottomAnchor, constant: 20.0).isActive = true
theContainerView.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 8.0).isActive = true
theContainerView.rightAnchor.constraint(equalTo: view.rightAnchor, constant: -8.0).isActive = true
theContainerView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -8.0).isActive = true
// pin Stack view 8-pts from top, left, right and bottom of the Container view
theStackView.topAnchor.constraint(equalTo: theContainerView.topAnchor, constant: 8.0).isActive = true
theStackView.leftAnchor.constraint(equalTo: theContainerView.leftAnchor, constant: 8.0).isActive = true
theStackView.rightAnchor.constraint(equalTo: theContainerView.rightAnchor, constant: -8.0).isActive = true
theStackView.bottomAnchor.constraint(equalTo: theContainerView.bottomAnchor, constant: -8.0).isActive = true
}
}
let vc = TestViewController()
vc.view.backgroundColor = .yellow
PlaygroundPage.current.liveView = vc