是否有与 SwiftUI 的 zstack 等效的 UIKit?

is there a UIKit equivalent to SwiftUI's zstack?

我正在尝试创建类似 this 的内容。我最近一直在使用 SwiftUI,所以我知道我可以通过向 zstack 添加图像、文本和按钮(我很灵活的文本是 button/NavigationLink 的标签)来创建它。但我环顾四周,想看看 UIKit 是否有办法做到这一点。最好不使用故事板。我对 cocoapods 库或任何需要的东西持开放态度。我环顾四周,探索使用 SwiftUI 创建所需的 ZStack,然后在我的 UIKit 中使用它和 UIHostingController,但因为它涉及 button/navigationlink。看到 NavigationLink 如何要求目的地符合视图,我想在将我的更多项目转换为 swiftui 之前四处询问。我更希望这个项目能给我更多在没有故事板的情况下在 UIKit 中构建视图的经验,所以我更愿意这样做而不是使用 SwiftUI。如果可能的话我想。

我尝试四处搜索,但我所有 google 涉及 UIButton 和图像的搜索都只是 link 到关于在 UIButton 中设置图像的帖子。

由于您想获得更多使用 UIKit 创建视图的经验,我创建了一个继承自 UIView 的视图,您可以重复使用。在 UIKit 中有很多代码可以得到相同的结果。下面提供了代码和输出。

注意:阅读提供的评论

代码

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
    }

}

输出

重要提示

  1. 您可以使用约束或框架创建布局。如果您使用约束,重要 将视图 .translatesAutoresizingMaskIntoConstraints 设置为 false(您可以阅读它的文档)。

  2. NSLayoutConstraint.activate([...]) 用于一次应用一组约束。或者,您可以使用:

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

对于个别约束

  1. 视图的手动布局有时需要填充。因此,为此您必须根据您所在视图的边缘(侧面)为填充使用负值或正值。很容易记住将填充值设置为视图中心的方向。

E.x., 从 leading/left 边开始,您需要向视图中心添加 10 的填充或从 right/trailing 侧向中心添加 -10 .