Evg*_*lov 26 ios uicollectionview ios13 swiftui
如何使用SwiftUI创建正方形项目的网格(例如在iOS照片库中)?
我尝试了这种方法,但是不起作用:
var body: some View {
List(cellModels) { _ in
Color.orange.frame(width: 100, height: 100)
}
}
Run Code Online (Sandbox Code Playgroud)
列表仍然具有UITableView样式:
Kev*_*inP 29
iOS 14 和 XCode 12
适用于 iOS 14 的 SwiftUI 带来了一个易于使用的全新原生网格视图,称为LazyVGrid:https : //developer.apple.com/documentation/swiftui/lazyvgrid
您可以从定义GridItem 的. GridItems 用于为每一列指定布局属性。在这种情况下,所有 GridItem 都是灵活的。
LazyVGrid 以 GridItem 的数组作为其参数,并根据定义的 GridItem 显示包含视图。
import SwiftUI
struct ContentView: View {
let columns = [
GridItem(.flexible()),
GridItem(.flexible()),
GridItem(.flexible()),
GridItem(.flexible())
]
var body: some View {
ScrollView {
LazyVGrid(columns: columns) {
ForEach(0...100, id: \.self) { _ in
Color.orange.frame(width: 100, height: 100)
}
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
Wil*_*ill 24
在 SwiftUI 中思考,有一个简单的方法:
struct MyGridView : View {
var body: some View {
List() {
ForEach(0..<8) { _ in
HStack {
ForEach(0..<3) { _ in
Image("orange_color")
.resizable()
.scaledToFit()
}
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
}
如果你愿意,SwiftUI 就足够了,有时你需要忘记 UICollectionView 之类的..
Mac*_*nik 12
一种可能的解决方案是将您包装UICollectionView成UIViewRepresentable。请参阅合并和创建视图 SwiftUI教程,其中将其包装MKMapView为示例。
到目前为止UICollectionView,SwiftUI中没有类似的东西,并且还没有计划。看到有关该推文的讨论。
要获取更多详细信息,请查看Integrated SwiftUI WWDC视频(〜8:08)。
App*_*ios 10
我们开发了一个 swift 包,它提供了一个在 SwiftUI 中使用的全功能 CollectionView。
在这里找到:https://github.com/apptekstudios/ASCollectionView
它旨在易于使用,但也可以充分利用新的 UICollectionViewCompositionalLayout 进行更复杂的布局。它支持自动调整单元格大小。
要实现网格视图,您可以按如下方式使用它:
import SwiftUI
import ASCollectionView
struct ExampleView: View {
@State var dataExample = (0 ..< 21).map { $0 }
var body: some View
{
ASCollectionView(data: dataExample, dataID: \.self) { item, _ in
Color.blue
.overlay(Text("\(item)"))
}
.layout {
.grid(layoutMode: .adaptive(withMinItemSize: 100),
itemSpacing: 5,
lineSpacing: 5,
itemSize: .absolute(50))
}
}
}
Run Code Online (Sandbox Code Playgroud)
有关更复杂布局的示例,请参阅演示项目。
QGrid是我创建的一个小型库,该库使用与SwiftUI的List视图相同的方法,通过从标识的数据的基础集合中按需计算其单元格:
假设您已经具有自定义单元格视图,QGrid则最简单的形式就是将它与您体内的这1行代码一起使用View:
struct PeopleView: View {
var body: some View {
QGrid(Storage.people, columns: 3) { GridCell(person: $0) }
}
}
struct GridCell: View {
var person: Person
var body: some View {
VStack() {
Image(person.imageName).resizable().scaledToFit()
Text(person.firstName).font(.headline).color(.white)
Text(person.lastName).font(.headline).color(.white)
}
}
}
Run Code Online (Sandbox Code Playgroud)
您还可以自定义默认布局配置:
struct PeopleView: View {
var body: some View {
QGrid(Storage.people,
columns: 3,
columnsInLandscape: 4,
vSpacing: 50,
hSpacing: 20,
vPadding: 100,
hPadding: 20) { person in
GridCell(person: person)
}
}
}
Run Code Online (Sandbox Code Playgroud)
请参考GitHub回购中的演示GIF和测试应用程序:
https://github.com/Q-Mobile/QGrid
代码 11.0
在寻找了一段时间后,我决定我想要 UICollectionView 的所有便利和性能。所以我实施了UIViewRepresentable协议。
此示例未实现 DataSource 并且data: [Int]在集合视图上有一个虚拟字段。当数据更改时,您将使用@Bindable var data: [YourData]onAlbumGridView自动重新加载您的视图。
AlbumGridView 然后可以像 SwiftUI 中的任何其他视图一样使用。
class AlbumPrivateCell: UICollectionViewCell {
private static let reuseId = "AlbumPrivateCell"
static func registerWithCollectionView(collectionView: UICollectionView) {
collectionView.register(AlbumPrivateCell.self, forCellWithReuseIdentifier: reuseId)
}
static func getReusedCellFrom(collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> AlbumPrivateCell{
return collectionView.dequeueReusableCell(withReuseIdentifier: reuseId, for: indexPath) as! AlbumPrivateCell
}
var albumView: UILabel = {
let label = UILabel()
return label
}()
override init(frame: CGRect) {
super.init(frame: frame)
contentView.addSubview(self.albumView)
albumView.translatesAutoresizingMaskIntoConstraints = false
albumView.topAnchor.constraint(equalTo: contentView.topAnchor).isActive = true
albumView.leftAnchor.constraint(equalTo: contentView.leftAnchor).isActive = true
albumView.rightAnchor.constraint(equalTo: contentView.rightAnchor).isActive = true
albumView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor).isActive = true
}
required init?(coder: NSCoder) {
fatalError("init?(coder: NSCoder) has not been implemented")
}
}
struct AlbumGridView: UIViewRepresentable {
var data = [1,2,3,4,5,6,7,8,9]
func makeUIView(context: Context) -> UICollectionView {
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: UICollectionViewFlowLayout())
collectionView.backgroundColor = .blue
collectionView.translatesAutoresizingMaskIntoConstraints = false
collectionView.dataSource = context.coordinator
collectionView.delegate = context.coordinator
AlbumPrivateCell.registerWithCollectionView(collectionView: collectionView)
return collectionView
}
func updateUIView(_ uiView: UICollectionView, context: Context) {
//
}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
class Coordinator: NSObject, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
private let parent: AlbumGridView
init(_ albumGridView: AlbumGridView) {
self.parent = albumGridView
}
// MARK: UICollectionViewDataSource
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
self.parent.data.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let albumCell = AlbumPrivateCell.getReusedCellFrom(collectionView: collectionView, cellForItemAt: indexPath)
albumCell.backgroundColor = .red
return albumCell
}
// MARK: UICollectionViewDelegateFlowLayout
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let width = collectionView.frame.width / 3
return CGSize(width: width, height: width)
}
}
}
Run Code Online (Sandbox Code Playgroud)
我自己一直在解决这个问题,并通过使用@Anjali 上面发布的源代码作为基础,以及@phillip,(Avery Vine 的工作),我包装了一个功能强大的 UICollectionView ...... ? 它将根据需要显示和更新网格。我还没有尝试过更可定制的视图或任何其他东西,但就目前而言,我认为它可以。
我在下面评论了我的代码,希望它对某人有用!
首先是包装纸。
struct UIKitCollectionView: UIViewRepresentable {
typealias UIViewType = UICollectionView
//This is where the magic happens! This binding allows the UI to update.
@Binding var snapshot: NSDiffableDataSourceSnapshot<DataSection, DataObject>
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
func makeUIView(context: UIViewRepresentableContext<UIKitCollectionView>) -> UICollectionView {
//Create and configure your layout flow seperately
let flowLayout = UICollectionViewFlowLayout()
flowLayout.sectionInsets = UIEdgeInsets(top: 25, left: 0, bottom: 25, right: 0)
//And create the UICollection View
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: flowLayout)
//Create your cells seperately, and populate as needed.
collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "customCell")
//And set your datasource - referenced from Avery
let dataSource = UICollectionViewDiffableDataSource<DataSection, DataObject>(collectionView: collectionView) { (collectionView, indexPath, object) -> UICollectionViewCell? in
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "customCell", for: indexPath)
//Do cell customization here
if object.id.uuidString.contains("D") {
cell.backgroundColor = .red
} else {
cell.backgroundColor = .green
}
return cell
}
context.coordinator.dataSource = dataSource
populate(load: [DataObject(), DataObject()], dataSource: dataSource)
return collectionView
}
func populate(load: [DataObject], dataSource: UICollectionViewDiffableDataSource<DataSection, DataObject>) {
//Load the 'empty' state here!
//Or any default data. You also don't even have to call this function - I just thought it might be useful, and Avery uses it in their example.
snapshot.appendItems(load)
dataSource.apply(snapshot, animatingDifferences: true) {
//Whatever other actions you need to do here.
}
}
func updateUIView(_ uiView: UICollectionView, context: UIViewRepresentableContext<UIKitCollectionView>) {
let dataSource = context.coordinator.dataSource
//This is where updates happen - when snapshot is changed, this function is called automatically.
dataSource?.apply(snapshot, animatingDifferences: true, completion: {
//Any other things you need to do here.
})
}
class Coordinator: NSObject {
var parent: UIKitCollectionView
var dataSource: UICollectionViewDiffableDataSource<DataSection, DataObject>?
var snapshot = NSDiffableDataSourceSnapshot<DataSection, DataObject>()
init(_ collectionView: UIKitCollectionView) {
self.parent = collectionView
}
}
}
Run Code Online (Sandbox Code Playgroud)
现在,DataProvider该类将允许我们访问该可绑定快照并在需要时更新 UI。此类对于正确更新集合视图至关重要。模型DataSection和 与Avery VineDataObject提供的模型具有相同的结构- 因此,如果您需要这些,请查看那里。
class DataProvider: ObservableObject { //This HAS to be an ObservableObject, or our UpdateUIView function won't fire!
var data = [DataObject]()
@Published var snapshot : NSDiffableDataSourceSnapshot<DataSection, DataObject> = {
//Set all of your sections here, or at least your main section.
var snap = NSDiffableDataSourceSnapshot<DataSection, DataObject>()
snap.appendSections([.main, .second])
return snap
}() {
didSet {
self.data = self.snapshot.itemIdentifiers
//I set the 'data' to be equal to the snapshot here, in the event I just want a list of the data. Not necessary.
}
}
//Create any snapshot editing functions here! You can also simply call snapshot functions directly, append, delete, but I have this addItem function to prevent an exception crash.
func addItems(items: [DataObject], to section: DataSection) {
if snapshot.sectionIdentifiers.contains(section) {
snapshot.appendItems(items, toSection: section)
} else {
snapshot.appendSections([section])
snapshot.appendItems(items, toSection: section)
}
}
}
Run Code Online (Sandbox Code Playgroud)
现在,CollectionView将显示我们的新系列。我用一些按钮制作了一个简单的 VStack,所以你可以看到它的运行情况。
struct CollectionView: View {
@ObservedObject var dataProvider = DataProvider()
var body: some View {
VStack {
UIKitCollectionView(snapshot: $dataProvider.snapshot)
Button("Add a box") {
self.dataProvider.addItems(items: [DataObject(), DataObject()], to: .main)
}
Button("Append a Box in Section Two") {
self.dataProvider.addItems(items: [DataObject(), DataObject()], to: .second)
}
Button("Remove all Boxes in Section Two") {
self.dataProvider.snapshot.deleteSections([.second])
}
}
}
}
struct CollectionView_Previews: PreviewProvider {
static var previews: some View {
CollectionView()
}
}
Run Code Online (Sandbox Code Playgroud)
并且仅针对那些视觉参考(是的,这是在 Xcode 预览窗口中运行的):
更新:此答案与 iOS 13 相关。对于 iOS 14,我们有 LazyGrids + 更多内容,遵循此答案将无济于事。
为了在不使用 UIKit 的情况下制作 CollectionView,首先我们需要一个数组扩展。数组扩展将帮助我们分块我们想要制作 TableView 的数组。以下是扩展的代码,+ 3 个示例。为了进一步了解此扩展程序的工作原理,请查看此站点,我从该站点复制了扩展程序:https : //www.hackingwithswift.com/example-code/language/how-to-split-数组成块
extension Array {
func chunked(into size: Int) -> [[Element]] {
return stride(from: 0, to: count, by: size).map {
Array(self[$0 ..< Swift.min($0 + size, count)])
}
}
}
let exampleArray = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
print(exampleArray.chunked(into: 2)) // prints [[1, 2], [3, 4], [5, 6], [7, 8], [9, 10], [11, 12]]
print(exampleArray.chunked(into: 3)) // prints [[1, 2, 3], [4, 5, 6], [7, 8, 9], [10, 11, 12]]
print(exampleArray.chunked(into: 5)) // prints [[1, 2, 3, 4, 5], [6, 7, 8, 9, 10], [11, 12]]
Run Code Online (Sandbox Code Playgroud)
现在让我们创建 SwiftUI 视图:
struct TestView: View {
let arrayOfInterest = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18].chunked(into: 4)
// = [[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12], [13, 14, 15, 16], [17, 18]]
var body: some View {
return VStack {
ScrollView {
VStack(spacing: 16) {
ForEach(self.arrayOfInterest.indices, id:\.self) { idx in
HStack {
ForEach(self.arrayOfInterest[idx].indices, id:\.self) { index in
HStack {
Spacer()
Text("\(self.arrayOfInterest[idx][index])")
.font(.system(size: 50))
.padding(4)
.background(Color.blue)
.cornerRadius(8)
Spacer()
}
}
}
}
}
}
}
}
}
struct TestView_Preview : PreviewProvider {
static var previews: some View {
TestView()
}
}
Run Code Online (Sandbox Code Playgroud)
解释:
首先,我们需要明确我们需要多少列并将该数字放入我们的分块扩展中。在我的例子中,我们有一个从 1 到 18 的数字数组(arrayOfInterest),我们想在我们的视图中显示它,我决定我希望我的视图有 4 列,所以我把它分成 4(所以 4 是数字我们的专栏)。
要创建一个 CollectionView,最明显的是我们的 CollectionView 是一个项目列表,所以它应该在一个列表中以使其易于滚动(不,不要这样做!改用 ScrollView。我见过奇怪的行为而这 2 个 foreach 在列表中)。在 ScrollView 之后,我们有 2 ForEach s,第一个使我们能够根据需要循环尽可能多的行,而第二个帮助我们制作列。
我知道我没有完美地解释代码,但我相信它值得与您分享,这样可以让您更轻松地查看表格。 此图像是我正在制作的真实应用程序的早期示例,它看起来与 CollectionView 无异,因此您可以确定这种方法运行良好。
问题:拥有一个数组并试图让 swift 为 foreach 创建这些索引有什么意义?
这很简单!如果您有一个在运行时定义其值/值数量的数组,例如您从 Web api 获取数字并且该 api 告诉您数组中有多少个数字,那么您需要使用某种方法像这样,让 swift 处理 foreachs 的索引。
更新:
更多信息,阅读这些是可选的。
LIST VS SCROLLVIEW:有些人可能不知道,列表的工作方式与滚动视图略有不同。当您创建滚动视图时,它总是计算整个 ScrollView,然后将其显示给我们。但是 list 不会这样做,当使用列表时,swift 会自动仅计算显示当前视图所需的少数列表组件,当您向下滚动到列表底部时,它只会替换正在显示的旧值用屏幕底部的新值滚动出来。所以总的来说, list 总是更轻,当你处理一个重视图时可以快得多,因为它在开始时不会计算你的所有视图,只计算必要的东西,而 ScrollView 不会。
你为什么说我们应该使用滚动视图而不是列表? 正如我之前所说,您可能不喜欢与列表的一些交互。例如在创建列表时,每一行都是可点击的,这很好,但不好的是只有整行是可点击的!这意味着您不能为一行的左侧设置点击动作,而为右侧设置不同的点击动作!这只是 List() 的奇怪交互之一,这需要一些我不具备的知识!或者是一个很大的 xcode-ios 问题,或者它可能很好并且符合预期!我认为这是一个苹果问题,我希望它最多能在下一个 WWDC 上得到修复。(更新:它当然随着 iOS14-SwiftUI 的 LazyGrids 之类的所有东西的引入而得到修复)
有什么办法可以解决这个问题? 据我所知,唯一的方法是使用 UIKit。我已经尝试了很多使用 SwiftUI 的方法,虽然我发现您可以从 ActionSheet 和 ContextMenu 获得帮助,以便在您点击列表时在选项方面做得更好,但我无法从中获得最佳的预期功能一个 SwiftUI 列表。所以从我的观点来看,SwiftUI 开发人员只能等待。
| 归档时间: |
|
| 查看次数: |
9432 次 |
| 最近记录: |