zamanada
zamanada

Reputation: 158

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.

Upvotes: 0

Views: 2400

Answers (1)

Visal Rajapakse
Visal Rajapakse

Reputation: 2042

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

enter image description here

Important pointers

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

  2. NSLayoutConstraint.activate([...]) Is used to apply an array of constraints at once. Alternatively, you can use:

cardImage.leadingAnchor.constraint(...)isActivated = true

for individual constraints

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

Upvotes: 1

Related Questions