Swift 在可滚动列表中添加项目

Posted

技术标签:

【中文标题】Swift 在可滚动列表中添加项目【英文标题】:Swift add items in scrollable list 【发布时间】:2019-10-21 16:00:24 【问题描述】:

现在这就是我项目中的全部内容:

最终它的外观和功能应该是这样的:

1.如何将项目添加到 ScrollView(在 2 x X 视图中)

2.如何使 ScrollView 真正能够滚动(并像下面的 3 张图片一样刷新),或者这是否可以仅通过一个列表来解决?

更新

最终的视图应该是这样的:

“MainWishList”单元格和“neue Liste erstellen”(= 添加新单元格)应该从一开始就存在。当用户单击“添加单元格”时,他应该能够为列表选择名称和图像。

【问题讨论】:

您给出的示例几乎可以肯定是使用UICollectionView,它是UIScrollView 的子类。查看本教程:raywenderlich.com/… 您向滚动视图添加一些东西,例如表格视图或在您的示例中的集合视图。然后将您的项目添加到集合视图的数据源中。如果内容大于显示,滚动视图将自动滚动,您根本不需要与滚动视图交互。 @JoakimDanielson 所以我只需添加一个集合视图并将其放在滚动视图的顶部,还是必须以某种方式连接它们? 实际上,如果您使用情节提要并添加集合视图,您还会获得滚动视图作为其中的一部分。 对,明白了。有没有办法从情节提要中添加响应式“添加项目”按钮?它应该看起来像 3 个屏幕截图中的那个,如果添加了一个项目,它也会移动到最后一个位置 【参考方案1】:

UICollectionView 的部分内置功能是当您的项目(单元格)超出框架的容量时自动滚动。所以不需要在滚动视图中嵌入集合视图。

这是一个基本示例。一切都是通过代码完成的(没有@IBOutlet@IBAction 或原型单元)。创建一个新的UIViewController 并将其类分配给ExampleViewController,如下所示:

//
//  ExampleViewController.swift
//  CollectionAddItem
//
//  Created by Don Mag on 10/22/19.
//

import UIKit

// simple cell with label
class ContentCell: UICollectionViewCell 

    let theLabel: UILabel = 
        let v = UILabel()
        v.translatesAutoresizingMaskIntoConstraints = false
        v.textAlignment = .center
        return v
    ()

    override init(frame: CGRect) 
        super.init(frame: frame)
        commonInit()
    

    required init?(coder: NSCoder) 
        super.init(coder: coder)
        commonInit()
    

    func commonInit() -> Void 
        contentView.backgroundColor = .yellow
        contentView.addSubview(theLabel)
        // constrain label to all 4 sides
        NSLayoutConstraint.activate([
            theLabel.topAnchor.constraint(equalTo: contentView.topAnchor),
            theLabel.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
            theLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
            theLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
        ])
    



// simple cell with button
class AddItemCell: UICollectionViewCell 

    let btn: UIButton = 
        let v = UIButton()
        v.translatesAutoresizingMaskIntoConstraints = false
        v.setTitle("+", for: .normal)
        v.setTitleColor(.systemBlue, for: .normal)
        v.titleLabel?.font = UIFont.systemFont(ofSize: 40.0)
        return v
    ()

    // this will be used as a "callback closure" in collection view controller
    var tapCallback: (() -> ())?

    override init(frame: CGRect) 
        super.init(frame: frame)
        commonInit()
    

    required init?(coder: NSCoder) 
        super.init(coder: coder)
        commonInit()
    

    func commonInit() -> Void 
        contentView.backgroundColor = .green
        contentView.addSubview(btn)
        // constrain button to all 4 sides
        NSLayoutConstraint.activate([
            btn.topAnchor.constraint(equalTo: contentView.topAnchor),
            btn.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
            btn.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
            btn.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
        ])
        btn.addTarget(self, action: #selector(didTap(_:)), for: .touchUpInside)
    

    @objc func didTap(_ sender: Any) 
        // tell the collection view controller we got a button tap
        tapCallback?()
    



class ExampleViewController: UIViewController, UICollectionViewDataSource 

    let theCollectionView: UICollectionView = 
        let v = UICollectionView(frame: CGRect.zero, collectionViewLayout: UICollectionViewFlowLayout())
        v.translatesAutoresizingMaskIntoConstraints = false
        v.backgroundColor = .white
        v.contentInsetAdjustmentBehavior = .always
        return v
    ()

    let columnLayout = FlowLayout(
        itemSize: CGSize(width: 100, height: 100),
        minimumInteritemSpacing: 10,
        minimumLineSpacing: 10,
        sectionInset: UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10)
    )

    // track collection view frame change
    var colViewWidth: CGFloat = 0.0

    // example data --- this will be filled with simple number strings
    var theData: [String] = [String]()

    override func viewDidLoad() 
        super.viewDidLoad()

        view.backgroundColor = .systemYellow

        view.addSubview(theCollectionView)

        // constrain collection view
        //      100-pts from top
        //      60-pts from bottom
        //      40-pts from leading
        //      40-pts from trailing
        NSLayoutConstraint.activate([
            theCollectionView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 100.0),
            theCollectionView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -60.0),
            theCollectionView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 40.0),
            theCollectionView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -40.0),
        ])

        // register the two cell classes for reuse
        theCollectionView.register(ContentCell.self, forCellWithReuseIdentifier: "ContentCell")
        theCollectionView.register(AddItemCell.self, forCellWithReuseIdentifier: "AddItemCell")

        // set collection view dataSource
        theCollectionView.dataSource = self

        // use custom flow layout
        theCollectionView.collectionViewLayout = columnLayout

    

    override func viewDidLayoutSubviews() 
        super.viewDidLayoutSubviews()

        // only want to call this when collection view frame changes
        // to set the item size
        if theCollectionView.frame.width != colViewWidth 
            let w = theCollectionView.frame.width / 2 - 15
            columnLayout.itemSize = CGSize(width: w, height: w)
            colViewWidth = theCollectionView.frame.width
        
    

    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int 
        // return 1 more than our data array (the extra one will be the "add item" cell
        return theData.count + 1
    

    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell 

        // if item is less that data count, return a "Content" cell
        if indexPath.item < theData.count 
            let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "ContentCell", for: indexPath) as! ContentCell
            cell.theLabel.text = theData[indexPath.item]
            return cell
        

        // past the end of the data count, so return an "Add Item" cell
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "AddItemCell", for: indexPath) as! AddItemCell

        // set the closure
        cell.tapCallback = 
            // add item button was tapped, so append an item to the data array
            self.theData.append("\(self.theData.count + 1)")
            // reload the collection view
            collectionView.reloadData()
            collectionView.performBatchUpdates(nil, completion: 
                (result) in
                // scroll to make newly added row visible (if needed)
                let i = collectionView.numberOfItems(inSection: 0) - 1
                let idx = IndexPath(item: i, section: 0)
                collectionView.scrollToItem(at: idx, at: .bottom, animated: true)
            )
        

        return cell

    




// custom FlowLayout class to left-align collection view cells
// found here: https://***.com/a/49717759/6257435
class FlowLayout: UICollectionViewFlowLayout 

    required init(itemSize: CGSize, minimumInteritemSpacing: CGFloat = 0, minimumLineSpacing: CGFloat = 0, sectionInset: UIEdgeInsets = .zero) 
        super.init()

        self.itemSize = itemSize
        self.minimumInteritemSpacing = minimumInteritemSpacing
        self.minimumLineSpacing = minimumLineSpacing
        self.sectionInset = sectionInset
        sectionInsetReference = .fromSafeArea
    

    required init?(coder aDecoder: NSCoder) 
        fatalError("init(coder:) has not been implemented")
    

    override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? 
        let layoutAttributes = super.layoutAttributesForElements(in: rect)!.map  $0.copy() as! UICollectionViewLayoutAttributes 
        guard scrollDirection == .vertical else  return layoutAttributes 

        // Filter attributes to compute only cell attributes
        let cellAttributes = layoutAttributes.filter( $0.representedElementCategory == .cell )

        // Group cell attributes by row (cells with same vertical center) and loop on those groups
        for (_, attributes) in Dictionary(grouping: cellAttributes, by:  ($0.center.y / 10).rounded(.up) * 10 ) 
            // Set the initial left inset
            var leftInset = sectionInset.left

            // Loop on cells to adjust each cell's origin and prepare leftInset for the next cell
            for attribute in attributes 
                attribute.frame.origin.x = leftInset
                leftInset = attribute.frame.maxX + minimumInteritemSpacing
            
        

        return layoutAttributes
    

当你运行这个时,数据数组将为空,所以你首先看到的是:

每次点击“+”单元格时,都会在数据数组中添加一个新项目(在本例中为数字字符串),将调用reloadData(),并会出现一个新单元格。

一旦我们的数据数组中有足够的项目,以至于它们无法全部放入集合视图框架中,集合视图将变为可滚动的:

【讨论】:

这看起来棒极了,谢谢!但是我究竟如何将它与我的代码联系起来呢?现在我有一个 HomeViewController,它是第一张图片(没有 CollectionView)。如何将它与已经存在的 HomeViewController 连接起来? 创建一个新的单视图项目...用上面的代码替换默认ViewController.swift文件中的所有内容...打开故事板,将默认视图控制器类从ViewController更改为ExampleViewController。看看能不能运行。如果是这样,那么研究代码以了解它在做什么,然后尝试在您的项目中实现类似的东西。 @Chris - 查看viewDidLayoutSubviews() 覆盖。因为我在集合视图上使用了前导和尾随约束(而不是硬编码的宽度),所以这就是设置视图框架的位置......所以这就是我设置 itemSize 以获得两个相等宽度的“列”的位置。如果您为集合视图提供恒定宽度(无论设备大小),那么您可以删除该代码并在 let columnLayout = 声明中使用您想要的任何大小。 @Chris - 我将编辑后的 ​​ViewController.swift 文件放在这里:pastebin.com/2u92UC2V ...查看以// DonMag -开头的 cmets @Chris - 谷歌搜索uicollectionview center align layout ...你会发现很多资源、示例等

以上是关于Swift 在可滚动列表中添加项目的主要内容,如果未能解决你的问题,请参考以下文章

Swift - 如何对 ScrollView 中的项目使用自动布局?

如何在列表视图的滚动中添加更多项目?

在 Flutter 中保持滚动视图偏移的同时添加列表视图项

Flutter - 在列表中添加新项目时保持滚动位置

将列表视图滚动到新添加项目的位置

如何滚动到添加到 MVVM Light 列表视图中的新项目