I'm trying to adjust layout without Storyboard in Swift. But using the code below, I can't adjust layout on iPhone Plus series (6+, 7+, and 8+).
Case1
Using images based on 640x1136 size, it works on iPhone8, 8+, and X.
class Constants {
// the image parts are based on 640x1136
static let guiPartsWidthOnDesign = CGFloat(640)
static var guiPartsMultiplier: CGFloat = UIScreen.main.bounds.width / guiPartsWidthOnDesign
}
Make buttons
for i in 1..<5 {
let subButton = UIImageView.init(frame: CGRect(
x: UIScreen.main.bounds.size.width/2-50*Constants.guiPartsMultiplier,
y: (100*Constants.guiPartsMultiplier)+CGFloat(i*100),
width: 187*Constants.guiPartsMultiplier,
height: 100*Constants.guiPartsMultiplier))
background.addSubview(subButton)
subButton.backgroundColor = UIColor.blue
subButton.alpha = 1.0
}
The results on multiple devices look the same layout.
Case2
Using images based on 750x1334 size, it didn't work well on iPhone8+.
class Constants {
// the images are based on 750x1334
static let guiPartsWidthOnDesign = CGFloat(750)
static var guiPartsMultiplier: CGFloat = UIScreen.main.bounds.width / guiPartsWidthOnDesign
}
Make buttons (same to the previous code.)
Result
As you see, the blue square's y positions are different between iPhone8 and 8 Plus.
(The results of iPhone8 and X are the same.)
How can I solve this problem?
UPDATE
I tried to use UIStackView, but it didn't work well.
UPDATE2
//Define this as class variable
fileprivate lazy var stackView: UIStackView = {
let sv = UIStackView()
sv.backgroundColor = UIColor.green
sv.translatesAutoresizingMaskIntoConstraints = false
sv.axis = .vertical
sv.alignment = .fill
sv.distribution = .fillEqually
sv.spacing = 10
return sv
}()
override func viewDidLoad() {
super.viewDidLoad()
self.view.addSubview(stackView)
stackView.centerXAnchor.constraint(equalTo: self.view.centerXAnchor).isActive = true
stackView.centerYAnchor.constraint(equalTo: self.view.centerYAnchor).isActive = true
let background = UIImageView.init(frame: UIScreen.main.bounds)
background.image = UIImage(named:"BG")
stackView.addSubview(background)
// background.translatesAutoresizingMaskIntoConstraints = false
// background.contentMode = .scaleAspectFill
for i in 1..<5 {
let subButton = UIImageView()
subButton.translatesAutoresizingMaskIntoConstraints = false
subButton.backgroundColor = UIColor.blue
subButton.alpha = 1.0
stackView.addArrangedSubview(subButton)
subButton.widthAnchor.constraint(equalTo: self.view.widthAnchor, multiplier: 0.3).isActive = true
}
}
*I'm sorry, I don't know but I couldn't upload the screenshot.
UPDATE3
I used the following code and the result was like this on all type devices:
//Define this as class variable
fileprivate lazy var stackView: UIStackView = {
let sv = UIStackView()
sv.translatesAutoresizingMaskIntoConstraints = false
sv.axis = .vertical
sv.alignment = .fill
sv.distribution = .fillEqually
sv.spacing = 10
return sv
}()
fileprivate lazy var catBg: UIImageView = {
let iv = UIImageView()
iv.translatesAutoresizingMaskIntoConstraints = false
iv.contentMode = .scaleAspectFill
iv.image = UIImage(named: "BG")
return iv
}()
override func viewDidLoad() {
super.viewDidLoad()
self.view.addSubview(catBg)
catBg.centerXAnchor.constraint(equalTo: self.view.centerXAnchor).isActive = true
catBg.centerYAnchor.constraint(equalTo: self.view.centerYAnchor).isActive = true
catBg.widthAnchor.constraint(equalTo: self.view.widthAnchor, multiplier: 0.7).isActive = true
catBg.heightAnchor.constraint(equalTo: catBg.widthAnchor, multiplier: 1.78).isActive = true
self.catBg.addSubview(stackView)
stackView.centerXAnchor.constraint(equalTo: catBg.centerXAnchor).isActive = true
stackView.centerYAnchor.constraint(equalTo: catBg.centerYAnchor).isActive = true
for i in 1..<5 {
let subButton = UIImageView()
subButton.translatesAutoresizingMaskIntoConstraints = false
subButton.backgroundColor = UIColor.blue
subButton.alpha = 1.0
stackView.addArrangedSubview(subButton)
subButton.widthAnchor.constraint(equalTo: catBg.widthAnchor, multiplier: 0.3).isActive = true
//If you have an image you can remove or change this anchor
subButton.heightAnchor.constraint(equalToConstant: 54).isActive = true
}
}
UPDATE4
My ideal is the center of this image. But so far the result is like the right image.
Even if you are not using storyboards, I would suggest you using UIStackview. In the stackview you specific axis to be vertical and a spacing if you'd like. Set the stackview to fill equally.
Don't using actual frames to calculate position for multiple screen layouts, it will get tedious.
You should use autolayout constraints in particular anchors.
//Define this as class variable
fileprivate lazy var stackView: UIStackView = {
let sv = UIStackView()
sv.translatesAutoresizingMaskIntoConstraints = false
sv.axis = .vertical
sv.alignment = .fill
sv.distribution = .fillEqually
sv.spacing = 10
return sv
}()
fileprivate lazy var catBg: UIImageView = {
let iv = UIImageView()
iv.translatesAutoresizingMaskIntoConstraints = false
iv.contentMode = .scaleAspectFill
iv.image = UIImage(named: "your_img_name")
return iv
}()
//Put this in viewDidLoad
//Edit
self.view.addSubview(catBg)
//catBg.centerXAnchor.constraint(equalTo: self.view.centerXAnchor).isActive = true
//catBg.centerYAnchor.constraint(equalTo: self.view.centerYAnchor).isActive = true
//catBg.widthAnchor.constraint(equalTo: self.view.widthAnchor, multiplier: 0.7).isActive = true
//catBg.heightAnchor.constraint(equalTo: catBg.widthAnchor, multiplier: 1.78).isActive = true
catBg.topAnchor.constraint(equalTo: self.view.topAnchor).isActive = true
catBg.leftAnchor.constraint(equalTo: self.view.leftAnchor).isActive = true
catBg.rightAnchor.constraint(equalTo: self.view.rightAnchor).isActive = true
catBg.bottomAnchor.constraint(equalTo: self.view.bottomAnchor).isActive = true
self.catBg.addSubview(stackView)
stackView.centerXAnchor.constraint(equalTo: catBg.centerXAnchor).isActive = true
stackView.centerYAnchor.constraint(equalTo: catBg.centerYAnchor).isActive = true
for i in 1..<5 {
let subButton = UIImageView()
subButton.translatesAutoResizingMaskIntoConstraints = false
subButton.backgroundColor = UIColor.blue
subButton.alpha = 1.0
stackView.addArrangedSubview(subButton)
subButton.widthAnchor.constraint(equalTo: catBg.widthAnchor, multiplier: 0.3).isActive = true
//If you have an image you can remove or change this anchor
subButton.heightAnchor.constraint(equalToConstant: 54).isActive = true
}
Related
I'm trying to achieve a specific design inside a custom reusable view.
(Something like this: )
So I pass an URL to retrive the image and I pass a String to add below.
Firstly, I want the entire view to be the width of the elements (if the text is long then the entire view will be wider), I don't know how to do that, the view seems to be the entire width of the screen.
Secondly, I want the items to be centered horizontally and vertically, what I tried does not work.
Here is my current code :
func initLayout() {
stackView.axis = NSLayoutConstraint.Axis.vertical
stackView.distribution = UIStackView.Distribution.fillEqually
stackView.alignment = UIStackView.Alignment.center
stackView.spacing = 10.0
stackView.addArrangedSubview(imageView)
stackView.addArrangedSubview(textContainer)
stackView.translatesAutoresizingMaskIntoConstraints = false
self.addSubview(stackView)
self.snp.makeConstraints { (make) -> Void in
make.height.equalTo(70)
}
self.stackView.snp.makeConstraints{ (make) -> Void in
make.edges.equalTo(self)
}
}
And it results in something like this:
As you can (or cannot) see, the view is centered in the middle of the screen, which is not what I want. The view should be the width of the text and everything centered inside this particular width, then I add it inside my VC and place it so it's leading.
if I understand well the is your constraint without Snapkit:
Set your objects under your class controller declaration:
let myImageView: UIImageView = {
let iv = UIImageView()
iv.contentMode = .scaleToFill
iv.clipsToBounds = true
iv.backgroundColor = .red
iv.translatesAutoresizingMaskIntoConstraints = false
return iv
}()
let myLabel: UILabel = {
let label = UILabel()
label.text = "Débats"
label.textColor = .white
label.textAlignment = .center
label.font = .systemFont(ofSize: 30, weight: .semibold) // set your font size here
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
Now in viewDidLoad set parameters and constraints:
myImageView.image = UIImage(named: "profilo") // set here your image
let myWidth = myLabel.intrinsicContentSize.width // This reveal only text width in label
view.addSubview(myLabel)
myLabel.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.5).isActive = true
myLabel.heightAnchor.constraint(equalToConstant: 50).isActive = true
myLabel.centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: 25).isActive = true
myLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
view.addSubview(myImageView)
myImageView.widthAnchor.constraint(equalToConstant: myWidth).isActive = true
myImageView.heightAnchor.constraint(equalTo: myImageView.widthAnchor).isActive = true
myImageView.bottomAnchor.constraint(equalTo: myLabel.topAnchor).isActive = true
myImageView.centerXAnchor.constraint(equalTo: myLabel.centerXAnchor).isActive = true
This is the result:
Intere code:
class Aiutotipo: UIViewController {
let myImageView: UIImageView = {
let iv = UIImageView()
iv.contentMode = .scaleToFill
iv.clipsToBounds = true
iv.backgroundColor = .red
iv.translatesAutoresizingMaskIntoConstraints = false
return iv
}()
let myLabel: UILabel = {
let label = UILabel()
label.text = "Débats"
label.textColor = .white
label.textAlignment = .center
label.font = .systemFont(ofSize: 30, weight: .semibold)
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
override func viewDidLoad() {
super.viewDidLoad()
myImageView.image = UIImage(named: "profilo") // set here your image
let myWidth = myLabel.intrinsicContentSize.width // This reveal only text width in label
view.addSubview(myLabel)
myLabel.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.5).isActive = true
myLabel.heightAnchor.constraint(equalToConstant: 50).isActive = true
myLabel.centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: 25).isActive = true
myLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
view.addSubview(myImageView)
myImageView.widthAnchor.constraint(equalToConstant: myWidth).isActive = true
myImageView.heightAnchor.constraint(equalTo: myImageView.widthAnchor).isActive = true
myImageView.bottomAnchor.constraint(equalTo: myLabel.topAnchor).isActive = true
myImageView.centerXAnchor.constraint(equalTo: myLabel.centerXAnchor).isActive = true
}
}
Is it possible to display an image from the assets folder to a uistackview programmatically? If so, how would you go about doing it?
I already know how to create a stack view filled with labels.
fileprivate lazy var stack: UIStackView = {
let stack = UIStackView(arrangedSubviews: [goalCompleteLabel, completeMoreGoalsLabel])
stack.translatesAutoresizingMaskIntoConstraints = false
stack.axis = .vertical
return stack
} ()
First set your image in imageView (set in it its constraints for more control of image dimension) and your label under your class controller:
let image: UIImageView = {
let imageView = UIImageView()
imageView.image = UIImage(named: "yourImage")?.withRenderingMode(.alwaysOriginal)
imageView.backgroundColor = .gray
imageView.layer.cornerRadius = 8
imageView.clipsToBounds = true
imageView.contentMode = .scaleAspectFill
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.heightAnchor.constraint(equalToConstant: 200).isActive = true
imageView.widthAnchor.constraint(equalToConstant: 200).isActive = true
return imageView
}()
let completeMoreGoalsLabel: UILabel = {
let label = UILabel()
label.text = "Dummytext"
label.textAlignment = .center
return label
}()
now set your stack view with distribution fillProportionally:
lazy var stack: UIStackView = {
let stackView = UIStackView(arrangedSubviews: [image, completeMoreGoalsLabel])
stackView.axis = .vertical
stackView.distribution = .fillProportionally
stackView.translatesAutoresizingMaskIntoConstraints = false
return stackView
}()
in viewDidLoad present your stack and add constraints:
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .darkGray
view.addSubview(stack)
stack.widthAnchor.constraint(equalToConstant: 200).isActive = true
stack.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
stack.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
stack.heightAnchor.constraint(equalToConstant: 250).isActive = true //200 imageHeight + 50 label height
}
I add corner radius on image to make it more cute...
Yes. You simply need to add an ImageView as an arranged subview of your stackview. Just like the labels. Here's the code -
class StackViewController: UIViewController {
var stackView = UIStackView()
var label = UILabel()
var imageView = UIImageView()
override func viewDidLoad() {
super.viewDidLoad()
configStackView()
}
func configStackView() -> Void {
// Add StackView as SubView
view.addSubview(stackView)
// Set StackView properties
stackView.axis = .vertical
stackView.alignment = .center
stackView.distribution = .equalSpacing
// Set imageView as 1st arranged subview of stackview
stackView.addArrangedSubview(imageView)
configImageView()
// Set Label as 2nd arranged subview of stackview
stackView.addArrangedSubview(label)
configLabel()
// Set StackView Constraints
setStackViewCostraints()
}
func setStackViewCostraints() -> Void {
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20).isActive = true
stackView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20).isActive = true
stackView.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
stackView.heightAnchor.constraint(equalToConstant: 200).isActive = true
}
func configImageView() -> Void {
imageView.image = UIImage(named: "bolt")
imageView.contentMode = .scaleAspectFit
// Set Constraints (ideally in a separate function)
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.widthAnchor.constraint(equalToConstant: 100).isActive = true
}
func configLabel() -> Void {
label.text = "Label"
}
}
Here's the how it renders -
Trying to create a UIScrollView and I cant seem to get my labels to appear as I would like them dead center.
lazy var label : UILabel = {
let label = UILabel()
label.text = "center of container view.center of container view"
label.font = UIFont(name: "Bebas Neue", size: 23)!
label.textColor = .black
return label
}()
// Mark: Properties
lazy var contentViewSize = CGSize(width: self.view.frame.width + 1200, height: self.view.frame.height)
// Mark: Views
fileprivate lazy var parentScrollView : UIView = {
var newView = UIView()
newView.translatesAutoresizingMaskIntoConstraints = false
newView.backgroundColor = .black
newView.frame = self.scrollView.bounds
newView.frame.size = contentViewSize
return newView
}()
fileprivate lazy var scrollView : UIScrollView = {
var uiScrollView = UIScrollView(frame: .zero)
uiScrollView.translatesAutoresizingMaskIntoConstraints = false
uiScrollView.frame = view.bounds
uiScrollView.backgroundColor = .gray
uiScrollView.addViewBorder(borderColor: UIColor.gray.cgColor, borderWith: 10, borderCornerRadius: 0)
// uiScrollView.alpha =
uiScrollView.contentSize = contentViewSize
// uiScrollView.autoresizingMask = .flexibleHeight
uiScrollView.showsVerticalScrollIndicator = true
uiScrollView.bounces = true
return uiScrollView
}()
fileprivate lazy var tutorialView : UIStackView = {
var tutView = UIStackView(arrangedSubviews: [label,label,label])
tutView.axis = .horizontal
tutView.backgroundColor = .white
tutView.distribution = .fillEqually
tutView.spacing = 0
return tutView
}()
override func viewDidLoad() {
view.backgroundColor = .white
view.addSubview(scrollView)
scrollView.addSubview(parentScrollView)
parentScrollView.addSubview(tutorialView)
scrollView.widthAnchor.constraint(equalTo:self.view.widthAnchor,multiplier: 0.9).isActive = true
scrollView.heightAnchor.constraint(equalTo:self.view.heightAnchor,multiplier: 0.7).isActive = true
scrollView.centerXAnchor.constraint(equalTo: self.view.centerXAnchor).isActive = true
scrollView.topAnchor.constraint(equalTo: self.view.topAnchor, constant: 75).isActive = true
tutorialView.topAnchor.constraint(equalTo: parentScrollView.topAnchor,constant: +10).isActive = true}
It seems that when I add that last constraight with the topAnchor I get a blank screen. When I take that off I actually get the borders between the two views and I am able to do some scrolling.
Any help would be appreciated. Thank you
Did you give a value to the scrollView?
uiScrollView.contentSize.width = 1200
Then give the tutorialView the same width constraint with the other constraints.
NSLayoutConstraint.activate([
tutorialView.widthAnchor.constraint(equalToConstant: 1200),
tutorialView.heightAnchor.constraint(equalTo: uiScrollView.heightAnchor)
])
You have to add tutView.translatesAutoresizingMaskIntoConstraints = false when giving layout constraints in closure of tutorialView and also there are some mistakes in your code. So included some changes in your code and check updated code:
lazy var label : UILabel = {
let label = UILabel()
label.text = "center of container view.center of container view"
label.font = UIFont(name: "Bebas Neue", size: 23)!
label.textColor = .green
return label
}()
// Mark: Properties
lazy var contentViewSize = CGSize(width: self.view.frame.width + 1200, height: self.view.frame.height)
// Mark: Views
fileprivate lazy var parentScrollView : UIView = {
var newView = UIView()
newView.translatesAutoresizingMaskIntoConstraints = false
newView.backgroundColor = UIColor.clear
// newView.layer.borderWidth = 2
// newView.layer.borderColor = UIColor.yellow.cgColor
return newView
}()
fileprivate lazy var scrollView : UIScrollView = {
var uiScrollView = UIScrollView()
uiScrollView.translatesAutoresizingMaskIntoConstraints = false
// uiScrollView.frame = view.bounds
uiScrollView.backgroundColor = .orange
uiScrollView.layer.borderColor = UIColor.blue.cgColor
uiScrollView.layer.borderWidth = 2
uiScrollView.layer.cornerRadius = 0
// uiScrollView.alpha =
uiScrollView.contentSize = contentViewSize
// uiScrollView.autoresizingMask = .flexibleHeight
uiScrollView.showsVerticalScrollIndicator = true
uiScrollView.bounces = true
return uiScrollView
}()
fileprivate lazy var tutorialView : UIStackView = {
var tutView = UIStackView(arrangedSubviews: [label,label,label])
tutView.axis = .horizontal
tutView.backgroundColor = .red
tutView.distribution = .fill
tutView.translatesAutoresizingMaskIntoConstraints = false
return tutView
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
view.addSubview(scrollView)
scrollView.addSubview(parentScrollView)
parentScrollView.addSubview(tutorialView)
scrollView.widthAnchor.constraint(equalTo:self.view.widthAnchor,multiplier: 0.9).isActive = true
scrollView.heightAnchor.constraint(equalTo:self.view.heightAnchor,multiplier: 0.7).isActive = true
scrollView.centerXAnchor.constraint(equalTo: self.view.centerXAnchor).isActive = true
scrollView.topAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.topAnchor, constant: 25).isActive = true
parentScrollView.centerXAnchor.constraint(equalTo: scrollView.centerXAnchor).isActive = true
parentScrollView.topAnchor.constraint(equalTo: scrollView.topAnchor, constant: 10).isActive = true
parentScrollView.heightAnchor.constraint(equalToConstant: 200).isActive = true
parentScrollView.widthAnchor.constraint(equalToConstant: 200).isActive = true
// scrollView.layer.borderWidth = 2
// scrollView.layer.borderColor = UIColor.blue.cgColor
tutorialView.heightAnchor.constraint(equalTo: parentScrollView.heightAnchor).isActive = true
tutorialView.widthAnchor.constraint(equalToConstant: 1200).isActive = true
}
I have created UIStackView programmatically and added 2 views that have 2 child views each. Here are my sample codes:
let sv = UIStackView()
sv.axis = .horizontal
sv.alignment = .center
sv.spacing = Config.Dimensions.horizontalSpacing
sv.distribution = .equalCentering
sv.translatesAutoresizingMaskIntoConstraints = false
let viewCountStudent = UIView()
viewCountStudent.addSubview(studentCount)
viewCountStudent.addSubview(labelStudent)
studentCount.topAnchor.constraint(equalTo: viewCountStudent.topAnchor).isActive = true
studentCount.leftAnchor.constraint(equalTo: viewCountStudent.leftAnchor).isActive = true
studentCount.bottomAnchor.constraint(equalTo: viewCountStudent.bottomAnchor).isActive = true
labelStudent.topAnchor.constraint(equalTo: viewCountStudent.topAnchor).isActive = true
labelStudent.leftAnchor.constraint(equalTo: studentCount.rightAnchor, constant: 8.0).isActive = true
labelStudent.rightAnchor.constraint(equalTo: viewCountStudent.rightAnchor).isActive = true
labelStudent.bottomAnchor.constraint(equalTo: viewCountStudent.bottomAnchor).isActive = true
let viewCountLesson = UIView()
viewCountLesson.addSubview(lessonCount)
viewCountLesson.addSubview(labelLesson)
lessonCount.leftAnchor.constraint(equalTo: viewCountLesson.leftAnchor).isActive = true
lessonCount.topAnchor.constraint(equalTo: viewCountLesson.topAnchor).isActive = true
lessonCount.bottomAnchor.constraint(equalTo: viewCountLesson.bottomAnchor).isActive = true
labelLesson.leftAnchor.constraint(equalTo: lessonCount.rightAnchor, constant: 8.0).isActive = true
labelLesson.rightAnchor.constraint(equalTo: viewCountLesson.rightAnchor).isActive = true
labelLesson.topAnchor.constraint(equalTo: viewCountLesson.topAnchor).isActive = true
labelLesson.bottomAnchor.constraint(equalTo: viewCountLesson.bottomAnchor).isActive = true
sv.addArrangedSubview(viewCountLesson)
sv.addArrangedSubview(viewCountStudent)
sv.topAnchor.constraint(equalTo: divider.bottomAnchor, constant: 8.0).isActive = true
sv.leftAnchor.constraint(equalTo: divider.leftAnchor, constant: 16.0).isActive = true
sv.rightAnchor.constraint(equalTo: divider.rightAnchor, constant: -16.0).isActive = true
sv.bottomAnchor.constraint(equalTo: guide.bottomAnchor, constant: -8.0).isActive = true
addSubview(sv)
The layout it gives is like this:
Above is the horizontal bar and below is the StackView. I wonder why the gap in between 2 views are not equally distributing by stack view. I am trying to center them with spacing distributing equally. Any idea?
This may help you understand...
Each "row" of three green labels is a Horizontal Stack View with Spacing: 8 and Distribution set to:
Fill
Fill Equally
Fill Proportionally
Equal Centering
Equal Spacing
As you can see, with Distribution: Equal Centering, the stack view arranges its subviews so their centers are equally spaced.
What you probably want is equal spacing on the sides and in-between:
To get that layout, use Distribution: Fill and add an empty "spacer" view in the stack, so you have:
spacer1 - viewCountLesson - spacer2 - viewCountStudent - spacer3
then set spacer2 width equal to spacer1 and spacer3 width equal to spacer1.
Here is the code used to create that:
class NewStackViewController: UIViewController {
let studentCount: UILabel = {
let v = UILabel()
v.translatesAutoresizingMaskIntoConstraints = false
v.backgroundColor = .yellow
v.text = "2"
return v
}()
let lessonCount: UILabel = {
let v = UILabel()
v.translatesAutoresizingMaskIntoConstraints = false
v.backgroundColor = .yellow
v.text = "1"
return v
}()
let labelStudent: UILabel = {
let v = UILabel()
v.translatesAutoresizingMaskIntoConstraints = false
v.backgroundColor = .cyan
v.text = "Students"
return v
}()
let labelLesson: UILabel = {
let v = UILabel()
v.translatesAutoresizingMaskIntoConstraints = false
v.backgroundColor = .cyan
v.text = "Lesson"
return v
}()
let divider: UIView = {
let v = UIView()
v.translatesAutoresizingMaskIntoConstraints = false
v.backgroundColor = .gray
return v
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(divider)
NSLayoutConstraint.activate([
divider.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 20.0),
divider.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -20.0),
divider.heightAnchor.constraint(equalToConstant: 2.0),
divider.centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: 0.0),
])
let sv = UIStackView()
sv.axis = .horizontal
sv.alignment = .fill
sv.spacing = 0 //Config.Dimensions.horizontalSpacing
sv.distribution = .fill
sv.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(sv)
NSLayoutConstraint.activate([
sv.leadingAnchor.constraint(equalTo: divider.leadingAnchor, constant: 16.0),
sv.trailingAnchor.constraint(equalTo: divider.trailingAnchor, constant: -16.0),
sv.topAnchor.constraint(equalTo: divider.bottomAnchor, constant: 8.0),
])
let viewCountStudent = UIView()
viewCountStudent.addSubview(studentCount)
viewCountStudent.addSubview(labelStudent)
studentCount.topAnchor.constraint(equalTo: viewCountStudent.topAnchor).isActive = true
studentCount.leftAnchor.constraint(equalTo: viewCountStudent.leftAnchor).isActive = true
studentCount.bottomAnchor.constraint(equalTo: viewCountStudent.bottomAnchor).isActive = true
labelStudent.topAnchor.constraint(equalTo: viewCountStudent.topAnchor).isActive = true
labelStudent.leftAnchor.constraint(equalTo: studentCount.rightAnchor, constant: 8.0).isActive = true
labelStudent.rightAnchor.constraint(equalTo: viewCountStudent.rightAnchor).isActive = true
labelStudent.bottomAnchor.constraint(equalTo: viewCountStudent.bottomAnchor).isActive = true
let viewCountLesson = UIView()
viewCountLesson.addSubview(lessonCount)
viewCountLesson.addSubview(labelLesson)
lessonCount.leftAnchor.constraint(equalTo: viewCountLesson.leftAnchor).isActive = true
lessonCount.topAnchor.constraint(equalTo: viewCountLesson.topAnchor).isActive = true
lessonCount.bottomAnchor.constraint(equalTo: viewCountLesson.bottomAnchor).isActive = true
labelLesson.leftAnchor.constraint(equalTo: lessonCount.rightAnchor, constant: 8.0).isActive = true
labelLesson.rightAnchor.constraint(equalTo: viewCountLesson.rightAnchor).isActive = true
labelLesson.topAnchor.constraint(equalTo: viewCountLesson.topAnchor).isActive = true
labelLesson.bottomAnchor.constraint(equalTo: viewCountLesson.bottomAnchor).isActive = true
let sp1 = spacerView()
let sp2 = spacerView()
let sp3 = spacerView()
sv.addArrangedSubview(sp1)
sv.addArrangedSubview(viewCountLesson)
sv.addArrangedSubview(sp2)
sv.addArrangedSubview(viewCountStudent)
sv.addArrangedSubview(sp3)
NSLayoutConstraint.activate([
sp2.widthAnchor.constraint(equalTo: sp1.widthAnchor, multiplier: 1.0),
sp3.widthAnchor.constraint(equalTo: sp1.widthAnchor, multiplier: 1.0),
])
[sp1, sp2, sp3, viewCountLesson, viewCountStudent, studentCount, labelStudent, lessonCount, labelLesson].forEach {
// set borderWidth to 1 to add borders to labels and views so we can see them
$0.layer.borderWidth = 0
$0.layer.borderColor = UIColor.lightGray.cgColor
// un-comment next line to set backgrounds to clear
//$0.backgroundColor = .clear
}
}
func spacerView() -> UIView {
let v = UIView()
v.translatesAutoresizingMaskIntoConstraints = false
v.backgroundColor = .green
return v
}
}
I'm following a tutorial to create an interactive popup animation (http://www.swiftkickmobile.com/building-better-app-animations-swift-uiviewpropertyanimator/), and now would like to add buttons to the popup rather than have static text as in the tutorial.
The animation works fine, but the buttons are not detecting touch. Touching the button causes the animation to reverse, instead of printing my "test" statement.
From research, it looks to either be an issue with view hierarchies, the animation disabling the button touch, or layout/constraint issues with the button. I've tried addressing the above issues, but nothing has worked, was hoping someone might be able to help?
I've left out the code pertaining to the animation since I think the issue is to do with layout (and the animation seems to be working fine) - but it's still a lot; apologies in advance for the large amount of code.
class ViewController: UIViewController {
private let popupOffset: CGFloat = 440
private lazy var contentImageView: UIImageView = {
let imageView = UIImageView()
imageView.image = #imageLiteral(resourceName: "Background")
return imageView
}()
private lazy var overlayView: UIView = {
let view = UIView()
view.backgroundColor = .black
view.alpha = 0
return view
}()
private lazy var popupView: UIView = {
let view = UIView()
view.backgroundColor = .white
view.layer.maskedCorners = [.layerMaxXMinYCorner, .layerMinXMinYCorner]
view.layer.shadowColor = UIColor.black.cgColor
view.layer.shadowOpacity = 0.1
view.layer.shadowRadius = 10
return view
}()
private lazy var closedTitleLabel: UILabel = {
let label = UILabel()
label.text = "Hello"
label.font = UIFont.systemFont(ofSize: 16, weight: UIFont.Weight.medium)
label.textColor = UIColor.darkGray
label.textAlignment = .center
return label
}()
private lazy var openTitleLabel: UILabel = {
let label = UILabel()
label.text = "Which door will you choose?"
label.font = UIFont.systemFont(ofSize: 16, weight: UIFont.Weight.medium)
label.textColor = UIColor.darkGray
label.textAlignment = .center
label.alpha = 0
label.transform = CGAffineTransform(scaleX: 1.6, y: 1.6).concatenating(CGAffineTransform(translationX: 0, y: 15))
return label
}()
private lazy var reviewsImageView: UIImageView = {
let imageView = UIImageView()
imageView.image = #imageLiteral(resourceName: "LabelBackground")
return imageView
}()
let stackView = UIStackView()
let buttonA = UIButton()
let buttonB = UIButton()
let buttonC = UIButton()
override func viewDidLoad() {
super.viewDidLoad()
layout()
popupView.addGestureRecognizer(panRecognizer)
}
override var prefersStatusBarHidden: Bool {
return true
}
//Layout
private var bottomConstraint = NSLayoutConstraint()
private func layout() {
contentImageView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(contentImageView)
contentImageView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
contentImageView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
contentImageView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
contentImageView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
overlayView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(overlayView)
overlayView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
overlayView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
overlayView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
overlayView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
popupView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(popupView)
popupView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
popupView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
bottomConstraint = popupView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: popupOffset)
bottomConstraint.isActive = true
popupView.heightAnchor.constraint(equalToConstant: 500).isActive = true
closedTitleLabel.translatesAutoresizingMaskIntoConstraints = false
popupView.addSubview(closedTitleLabel)
closedTitleLabel.leadingAnchor.constraint(equalTo: popupView.leadingAnchor).isActive = true
closedTitleLabel.trailingAnchor.constraint(equalTo: popupView.trailingAnchor).isActive = true
closedTitleLabel.topAnchor.constraint(equalTo: popupView.topAnchor, constant: 20).isActive = true
openTitleLabel.translatesAutoresizingMaskIntoConstraints = false
popupView.addSubview(openTitleLabel)
openTitleLabel.leadingAnchor.constraint(equalTo: popupView.leadingAnchor).isActive = true
openTitleLabel.trailingAnchor.constraint(equalTo: popupView.trailingAnchor).isActive = true
openTitleLabel.topAnchor.constraint(equalTo: popupView.topAnchor, constant: 20).isActive = true
reviewsImageView.translatesAutoresizingMaskIntoConstraints = false
popupView.addSubview(reviewsImageView)
reviewsImageView.leadingAnchor.constraint(equalTo: popupView.leadingAnchor).isActive = true
reviewsImageView.trailingAnchor.constraint(equalTo: popupView.trailingAnchor).isActive = true
reviewsImageView.bottomAnchor.constraint(equalTo: popupView.bottomAnchor).isActive = true
reviewsImageView.heightAnchor.constraint(equalToConstant: 428).isActive = true
buttonA.backgroundColor = UIColor.clear
let heightConstraintA = buttonA.heightAnchor.constraint(equalToConstant: 135)
heightConstraintA.isActive = true
heightConstraintA.priority = UILayoutPriority(rawValue: 999)
buttonA.translatesAutoresizingMaskIntoConstraints = false
buttonA.setTitle("A", for: .normal)
buttonA.setTitleColor(UIColor.darkGray, for: .normal)
buttonA.backgroundColor = UIColor.clear
buttonA.addTarget(self, action: #selector(buttonATapped(sender:)), for: .touchDown)
//self.popupView.addSubview(buttonA)
buttonB.backgroundColor = UIColor.clear
let heightConstraintB = buttonB.heightAnchor.constraint(equalToConstant: 135)
heightConstraintB.isActive = true
heightConstraintB.priority = UILayoutPriority(rawValue: 999)
buttonB.translatesAutoresizingMaskIntoConstraints = false
buttonB.setTitle("B", for: .normal)
buttonB.setTitleColor(UIColor.darkGray, for: .normal)
buttonB.backgroundColor = UIColor.clear
//self.popupView.addSubview(buttonB)
buttonC.backgroundColor = UIColor.clear
let heightConstraintC = buttonC.heightAnchor.constraint(equalToConstant: 135)
heightConstraintC.isActive = true
heightConstraintC.priority = UILayoutPriority(rawValue: 999)
buttonC.translatesAutoresizingMaskIntoConstraints = false
buttonC.setTitle("C", for: .normal)
buttonC.setTitleColor(UIColor.darkGray, for: .normal)
buttonC.backgroundColor = UIColor.clear
//self.popupView.addSubview(buttonC)
popupView.addSubview(stackView)
stackView.backgroundColor = UIColor.clear
stackView.addArrangedSubview(buttonA)
stackView.addArrangedSubview(buttonB)
stackView.addArrangedSubview(buttonC)
stackView.translatesAutoresizingMaskIntoConstraints = false
popupView.addSubview(stackView)
stackView.leadingAnchor.constraint(equalTo: popupView.leadingAnchor).isActive = true
stackView.trailingAnchor.constraint(equalTo: popupView.trailingAnchor).isActive = true
stackView.bottomAnchor.constraint(equalTo: popupView.bottomAnchor).isActive = true
stackView.heightAnchor.constraint(equalToConstant: 428).isActive = true
stackView.axis = .vertical
stackView.distribution = .fill
stackView.translatesAutoresizingMaskIntoConstraints = false
}
#objc func buttonATapped(sender: UIButton) {
print ("test")
}
private func animateTransitionIfNeeded(to state: State, duration: TimeInterval) {
//Animation code
}
#objc private func popupViewPanned(recognizer: UIPanGestureRecognizer) {
//Animation code
}
}
***For anyone else having the same issue, here is how I solved it, thanks to #OverD:
I removed the reviewsImageView completely (because that was only for color in my case, and I can easily add the color to the UIButton instead) Then instead of adding the buttons to the popupView, I added them to the stack view, and the stack view to the popupView. Lastly, my syntax for addTarget was not correct, and for some reason changing it to touchDown instead of touchUpInside made it work.
I noticed from the provided code that you are adding the same buttons multiple times, once as a subview of the popView and once in the stackView. Also you are not assigning any targets for the buttons.
I hope this helps