Buttons not working when running app on Xcode 12 [duplicate] - ios

This question already has answers here:
Since Updating to xcode 12 I am not able to place any UIControl inside UITableViewCell
(3 answers)
Closed 2 years ago.
I'm using Version 12.0.1 (12A7300). I have some buttons on UIViews and others in UITableViewCells. The actions for the buttons inside table cells are in the parent view controller which I access using protocols. When running the app either on simulators or on my device. All the buttons are added programmatically. The buttons do not work. I reinstalled Xcode Version 11.7 (11E801a) and everything works.
Any way to get around this issue? Is this a bug or did something change and I'm not aware of?
Here is an example of my code. In the UITableViewCell:
var addToCartDelegate: AddToCartDelegate?
let addToCartButton: UIButton = {
let button = UIButton()
return button
}()
let applePayButton: PKPaymentButton = {
let button = PKPaymentButton(paymentButtonType: .buy, paymentButtonStyle: .whiteOutline)
return button
}()
func configureAddToCartButton() {
let button = addToCartButton
button.setTitle("Add To Cart", for: .normal)
button.titleLabel?.font = getScaledFont(forFont: Fonts.bold, textStyle: .body)
button.setTitleColor(Colors.primaryButtonTintColor, for: .normal)
button.tintColor = Colors.primaryButtonTintColor
button.backgroundColor = Colors.primaryButtonBackgroundColor
button.layer.cornerRadius = Attributes.cornerRadius
button.addTarget(self, action: #selector(addToCartButtonWasTapped), for: .touchUpInside)
}
func configureStackView() {
var stackView = UIStackView()
if useApplePay {
stackView = UIStackView(arrangedSubviews: [addToCartButton, applePayButton])
} else {
stackView = UIStackView(arrangedSubviews: [addToCartButton])
}
stackView.axis = .horizontal
stackView.spacing = 16
stackView.distribution = .fillEqually
addSubview(stackView)
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true
stackView.heightAnchor.constraint(equalToConstant: 44).isActive = true
stackView.rightAnchor.constraint(equalTo: rightAnchor, constant: -16).isActive = true
stackView.leftAnchor.constraint(equalTo: leftAnchor, constant: 16).isActive = true
}
#objc func addToCartButtonWasTapped() {
addToCartDelegate?.addItemToCart()
}
#objc func applePayButtonWasTapped() {
addToCartDelegate?.buyWithApplePay()
}

As bluntly as possible you should not add content to a cell's view directly. Instead, content should be added to the cell's contentView by using contentView.addSubview(). In iOS 14, a cell's contentView appears above content added directly to the cell. This will in effect prohibit a user from interacting with those items, as the contentView will absorb all interaction. See the below images:
Pre iOS 14, notice the contentView behind all views added to the cell
iOS 14 and above, notice the contentView is in front of all views added directly to the cell

Do the buttons visible?
Make sure you used configureEditProfileButton() function and added "editProfileButton" to main view.

Related

is there a UIKit equivalent to SwiftUI's zstack?

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.

Constraints Issues On UI button

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.)

bounds of a UIView changes after viewDidLoad

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

Adding buttons programmatically to stackView in Swift

I've tried to add buttons dynamically/programmatically in UIStackView that I've built with interface builder but they failed to show up when I run the application. The number of buttons that's supposed to be added ranging normally from 4-6. Can you guys tell me what's wrong with the code
#Nowonder I just recreate what you are trying to achieve. The following are the steps.
Add a UIStackView in viewController from Interface Builder and add required constraints.
Add the following code.
override func viewDidLoad() {
super.viewDidLoad()
let button = UIButton()
button.setTitle("btn 1", for: .normal)
button.backgroundColor = UIColor.red
button.translatesAutoresizingMaskIntoConstraints = false
let button2 = UIButton()
button2.setTitle("btn 2", for: .normal)
button2.backgroundColor = UIColor.gray
button2.translatesAutoresizingMaskIntoConstraints = false
let button3 = UIButton()
button3.setTitle("btn 3", for: .normal)
button3.backgroundColor = UIColor.brown
button3.translatesAutoresizingMaskIntoConstraints = false
buttonStackView.alignment = .fill
buttonStackView.distribution = .fillEqually
buttonStackView.spacing = 8.0
buttonStackView.addArrangedSubview(button)
buttonStackView.addArrangedSubview(button2)
buttonStackView.addArrangedSubview(button3)
}
Following is the outcome.
Hope it helps.
I think you need to do few more steps before the button will start showing up.
Add the stack view to current view's subviews
view.addSubview(buttonStackView)
Now each of these views buttons, as well as the stackView, needs to set the translatesAutoresizingMaskIntoConstraints to false.
button1.translatesAutoresizingMaskIntoConstraints = false
button2.translatesAutoresizingMaskIntoConstraints = false
buttonStackView.translatesAutoresizingMaskIntoConstraints = false
Now set the stackView contraints
NSLayoutConstraint.activate([
buttonStackView.topAnchor.constraint(equalTo: view.topAnchor),
buttonStackView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
buttonStackView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
buttonStackView.trailingAnchor.constraint(equalTo: view.trailingAnchor)])
You will need to provide the constraints, or can override the intrinsic size of stackView.
override func intrinsicContentSize() -> CGSize
{
return CGSizeMake(200, 40)
}
As UILabel have the intrinsic size and so, the button will show, if not you will have to set the constraints for them also to be safe.
SOLVED!
The button showed up when I set the type of the button with the instance method init(type:)

Swift 3 - Programmatically build a button with an image and label

I am currently trying to build an interface (programmatically) with 5 x buttons, containing an image and a label.
I have done this successfully for ONE button using a UIStackView (holding the UIButton and a UIlabel).
I have two questions for this forum…
A UIButton can be built to display a title OR an image, can it have both?
Can a ‘for in’ loop be used to generate 5 x individual buttons? i.e: a way to re-use code instead of typing out code for 5 x buttons, 5 x labels, 5 x stack views.
My working UIStackView button code is as follows:
// Button
let btnSettings = UIButton()
// btnSettings.setTitle("Settings", for: .normal)
btnSettings.setImage(#imageLiteral(resourceName: "star-in-circle"), for: .normal)
btnSettings.heightAnchor.constraint(equalToConstant: 100.0).isActive = true
btnSettings.widthAnchor.constraint(equalToConstant: 100.0).isActive = true
btnSettings.contentMode = .scaleAspectFit
btnSettings.addTarget(self, action: #selector(openSettings), for: .touchUpInside)
btnSettings.translatesAutoresizingMaskIntoConstraints = false
// Text Label
let textLabel = UILabel()
textLabel.backgroundColor = UIColor.clear
textLabel.widthAnchor.constraint(equalToConstant: 100.0).isActive = true
textLabel.heightAnchor.constraint(equalToConstant: 20.0).isActive = true
textLabel.font = UIFont.boldSystemFont(ofSize: 18)
textLabel.text = "Settings"
textLabel.textAlignment = .center
// Stack View
let stackView = UIStackView()
stackView.axis = UILayoutConstraintAxis.vertical
stackView.distribution = UIStackViewDistribution.equalSpacing
stackView.alignment = UIStackViewAlignment.center
stackView.spacing = 1.0
stackView.addArrangedSubview(btnSettings)
stackView.addArrangedSubview(textLabel)
stackView.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(stackView)
// Constraints
stackView.centerXAnchor.constraint(equalTo: self.view.centerXAnchor).isActive = true
stackView.centerYAnchor.constraint(equalTo: self.view.centerYAnchor).isActive = true
Update / Solved Question 1
I needed to use the button.setBackgroundImage() in order for the button title to show up with the image.
btnSettings.setBackgroundImage(#imageLiteral(resourceName: "star-in-circle"), for: .normal)
btnSettings.setTitle("Button Title", for: .normal)
btnSettings.backgroundColor = UIColor.white
Of course, UIButton can have both an image or/and text. You can use:
button.setImage(image: UIImage?, for: UIControlState)
button.setTitle(title: String?, for: UIControlState)
Create a function that will return UIButton and do something like that:
let button = generateButton()
stackView.addArrangedSubview(button)

Resources