UICollectionView и SwiftUI?

Как создать сетку из квадратных элементов (например, как в фотобиблиотеке iOS) с помощью SwiftUI?

Я пробовал этот подход, но он не работает:

var body: some View {
    List(cellModels) { _ in
        Color.orange.frame(width: 100, height: 100)
    }
}

Список по-прежнему имеет стиль UITableView:

UICollectionView и SwiftUI?

Это похоже на то, что вы ищете averyvine.com/blog/programming/2019/06/07/…

Anjali Kevadiya 01.08.2019 20:04
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
55
1
51 809
16
Перейти к ответу Данный вопрос помечен как решенный

Ответы 16

Попробуйте использовать 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, если хотите прокрутить

Единственная проблема в том, что это не обновляется динамически в зависимости от ширины дисплея....

Chris 05.06.2019 21:50

Да, чтобы приспособиться к размеру дисплея, нам нужно использовать GeometryReader. Я скорректировал свой код, чтобы построить сетку 3x3 в зависимости от размера дисплея.

Krames 05.06.2019 22:28

Но это не список, а стек - без повторного использования. Что делать, если у меня есть 10000 предметов?

Evgeny Mikhaylov 06.06.2019 03:00

Затем вы делаете его динамическим, перебирая эти элементы в цикле ForEach. И, как я уже сказал, вы можете обернуть вертикальные или горизонтальные стеки с помощью ScrollView, если они не все помещаются на экране.

Krames 06.06.2019 04:37

Этот ответ не имеет ничего общего с CollectionView. Ячейки не будут использоваться повторно, и у них ужасная производительность.

Argent 07.06.2019 08:34

Повторное использование здесь не проблема — это декларативный код, поэтому здесь вы просто создаете сверхлегкие struct, соответствующие типу View. Повторное использование фактических экземпляров презентации обрабатывается без вашего участия. В этом и прелесть SwiftUI ? Хотя вопрос о лучших практиках представления коллекций в SwiftUI остается актуальным, я думаю. Кажется, здесь нет эквивалента UICollectionViewLayout, и это было то, что элегантно решало проблему в UIKit.

Maciek Czarnik 07.06.2019 08:50

@Argent Первоначальный вопрос был о том, как создать сетку из квадратных элементов, и поэтому я дал один вариант. В нем ничего не говорилось о CollectionView и не было никаких заявлений о производительности.

Krames 07.06.2019 20:13

@MaciekCzarnik хороший ответ, но это не совсем то же самое, что CollectionView, поскольку HStacks и VStacks не могут обрабатывать произвольный динамический макет; например, схема потока.

leftspin 08.06.2019 19:19

Так поддерживает ли SwiftUI версия ScrollView повторное использование или нет? Если нам нужно предъявить 1000 предметов, будут ли они все время в памяти?

Sotiris Kaniras 07.07.2019 18:59

Любая идея, как сделать его динамичным?

Bjorn Morrhaye 14.07.2019 07:48

@MaciekCzarnik Я бы согласился. SwiftUI кажется полезным для создания быстрых, отзывчивых макетов и расположения полудинамического контента. Все, что требует более сложной логики или производительности, лучше всего использовать упакованные компоненты UIKit.

Krames 15.07.2019 21:34
Ответ принят как подходящий

Одно из возможных решений — обернуть UICollectionView в UIViewRepresentable. См. Объединение и создание представлений SwiftUI Tutorial, где в качестве примера они оборачивают MKMapView.

На данный момент в SwiftUI нет эквивалента UICollectionView, и пока не планируется. См. обсуждение под этим твит.

Чтобы получить более подробную информацию, посмотрите видео Интеграция SwiftUI WWDC (~8:08).

Обновлять:

Начиная с iOS 14 (бета-версия) мы можем использовать Lazy*Stack, по крайней мере, для достижения производительности представления коллекции в SwiftUI. Когда дело доходит до расположения ячеек, я думаю, что нам все равно придется управлять им вручную для каждой строки/столбца.

Остерегайтесь плохая работаLazy*Stack при использовании со многими строками.

noe 09.08.2021 16:20

Я думаю, вы можете использовать прокрутку, как это

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.

Проблема с этим подходом в том, что он не адаптирует столбцы в соответствии с шириной экрана. Кто-нибудь нашел решение?

Mane Manero 19.06.2019 12:39

Подсчитав UIScreen.main.bounds.width / (itemSize + spacing), вы получите количество предметов, которые помещаются в один ряд. (В случае, если все ваши горизонтальные интервалы одинаковы). Поместив это в вычисляемое свойство, вы можете легко использовать его в приведенном выше примере кода.

iComputerfreak 22.06.2019 22:24

Проблема с этим решением заключается в том, что оно не работает для представлений коллекций с горизонтальной ориентацией. То есть это работает, но в этом случае мы вообще теряем возможность повторного использования.

Vladimir88dev 04.07.2019 09:36

@ManeManero Удалите рамку изображения и установите .edgesIgnoringSafeArea(.all)

ssowri1 22.04.2020 17:15

Я написал небольшой компонент под названием ?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)
}

Хотя эта ссылка может ответить на вопрос, лучше включить сюда основные части ответа и предоставить ссылку для справки. Ответы, содержащие только ссылки, могут стать недействительными, если связанная страница изменится. - Из обзора

Derk Jan Speelman 09.07.2019 10:44

@DerkJanSpeelman Я обновил ответ, включив в него основные части. Ваше здоровье

Peter Minarik 09.07.2019 11:39

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 05.08.2019 11:06

@Imthath: эта функция включена в список дорожных карт / TODO ;-) Это не займет много времени, поэтому, если хотите, не стесняйтесь вносить свой вклад!

Karol Kulesza 06.08.2019 21:26

К сожалению, цель Mac не скомпилируется из-за попытки использовать UIDevice.

OwlOCR 26.10.2019 00:03

@GOR: повторите попытку с последней версией (v.0.1.3)

Karol Kulesza 26.10.2019 14:02

Я не могу найти, как добавить действие при касании элемента. Какой-то облом.

atulkhatri 18.12.2019 17:56

Я использую эту библиотеку, и я благодарен создателям. Комментарий перед упоминанием действия над элементом касания. просто передайте «Кнопку» или любое представление с прикрепленным к нему «tapGesture». QGrid (viewModel.colorSelectionNames, columns: columnCount) { colorName in Circle() .onTapGesture { self.viewModel.selectedColorName = colorName} }

M.Serag 16.04.2020 16:46

ДУУУДЕ! Вы спасли мою жизнь! это невероятная работа!

jforward5 18.04.2020 23:16

Если я хочу выбрать один из них в коллекции, как я узнаю, какой из них выбран, номер выбранного

Braver Chiang 18.05.2020 14:01

Это фантастика, спасибо @KarolKulesza!!

BrianS 05.01.2021 17:59

Пример оформления заказа на основе 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)
        }
    }
}

Снимок экрана

AlbumGridView Preview

Пожалуйста, используйте SwiftUI LazyGridView вместо этого для >= iOS 14

philipp 04.09.2020 01:48

Метод cellForItemAt не вызывает меня, если я добавляю AlbumGridView из своего представления.

Gaurav Thummar 15.12.2020 20:37

@GauravThummar Я бы внимательно изучил ленивые представления, представленные на WWDC 2020, вместо того, чтобы пробовать этот материал для представления коллекции.

philipp 16.12.2020 04:31

Спасибо, Филипп, у меня это сработало, как только я добавил его в основной вид и дал конкретный кадр.

Gaurav Thummar 17.12.2020 15:37

Производительность @philipp LazyVGrid ужасна для большого количества элементов даже в iOS 15.

Mycroft Canner 07.10.2021 16:57

Основываясь на ответе Уилла, я завернул все это в 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):

UICollectionView meets SwiftUI

Мы разработали пакет 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))
        }
    }
}

См. демонстрационный проект для примеров гораздо более сложных макетов.

PortraitLandscape

Это часть невероятной работы, которую вы здесь получили.

Mateusz 05.02.2020 10:00

Это просто безумно впечатляет.

Sondergaard 02.04.2020 05:53

Этот пакет очень глючный...

Mycroft Canner 07.10.2021 16:56

ОБНОВЛЕНИЕ: этот ответ связан с 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, я решил сделать свое собственное, простое и красивое решение математический.

  1. Думаю, у вас есть множество предметов var items : [ITEM] = [...YOUR_ITEMS...]
  2. Вы хотите отобразить сетку Nx2

When N is the number of ROWS and 2 is the number of COLUMNS

  1. Чтобы показать все элементы, вам нужно использовать два оператора ForEach, один для столбцов и один для строк.

Into both ForEach: (i) current index of ROWS, and (j) current index of COLUMNS

  1. Показать текущий элемент в индексе [(я * 2) + Дж]
  2. Теперь переходим к коду:

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()
   }
}

Вы не можете использовать его с большим количеством объектов :) Проблема с памятью. Только в списке есть элементы многократного использования внутри

iTux 14.05.2020 13:04

я новичок, я могу понять, как большая сетка m x n может исчерпать память, но это тип решения, который не позволяет прокручивать сетку m x n, где m, n варьируется от 1 до 64. с прокруткой и масштабированием. Я думаю просто отображать пиксели.

Ross Youngblood 05.09.2021 03:55

Хотя следующая 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)
                }
            }
        }
    }
}

LazyVGrid in use

В iOS 15 производительность LazyVGrid ужасна (даже для самого простого примера) по сравнению с UICollectionView, который не масштабируется с количеством элементов. Довольно нелепо, что у них есть лекции WWDC о том, как избежать заминок, и они даже не исправляют проблемы с производительностью в своих собственных компонентах.

Mycroft Canner 07.10.2021 16:55

Другие вопросы по теме