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