每次调用时将 2 个图像视图添加到 uiscrollview func

add 2 image views to a uiscrollview func every time it is called

我的 swift 下面的代码目标是每次添加 2 个图像视图。屁股你可以在下面的 gif 中只添加一个图像视图。我只需要添加 2 个图像视图。图像视图是 lastImage 和 lastImage2。你可以看到只显示了 lastImage。似乎我只能在调用 func didclickadd 时添加 1 个图像视图。

import UIKit

class ViewController: UIViewController {

    fileprivate var  lastImage:UIImageView?
    fileprivate var  lastImage2:UIImageView?

    fileprivate var mainViewBootom:NSLayoutConstraint?


    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .white
        setupVIew()
    }

    override func viewDidAppear(_ animated: Bool) {
        scrollView.contentSize = CGSize(width: view.frame.width, height: mainView.frame.height)
        view.layoutIfNeeded()
    }

    //MARK: Components
    let scrollView:UIScrollView = {
        let sv = UIScrollView(frame: .zero)
        return sv
    }()

    let mainView:UIView = {
        let uv = UIView()
        uv.backgroundColor = .white
        return uv
    }()

    let btnAdd:UIButton = {
        let btn = UIButton(type: .system)
        btn.setTitle("Add", for: .normal)
        return btn
    }()



    let textField:UITextField = {
        let jake = UITextField()
        return jake

    }()

    //MARK: Setup UI
    func setupVIew() {
        view.addSubview(scrollView)
        view.addSubview(btnAdd)
        view.addSubview(textField)
        scrollView.translatesAutoresizingMaskIntoConstraints = false
        btnAdd.translatesAutoresizingMaskIntoConstraints = false
        textField.translatesAutoresizingMaskIntoConstraints = false

        NSLayoutConstraint.activate([




            btnAdd.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            btnAdd.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -12),
            btnAdd.widthAnchor.constraint(equalToConstant: 100),
            btnAdd.heightAnchor.constraint(equalToConstant: 45),


            //
            textField.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            textField.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: 25),
            textField.widthAnchor.constraint(equalToConstant: 100),
            textField.heightAnchor.constraint(equalToConstant: 45),
            //



            scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
            scrollView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
            scrollView.bottomAnchor.constraint(equalTo: btnAdd.topAnchor , constant: -12),
        ])
        btnAdd.addTarget(self, action: #selector(didClickedAdd), for: .touchUpInside)

        scrollView.addSubview(mainView)
        mainView.translatesAutoresizingMaskIntoConstraints = false

        NSLayoutConstraint.activate([
            mainView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            mainView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
            mainView.topAnchor.constraint(equalTo: scrollView.topAnchor),
        ])

        let imgView = UIImageView(frame: CGRect(x: 0, y: 0, width: 150, height: 100))
        imgView.backgroundColor  = .red
        mainView.addSubview(imgView)


        let samsam = UIImageView(frame: CGRect(x: 0, y: 200, width: 40, height: 100))
        samsam.backgroundColor  = .blue
        mainView.addSubview(samsam)




        imgView.translatesAutoresizingMaskIntoConstraints = false
        imgView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
        imgView.widthAnchor.constraint(equalToConstant: 150).isActive = true
        imgView.heightAnchor.constraint(equalToConstant: 100).isActive = true


        samsam.translatesAutoresizingMaskIntoConstraints = false
        samsam.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
        samsam.topAnchor.constraint(equalTo: imgView.bottomAnchor).isActive = true
        samsam.widthAnchor.constraint(equalToConstant: 75).isActive = true
        samsam.heightAnchor.constraint(equalToConstant: 100).isActive = true



        if lastImage != nil {
            imgView.topAnchor.constraint(equalTo: lastImage!.bottomAnchor , constant: 20).isActive = true
        }else{
            imgView.topAnchor.constraint(equalTo: mainView.topAnchor , constant: 12).isActive = true
        }
        lastImage = samsam
        mainViewBootom = mainView.bottomAnchor.constraint(equalTo: lastImage!.bottomAnchor , constant: 12)
        mainViewBootom!.isActive = true
    }

    @objc func didClickedAdd(){
        let imgView = UIImageView(frame: CGRect(x: 20, y: 0, width: 30, height: 20))
        imgView.backgroundColor  = .orange
        mainView.addSubview(imgView)

        let ss = UIImageView(frame: CGRect(x: 0, y: 0, width: 40, height: 50))
        imgView.backgroundColor  = .green
        mainView.addSubview(ss)


        imgView.translatesAutoresizingMaskIntoConstraints = false
        imgView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
        imgView.widthAnchor.constraint(equalToConstant: 40).isActive = true
        imgView.heightAnchor.constraint(equalToConstant: 60).isActive = true


        ss.translatesAutoresizingMaskIntoConstraints = false
        ss.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = false
        ss.widthAnchor.constraint(equalToConstant: 80).isActive = true
        ss.heightAnchor.constraint(equalToConstant: 90).isActive = true

        if lastImage != nil {

            ss.topAnchor.constraint(equalTo: imgView.topAnchor , constant: 20).isActive = true

            imgView.topAnchor.constraint(equalTo: lastImage!.bottomAnchor , constant: 50).isActive = true



        }else{
            imgView.topAnchor.constraint(equalTo: mainView.topAnchor , constant: 10).isActive = true
            ss.bottomAnchor.constraint(equalTo: imgView.bottomAnchor , constant: 25).isActive = true


        }


        lastImage = imgView
        lastImage2 = ss
        mainView.removeConstraint(mainViewBootom!)


        mainViewBootom = mainView.bottomAnchor.constraint(equalTo: lastImage2!.bottomAnchor , constant: 40)




        mainViewBootom!.isActive = true
        view.layoutIfNeeded()

        scrollView.contentSize = CGSize(width: view.frame.width, height: mainView.frame.height)
        view.layoutIfNeeded()

    }

}

情侣笔记...

通过适当的约束设置,自动布局会自行处理 UIScrollView 内容大小。无需设置 scrollView.contentSize = ...

您有几个向 mainView 添加子视图(图像视图)的实例,它是滚动视图的子视图,但随后您将该子视图的约束添加到控制器的视图。确保将元素限制为正确的其他元素。

这是您的代码,其中包含更改注释:

class BenViewController: UIViewController {

    fileprivate var  lastImage:UIImageView?

// 1) don't need this
//  fileprivate var  lastImage2:UIImageView?

    fileprivate var mainViewBootom:NSLayoutConstraint?


    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .white
        setupVIew()
    }

// 2) don't need this
//  override func viewDidAppear(_ animated: Bool) {
//      scrollView.contentSize = CGSize(width: view.frame.width, height: mainView.frame.height)
//      view.layoutIfNeeded()
//  }

    //MARK: Components
    let scrollView:UIScrollView = {
        let sv = UIScrollView(frame: .zero)
        return sv
    }()

    let mainView:UIView = {
        let uv = UIView()
        uv.backgroundColor = .white
        return uv
    }()

    let btnAdd:UIButton = {
        let btn = UIButton(type: .system)
        btn.setTitle("Add", for: .normal)
        return btn
    }()



    let textField:UITextField = {
        let jake = UITextField()
        return jake

    }()

    //MARK: Setup UI
    func setupVIew() {
        view.addSubview(scrollView)
        view.addSubview(btnAdd)
        view.addSubview(textField)
        scrollView.translatesAutoresizingMaskIntoConstraints = false
        btnAdd.translatesAutoresizingMaskIntoConstraints = false
        textField.translatesAutoresizingMaskIntoConstraints = false

        NSLayoutConstraint.activate([




            btnAdd.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            btnAdd.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -12),
            btnAdd.widthAnchor.constraint(equalToConstant: 100),
            btnAdd.heightAnchor.constraint(equalToConstant: 45),


            //
            textField.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            textField.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: 25),
            textField.widthAnchor.constraint(equalToConstant: 100),
            textField.heightAnchor.constraint(equalToConstant: 45),
            //



            scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
            scrollView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
            scrollView.bottomAnchor.constraint(equalTo: btnAdd.topAnchor , constant: -12),
        ])
        btnAdd.addTarget(self, action: #selector(didClickedAdd), for: .touchUpInside)

        scrollView.addSubview(mainView)
        mainView.translatesAutoresizingMaskIntoConstraints = false

// 3) change this:
//      NSLayoutConstraint.activate([
//          mainView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
//          mainView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
//          mainView.topAnchor.constraint(equalTo: scrollView.topAnchor),
//      ])
//

// to this
        NSLayoutConstraint.activate([
            mainView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor),
            mainView.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor),
            mainView.topAnchor.constraint(equalTo: scrollView.topAnchor),
            mainView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor),
            mainView.widthAnchor.constraint(equalTo: scrollView.widthAnchor),
        ])
// end of change 3)

        let imgView = UIImageView(frame: CGRect(x: 0, y: 0, width: 150, height: 100))
        imgView.backgroundColor  = .red
        mainView.addSubview(imgView)


        let samsam = UIImageView(frame: CGRect(x: 0, y: 200, width: 40, height: 100))
        samsam.backgroundColor  = .blue
        mainView.addSubview(samsam)


        imgView.translatesAutoresizingMaskIntoConstraints = false

// 4) change view.centerXAnchor to mainView.centerXAnchor
//      imgView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
        imgView.centerXAnchor.constraint(equalTo: mainView.centerXAnchor).isActive = true

        imgView.widthAnchor.constraint(equalToConstant: 150).isActive = true
        imgView.heightAnchor.constraint(equalToConstant: 100).isActive = true


        samsam.translatesAutoresizingMaskIntoConstraints = false

// 5) change view.centerXAnchor to mainView.centerXAnchor
//      samsam.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
        samsam.centerXAnchor.constraint(equalTo: mainView.centerXAnchor).isActive = true

        samsam.topAnchor.constraint(equalTo: imgView.bottomAnchor).isActive = true
        samsam.widthAnchor.constraint(equalToConstant: 75).isActive = true
        samsam.heightAnchor.constraint(equalToConstant: 100).isActive = true



        if lastImage != nil {
            imgView.topAnchor.constraint(equalTo: lastImage!.bottomAnchor , constant: 20).isActive = true
        }else{
            imgView.topAnchor.constraint(equalTo: mainView.topAnchor , constant: 12).isActive = true
        }
        lastImage = samsam
        mainViewBootom = mainView.bottomAnchor.constraint(equalTo: lastImage!.bottomAnchor , constant: 12)
        mainViewBootom!.isActive = true
    }

    @objc func didClickedAdd(){
        let imgView = UIImageView(frame: CGRect(x: 20, y: 0, width: 30, height: 20))
        imgView.backgroundColor  = .orange
        mainView.addSubview(imgView)

        let ss = UIImageView(frame: CGRect(x: 0, y: 0, width: 40, height: 50))

// 6) typo or copy/paste mistake
//      imgView.backgroundColor  = .green
        ss.backgroundColor  = .green

        mainView.addSubview(ss)


        imgView.translatesAutoresizingMaskIntoConstraints = false

// 7) change view.centerXAnchor to mainView.centerXAnchor
//      imgView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
        imgView.centerXAnchor.constraint(equalTo: mainView.centerXAnchor).isActive = true

        imgView.widthAnchor.constraint(equalToConstant: 40).isActive = true
        imgView.heightAnchor.constraint(equalToConstant: 60).isActive = true


        ss.translatesAutoresizingMaskIntoConstraints = false

// 8) change view.leadingAnchor to mainView.leadingAnchor
//      ss.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = false
        ss.leadingAnchor.constraint(equalTo: mainView.leadingAnchor).isActive = false

        ss.widthAnchor.constraint(equalToConstant: 80).isActive = true
        ss.heightAnchor.constraint(equalToConstant: 90).isActive = true

// 9) always need to do this ... but did you mean imgView.bottomAnchor?
        ss.topAnchor.constraint(equalTo: imgView.topAnchor , constant: 20).isActive = true

        if lastImage != nil {

            // 9a) instead of only here
            //ss.topAnchor.constraint(equalTo: imgView.topAnchor , constant: 20).isActive = true

            imgView.topAnchor.constraint(equalTo: lastImage!.bottomAnchor , constant: 50).isActive = true

        }else{

            imgView.topAnchor.constraint(equalTo: mainView.topAnchor , constant: 10).isActive = true

        }

// 10) always need to do this
        // deactivate bottom constraint
        mainViewBootom?.isActive = false
        lastImage = ss
        mainViewBootom = mainView.bottomAnchor.constraint(equalTo: lastImage!.bottomAnchor, constant: 40)
        mainViewBootom?.isActive = true

// 11) don't need any of this
//      lastImage = imgView
//      lastImage2 = ss
//      mainView.removeConstraint(mainViewBootom!)
//
//
//      mainViewBootom = mainView.bottomAnchor.constraint(equalTo: lastImage2!.bottomAnchor , constant: 40)
//
//
//
//
//      mainViewBootom!.isActive = true
//      view.layoutIfNeeded()
//
//      scrollView.contentSize = CGSize(width: view.frame.width, height: mainView.frame.height)
//      view.layoutIfNeeded()

    }

}

使用 Xcode 的“查看调试器”(在我下面的屏幕快照中该按钮用红色圆圈圈出),您会看到发生了什么:

您的 ss 视图没有背景颜色。请注意,当您创建该视图时,您不小心第二次重置了 imgView 背景颜色,而不是设置 ss.backgroundColor.

解决这个问题,您会看到 imgViewss:

在尝试诊断此类问题时,视图调试器是您最好的朋友。现在,显然,绿色视图可能不在您想要的位置,但您现在应该能够看到它并非常容易地诊断该问题。


说了这么多,还有一些观察:

  1. 你让生活变得比你需要的更艰难。如果您只是为滚动视图和该滚动视图中的堆栈视图设置约束,那么您只需要添加一个排列的子视图。例如:

    @objc func didTapButton(_ sender: UIButton) {
        stackView.addArrangedSubview(randomView())
        stackView.addArrangedSubview(randomView())
    }
    

    请注意,一旦设置了堆栈视图和滚动视图(见下文),那么您根本不需要为这些子视图设置 contentSize 或约束(除了 widthAnchorheightAnchor)。自动布局引擎,结合stack view和scroll view之间的约束,为你搞定一切。

    所以,一个完整的工作示例:

    class ViewController: UIViewController {
    
        let scrollView: UIScrollView = {
            let scrollView = UIScrollView()
            scrollView.translatesAutoresizingMaskIntoConstraints = false
            return scrollView
        }()
    
        let stackView: UIStackView = {
            let stackView = UIStackView()
            stackView.translatesAutoresizingMaskIntoConstraints = false
            stackView.axis = .vertical
            stackView.alignment = .center
            stackView.spacing = 10
            return stackView
        }()
    
        let button: UIButton = {
            let button = UIButton(type: .system)
            button.translatesAutoresizingMaskIntoConstraints = false
            button.setTitle("Add", for: .normal)
            button.addTarget(self, action: #selector(didTapButton(_:)), for: .touchUpInside)
            return button
        }()
    
        override func viewDidLoad() {
            super.viewDidLoad()
            configure()
        }
    }
    
    // MARK: - Actions
    
    extension ViewController {
        @objc func didTapButton(_ sender: UIButton) {
            stackView.addArrangedSubview(randomView())
            stackView.addArrangedSubview(randomView())
        }
    }
    
    // MARK: - Private utility methods
    
    private extension ViewController {
        func configure() {
            view.addSubview(scrollView)
            view.addSubview(button)
            scrollView.addSubview(stackView)
    
            NSLayoutConstraint.activate([
                // define frame of `scrollView`
    
                scrollView.topAnchor.constraint(equalTo: view.topAnchor),
                scrollView.bottomAnchor.constraint(equalTo: button.topAnchor),
                scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
                scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
    
                // define frame of `button`
    
                button.centerXAnchor.constraint(equalTo: view.centerXAnchor),
                button.bottomAnchor.constraint(equalTo: view.bottomAnchor),
    
                // define contentSize of `scrollView` based upon size of `stackView`
    
                stackView.topAnchor.constraint(equalTo: scrollView.contentLayoutGuide.topAnchor),
                stackView.bottomAnchor.constraint(equalTo: scrollView.contentLayoutGuide.bottomAnchor),
                stackView.leadingAnchor.constraint(equalTo: scrollView.contentLayoutGuide.leadingAnchor),
                stackView.trailingAnchor.constraint(equalTo: scrollView.contentLayoutGuide.trailingAnchor),
    
                // but define width of `stackView` relative to the _main view_
    
                stackView.widthAnchor.constraint(equalTo: scrollView.frameLayoutGuide.widthAnchor)
            ])
    
            button.setContentHuggingPriority(.required, for: .vertical)
        }
    
        func randomView() -> UIView {
            let widthRange = view.bounds.width * 0.1 ... view.bounds.width * 0.9
            let heightRange = view.bounds.width * 0.1 ... view.bounds.width * 0.25
            let view = UIView()
            view.translatesAutoresizingMaskIntoConstraints = false
            NSLayoutConstraint.activate([
                view.widthAnchor.constraint(equalToConstant: .random(in: widthRange)),
                view.heightAnchor.constraint(equalToConstant: .random(in: heightRange))
            ])
            view.backgroundColor = UIColor(red: .random(in: 0.25...1), green: .random(in: 0.25...1), blue: .random(in: 0.25...1), alpha: 1)
            return view
        }
    }
    
  2. 更好的是,我会在 Interface Builder 中亲自设置滚动视图、堆栈视图、按钮和所有关联的约束,然后在我的示例中设置那个毛茸茸的 configure 方法完全消失。学习如何以编程方式创建视图很有趣,但在实际项目中,这很少是最有效的方法。在需要的地方进行编程视图(例如,通过单击按钮将排列好的子视图添加到堆栈视图),但除此之外,对于那些当你第一次 运行 应用程序时应该存在的视图,Interface Builder 是值得考虑的。

    例如它大大减少了上面的代码量,只剩下:

    class ViewController: UIViewController {
    
        @IBOutlet weak var scrollView: UIScrollView!
        @IBOutlet weak var stackView: UIStackView!
    
        @IBAction func didTapButton(_ sender: UIButton) {
            stackView.addArrangedSubview(randomView())
            stackView.addArrangedSubview(randomView())
        }
    }
    
    // MARK: - Private utility methods
    
    private extension ViewController {
        func randomView() -> UIView { ... }
    }
    

    显然,习惯在 IB 中设计视图和配置约束需要一段时间,但这是值得的。它从最基本的要素中提炼出我们的代码。

  3. 在您的代码中,您正在为这些图像视图设置框架,然后设置 translatesAutoresizingMaskIntoConstraints。在这种情况下设置 frame 绝对没有意义,因为 translatesAutoresizingMaskIntoConstraints 表示“忽略我的 frame,改用约束。”

  4. 我假设你做所有这些只是为了熟悉滚动视图,但值得注意的是,尤其是在添加大量图像视图时,滚动视图是一种固有的低效的方法。

    例如,假设您添加了 100 个图像视图,但您一次只能看到 8 个。你真的想同时在内存中保存所有 100 个图像视图吗?号

    但是,作为 UIScrollView 的子类的 UITableView 会处理这个问题。您最终只会将当前可见的图像视图保留在内存中。这是一个更好的方法。

    当您开始使用实际的 UIImage 对象时尤其如此,因为它们需要大量内存。当我们看到大小合理的 PNG/JPG 资产时,我们会产生一种安全感,但是当它们被加载到内存中时,它们是未压缩的并且需要不成比例的内存量。