Как создать сетку из квадратных элементов (например, как в фотобиблиотеке iOS) с помощью SwiftUI?
Я пробовал этот подход, но он не работает:
var body: some View {
List(cellModels) { _ in
Color.orange.frame(width: 100, height: 100)
}
}
Список по-прежнему имеет стиль UITableView:





Попробуйте использовать VStack и HStack
var body: some View {
GeometryReader { geometry in
VStack {
ForEach(1...3) {_ in
HStack {
Color.orange.frame(width: 100, height: 100)
Color.orange.frame(width: 100, height: 100)
Color.orange.frame(width: 100, height: 100)
}.frame(width: geometry.size.width, height: 100)
}
}
}
}
Вы можете обернуть ScrollView, если хотите прокрутить
Единственная проблема в том, что это не обновляется динамически в зависимости от ширины дисплея....
Да, чтобы приспособиться к размеру дисплея, нам нужно использовать GeometryReader. Я скорректировал свой код, чтобы построить сетку 3x3 в зависимости от размера дисплея.
Но это не список, а стек - без повторного использования. Что делать, если у меня есть 10000 предметов?
Затем вы делаете его динамическим, перебирая эти элементы в цикле ForEach. И, как я уже сказал, вы можете обернуть вертикальные или горизонтальные стеки с помощью ScrollView, если они не все помещаются на экране.
Этот ответ не имеет ничего общего с CollectionView. Ячейки не будут использоваться повторно, и у них ужасная производительность.
Повторное использование здесь не проблема — это декларативный код, поэтому здесь вы просто создаете сверхлегкие struct, соответствующие типу View. Повторное использование фактических экземпляров презентации обрабатывается без вашего участия. В этом и прелесть SwiftUI ? Хотя вопрос о лучших практиках представления коллекций в SwiftUI остается актуальным, я думаю. Кажется, здесь нет эквивалента UICollectionViewLayout, и это было то, что элегантно решало проблему в UIKit.
@Argent Первоначальный вопрос был о том, как создать сетку из квадратных элементов, и поэтому я дал один вариант. В нем ничего не говорилось о CollectionView и не было никаких заявлений о производительности.
@MaciekCzarnik хороший ответ, но это не совсем то же самое, что CollectionView, поскольку HStacks и VStacks не могут обрабатывать произвольный динамический макет; например, схема потока.
Так поддерживает ли SwiftUI версия ScrollView повторное использование или нет? Если нам нужно предъявить 1000 предметов, будут ли они все время в памяти?
Любая идея, как сделать его динамичным?
@MaciekCzarnik Я бы согласился. SwiftUI кажется полезным для создания быстрых, отзывчивых макетов и расположения полудинамического контента. Все, что требует более сложной логики или производительности, лучше всего использовать упакованные компоненты UIKit.
Одно из возможных решений — обернуть UICollectionView в UIViewRepresentable. См. Объединение и создание представлений SwiftUI Tutorial, где в качестве примера они оборачивают MKMapView.
На данный момент в SwiftUI нет эквивалента UICollectionView, и пока не планируется. См. обсуждение под этим твит.
Чтобы получить более подробную информацию, посмотрите видео Интеграция SwiftUI WWDC (~8:08).
Обновлять:
Начиная с iOS 14 (бета-версия) мы можем использовать Lazy*Stack, по крайней мере, для достижения производительности представления коллекции в SwiftUI. Когда дело доходит до расположения ячеек, я думаю, что нам все равно придется управлять им вручную для каждой строки/столбца.
Остерегайтесь плохая работаLazy*Stack при использовании со многими строками.
Я думаю, вы можете использовать прокрутку, как это
struct MovieItemView : View {
var body: some View {
VStack {
Image("sky")
.resizable()
.frame(width: 150, height: 200)
VStack {
Text("Movie Title")
.font(.headline)
.fontWeight(.bold)
Text("Category")
.font(.subheadline)
}
}
}
}
struct MoviesView : View {
var body: some View {
VStack(alignment: .leading, spacing: 10){
Text("Now Playing")
.font(.title)
.padding(.leading)
ScrollView {
HStack(spacing: 10) {
MovieItemView()
MovieItemView()
MovieItemView()
MovieItemView()
MovieItemView()
MovieItemView()
}
}
.padding(.leading, 20)
}
}
}
Думая в SwiftUI, есть простой способ:
struct MyGridView : View {
var body: some View {
List() {
ForEach(0..<8) { _ in
HStack {
ForEach(0..<3) { _ in
Image("orange_color")
.resizable()
.scaledToFit()
}
}
}
}
}
}
SwiftUI достаточно, если вы хотите, вам нужно иногда забывать, например, UIColectionView.
Проблема с этим подходом в том, что он не адаптирует столбцы в соответствии с шириной экрана. Кто-нибудь нашел решение?
Подсчитав UIScreen.main.bounds.width / (itemSize + spacing), вы получите количество предметов, которые помещаются в один ряд. (В случае, если все ваши горизонтальные интервалы одинаковы). Поместив это в вычисляемое свойство, вы можете легко использовать его в приведенном выше примере кода.
Проблема с этим решением заключается в том, что оно не работает для представлений коллекций с горизонтальной ориентацией. То есть это работает, но в этом случае мы вообще теряем возможность повторного использования.
@ManeManero Удалите рамку изображения и установите .edgesIgnoringSafeArea(.all)
Я написал небольшой компонент под названием ?GridStack, который создает сетку, которая подстраивается под доступную ширину. Даже когда это изменяется динамически, например, когда вы поворачиваете iPad.
https://github.com/pietropizzi/GridStack
Суть этой реализации аналогична тому, что здесь ответили другие (так что HStack внутри VStack), с той разницей, что она вычисляет ширину в зависимости от доступной ширины и конфигурации, которую вы передаете.
minCellWidth вы определяете наименьшую ширину, которую должен иметь ваш элемент в сетке.spacing вы определяете пространство между элементами в сетке.например
GridStack(
minCellWidth: 320,
spacing: 15,
numItems: yourItems.count
) { index, cellWidth in
YourItemView(item: yourItems[index]).frame(width: cellWidth)
}
Хотя эта ссылка может ответить на вопрос, лучше включить сюда основные части ответа и предоставить ссылку для справки. Ответы, содержащие только ссылки, могут стать недействительными, если связанная страница изменится. - Из обзора
@DerkJanSpeelman Я обновил ответ, включив в него основные части. Ваше здоровье
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)
}
}
}
Вы также можете настроить конфигурацию макета по умолчанию:
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)
}
}
}
Пожалуйста, обратитесь к демонстрационному GIF и тестовому приложению в репозитории GitHub:
https://github.com/Q-Mobile/QGrid
Я еще не тестировал вашу библиотеку, но она выглядит впечатляюще. Потрясающая работа. Я предполагаю, что это не может прокручиваться по горизонтали, верно?
@Imthath: эта функция включена в список дорожных карт / TODO ;-) Это не займет много времени, поэтому, если хотите, не стесняйтесь вносить свой вклад!
К сожалению, цель Mac не скомпилируется из-за попытки использовать UIDevice.
@GOR: повторите попытку с последней версией (v.0.1.3)
Я не могу найти, как добавить действие при касании элемента. Какой-то облом.
Я использую эту библиотеку, и я благодарен создателям. Комментарий перед упоминанием действия над элементом касания. просто передайте «Кнопку» или любое представление с прикрепленным к нему «tapGesture». QGrid (viewModel.colorSelectionNames, columns: columnCount) { colorName in Circle() .onTapGesture { self.viewModel.selectedColorName = colorName} }
ДУУУДЕ! Вы спасли мою жизнь! это невероятная работа!
Если я хочу выбрать один из них в коллекции, как я узнаю, какой из них выбран, номер выбранного
Это фантастика, спасибо @KarolKulesza!!
Пример оформления заказа на основе ZStack здесь
Grid(0...100) { _ in
Rectangle()
.foregroundColor(.blue)
}
XCode 11.0
Посмотрев некоторое время, я решил, что мне нужны все удобства и производительность от UICollectionView. Поэтому я реализовал протокол UIViewRepresentable.
Этот пример не реализует DataSource и имеет фиктивное поле data: [Int] в представлении коллекции.
Вы должны использовать @Bindable var data: [YourData] на AlbumGridView, чтобы автоматически перезагружать представление при изменении данных.
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)
}
}
}
Пожалуйста, используйте SwiftUI LazyGridView вместо этого для >= iOS 14
Метод cellForItemAt не вызывает меня, если я добавляю AlbumGridView из своего представления.
@GauravThummar Я бы внимательно изучил ленивые представления, представленные на WWDC 2020, вместо того, чтобы пробовать этот материал для представления коллекции.
Спасибо, Филипп, у меня это сработало, как только я добавил его в основной вид и дал конкретный кадр.
Производительность @philipp LazyVGrid ужасна для большого количества элементов даже в iOS 15.
Основываясь на ответе Уилла, я завернул все это в SwiftUI ScrollView. Таким образом, вы можете добиться горизонтальной (в данном случае) или вертикальной прокрутки.
Он также использует GeometryReader, поэтому его можно рассчитать с размером экрана.
GeometryReader{ geometry in
.....
Rectangle()
.fill(Color.blue)
.frame(width: geometry.size.width/6, height: geometry.size.width/6, alignment: .center)
}
Вот рабочий пример:
import SwiftUI
struct MaterialView: View {
var body: some View {
GeometryReader{ geometry in
ScrollView(Axis.Set.horizontal, showsIndicators: true) {
ForEach(0..<2) { _ in
HStack {
ForEach(0..<30) { index in
ZStack{
Rectangle()
.fill(Color.blue)
.frame(width: geometry.size.width/6, height: geometry.size.width/6, alignment: .center)
Text("\(index)")
}
}
}.background(Color.red)
}
}.background(Color.black)
}
}
}
struct MaterialView_Previews: PreviewProvider {
static var previews: some View {
MaterialView()
}
}
Поскольку я не использую Catalina Beta, я написал здесь свой код, который вы можете запустить на Xcode 11 (Mojave) в качестве игровой площадки, чтобы воспользоваться преимуществами компиляции во время выполнения и предварительного просмотра.
В основном, когда вы ищете сеточный подход, вы должны иметь в виду, что дочернее представление SwiftUI получает параметр идеального размера из родительского представления, поэтому они могут автоматически адаптироваться на основе своего собственного контента, это поведение можно переопределить (не путать с быстрой директивой переопределения) путем принудительного просмотра определенного размера с помощью метода .frame(...).
На мой взгляд, это делает поведение View стабильным, так как структура Apple SwiftUI была правильно протестирована.
import PlaygroundSupport
import SwiftUI
struct ContentView: View {
var body: some View {
VStack {
ForEach(0..<5) { _ in
HStack(spacing: 0) {
ForEach(0..<5) { _ in
Button(action: {}) {
Text("Ok")
}
.frame(minWidth: nil, idealWidth: nil, maxWidth: .infinity, minHeight: nil, idealHeight: nil, maxHeight: .infinity, alignment: .center)
.border(Color.red)
}
}
}
}
}
}
let contentView = ContentView()
PlaygroundPage.current.liveView = UIHostingController(rootView: contentView)
Я сам решал эту проблему, и, используя в качестве основы источник, опубликованный выше @Anjali, а также @phillip (работа Эйвери Вайн), я обернул 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
}
}
}
Теперь класс DataProvider позволит нам получить доступ к этому привязываемому снимку и обновить пользовательский интерфейс, когда мы этого захотим. Этот класс существенный для правильного обновления представления коллекции. Модели DataSection и DataObject имеют ту же структуру, что и предоставленная Эйвери Вайн - так что если они вам нужны, посмотрите там.
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)
}
}
}
А теперь 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()
}
}
И только для тех визуальных ссылок (да, это работает в окне предварительного просмотра Xcode):
Мы разработали пакет swift, который предоставляет полнофункциональный CollectionView для использования в SwiftUI.
Найдите его здесь: 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))
}
}
}
См. демонстрационный проект для примеров гораздо более сложных макетов.
Это часть невероятной работы, которую вы здесь получили.
Это просто безумно впечатляет.
Этот пакет очень глючный...
ОБНОВЛЕНИЕ: этот ответ связан с iOS 13. Для iOS 14 у нас есть LazyGrids + многое другое, и следование этому ответу не будет полезным.
Для создания CollectionView без использования UIKit прежде всего нам нужно расширение массива. расширение массива поможет нам разделить наш массив, вокруг которого мы хотим создать TableView. Ниже код расширения + 3 примера. Чтобы немного лучше понять, как работает это расширение, взгляните на этот сайт, с которого я скопировал расширение: https://www.hackingwithswift.com/example-code/language/how-to-split-an-array-into-chunks
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]]
Теперь давайте создадим наше представление 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()
}
}
Изображение предварительного просмотра кода выше
Объяснение:
Прежде всего, нам нужно четко указать, сколько столбцов нам нужно, и поместить это число в наше разбитое на фрагменты расширение. В моем примере у нас есть массив (arrayOfInterest) чисел от 1 до 18, который мы хотим отобразить в нашем представлении, и я решил, что хочу, чтобы мое представление имело 4 столбца, поэтому я разбил его на 4 (так что 4 — это число наших столбцов).
Чтобы сделать CollectionView, наиболее очевидно, что наш CollectionView представляет собой СПИСОК элементов, поэтому он должен быть в списке, чтобы его можно было легко прокручивать (НЕТ, НЕ ДЕЛАЙТЕ ЭТОГО! вместо этого используйте ScrollView. Я видел странное поведение в то время как эти 2 foreach находятся в списке). после ScrollView у нас есть 2 ForEach , первый позволяет нам зацикливать столько строк, сколько необходимо, а второй помогает нам создавать столбцы.
Я знаю, что не полностью объяснил код, но я уверен, что им стоит поделиться с вами, чтобы вам было проще просматривать таблицы. Это изображение — ранний пример реального приложения, которое я делаю., и это не что иное, как CollectionView, поэтому вы можете быть уверены, что этот подход работает хорошо.
ВОПРОС: какой смысл иметь массив и пытаться позволить Swift создавать эти индексы для foreach?
это просто! если у вас есть массив, который определяет его значения/количество значений во время выполнения, например. вы получаете числа из веб-API, и этот API сообщает вам, сколько чисел находится в вашем массиве, тогда вам нужно будет использовать какой-то подход, подобный этому, и позволить Swift позаботиться об индексах foreachs.
ОБНОВИТЬ:
Дополнительная информация, читать их необязательно.
СПИСОК VS ПРОКРУТКА: как некоторые из вас могут не знать, список работает немного иначе, чем прокрутка. когда вы создаете scrollview, он всегда вычисляет весь ScrollView, а затем показывает его нам. но list этого не делает, при использовании списков Swift автоматически вычисляет только несколько компонентов списка, которые необходимы для отображения текущего представления, и когда вы прокручиваете список вниз, он заменяет только старые значения, которые прокручивается, с новыми значениями тех, что внизу экрана, с. так что в целом список всегда легче и может быть намного быстрее, когда вы работаете с тяжелым представлением, потому что он не вычисляет все ваше представление в начале, а вычисляет только необходимые вещи, а ScrollView - нет.
ПОЧЕМУ ВЫ СКАЗАЛИ, ЧТО МЫ ДОЛЖНЫ ИСПОЛЬЗОВАТЬ SCROLLVIEW ВМЕСТО СПИСКА? как я уже говорил, есть некоторые взаимодействия со списком, которые вам, вероятно, не нравятся. например, при создании списка можно нажимать на каждую строку, и это нормально, но плохо то, что нажимать можно ТОЛЬКО на всю строку! это означает, что вы не можете установить действие касания для левой стороны ряда и другое действие для правой стороны! это всего лишь одно из странных взаимодействий List() это либо нуждается в некоторых знаниях, которых у меня нет! или это большая проблема с xcode-ios, или, может быть, все в порядке и как задумано! я думаю, что это проблема Apple, и я надеюсь, что она будет исправлена самое большее до следующей WWDC. (ОБНОВЛЕНИЕ: и это, конечно, было исправлено с введением всех вещей, таких как LazyGrids для iOS14-SwiftUI)
КАКИЕ-ЛИБО СПОСОБЫ РЕШИТЬ ЭТУ ПРОБЛЕМУ? насколько я знаю, единственный способ - использовать UIKit. Я перепробовал множество способов со SwiftUI, и хотя я обнаружил, что вы можете получить помощь от ActionSheet и ContextMenu, чтобы сделать списки лучше с точки зрения опций при нажатии на них, я не смог получить оптимальную предполагаемую функциональность из список SwiftUI. так что, судя по моему мнению, разработчики SwiftUI пока могут только ждать.
Устав от поиска множества сложных решений или библиотек Github, я решил сделать свое собственное, простое и красивое решение математический.
var items : [ITEM] = [...YOUR_ITEMS...]When N is the number of ROWS and 2 is the number of COLUMNS
ForEach, один для столбцов и один для строк.Into both
ForEach: (i) current index of ROWS, and (j) current index of COLUMNS
Note: Xcode 11.3.1
var items : [ITEM] = [...YOUR_ITEMS...]
var body: some View {
VStack{
// items.count/2 represent the number of rows
ForEach(0..< items.count/2){ i in
HStack(alignment: .center,spacing: 20){
//2 columns
ForEach(0..<2){ j in
//Show your custom view here
// [(i*2) + j] represent the index of the current item
ProductThumbnailView(product: self.items[(i*2) + j])
}
}
}.padding(.horizontal)
Spacer()
}
}
Вы не можете использовать его с большим количеством объектов :) Проблема с памятью. Только в списке есть элементы многократного использования внутри
я новичок, я могу понять, как большая сетка m x n может исчерпать память, но это тип решения, который не позволяет прокручивать сетку m x n, где m, n варьируется от 1 до 64. с прокруткой и масштабированием. Я думаю просто отображать пиксели.
Хотя следующая WWDC не за горами, нам все еще не хватает представления коллекции в SwiftUI. Я попытался предоставить достойный пример того, как создать свой собственный Представление коллекции SwiftUI, используя UIViewControllerRepresentable и Combine. Я решил создать свое собственное представление коллекции вместо использования библиотек с открытым исходным кодом, так как я чувствовал, что им не хватает некоторых ключевых функций, или они были раздуты слишком многими вещами. В моем примере я использовал UICollectionViewDiffableDataSource и UICollectionViewCompositionalLayout для создания представления коллекции. Он поддерживает функции pullToRefresh и pagination.
Полную реализацию можно найти в: https://github.com/shabib87/SwiftUICollectionView.
iOS 14 и XCode 12
SwiftUI для iOS 14 предлагает новый и простой в использовании вид сетки, который называется LazyVGrid: https://developer.apple.com/documentation/swiftui/lazyvgrid
Вы можете начать с определения массива GridItem. GridItems используются для указания свойств макета для каждого столбца. В этом случае все GridItems являются гибкими.
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)
}
}
}
}
}
В iOS 15 производительность LazyVGrid ужасна (даже для самого простого примера) по сравнению с UICollectionView, который не масштабируется с количеством элементов. Довольно нелепо, что у них есть лекции WWDC о том, как избежать заминок, и они даже не исправляют проблемы с производительностью в своих собственных компонентах.
Это похоже на то, что вы ищете averyvine.com/blog/programming/2019/06/07/…