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 在可滚动列表中添加项目的主要内容,如果未能解决你的问题,请参考以下文章