paololeonardi / waterfallgrid Goto Github PK
View Code? Open in Web Editor NEWA waterfall grid layout view for SwiftUI.
License: MIT License
A waterfall grid layout view for SwiftUI.
License: MIT License
Hi, thanks for this library.
I want to ask you something,
When I put WaterfallGrid inside scrollview, the grid just disappears.
How I can fix that?
var body: some View {
GeometryReader { geometry in
ScrollView(.vertical) {
Text("Hello")
WaterfallGrid((0..<10), id: \.self) { index in
Image("swift_logo")
.resizable()
.aspectRatio(contentMode: .fit)
}
.gridStyle(
columnsInPortrait: 2,
columnsInLandscape: 3,
spacing: 8,
padding: EdgeInsets(top: 16, leading: 8, bottom: 16, trailing: 8),
animation: .easeInOut(duration: 0.5)
)
.scrollOptions(
direction: .horizontal,
showsIndicators: true
)
Text("Hello")
}
}
}
Come debbo modificare il codice sotto, per visualizzare le sezioni?
ForEach(0..<3, id: \.self) { index in
Section {
Text("Sezione N. \(index)")
WaterfallGrid(self.thumbnails) { thumb in
ThumbnailView(thumb: thumb)
.onTapGesture {
let index = self.thumbnails.firstIndex { thumbnails in
thumbnails.id == thumb.id
}
self.thumbnails[index!].isSelected.toggle()
}
}
}
}
Tieni presente che se utilizzo il codice sotto:
WaterfallGrid(thumbnails) { thumb in
ThumbnailView(thumb: thumb)
.onTapGesture {
let index = self.thumbnails.firstIndex { thumbnails in
thumbnails.id == thumb.id
}
self.thumbnails[index!].isSelected.toggle()
}
}
funziona perfettamente, come puoi vedere nello screenshot sotto.
Bug Report
The "waterfall" behavior doesn't work when using the column / horizontal layout option. If you have a number of items with varying sizes and apply the .scrollOptions(direction: .horizontal)
modifier, the items take up a defined amount of height and do not "waterfall" into one another.
I believe this has to do with how the sizes of items are calculated; the calculations aren't correct for the horizontal layout because they may still be prioritizing the same factors used to calculate the vertical waterfall behavior.
How can i hide scroll ??
I have an AsyncImage class that displays a placeholder when the image is fetching. I tried to use this library with my AsyncImage but it just gets stuck with showing the placeholder. My guess is the WaterFallGrid isn't updating when the AsyncImages changed? Is there any way to handle url images besides pre-loading them into an array of UIImages?
Can I use this library in Rxswift?
Awesome stuff mate! : )
Is there a way to use a rectangle() or photo, instead of data?
I need it to be right to left grid. is there any possibility to do that?
Hi, I was making an app and I am using Kingfisher, it is to use Url instead of images, the Url becomes an image after being published. I was using the Waterfall Grid but it won't let me use the Kingfisher. How can I do it?
App crashes when images are more than 40-50 on fast scroll. How this bug can be solved? It works ok with lists but with waterfall grid it crashes!
var coverRow: some View {
VStack {
if selectedTask.taskCover != nil {
Image(uiImage: selectedTask.tTaskCover)
.resizable()
.aspectRatio(contentMode: .fit)
.cornerRadius(CGFloat.bl_4.double)
.blendMode(colorScheme == .dark ? .screen : .normal)
.opacity(colorScheme == .dark ? 0.75 : 0.85)
} else {
Button(action: {
isShowingImagePicker = true
}, label: {
RoundedRectangle(cornerRadius: CGFloat.bl_4.double)
.fill(Color.s_c.opacity(0.35))
.frame(height: 180)
.overlay(Image(systemName: "photo").modifier(SK_20(textColor: Color.t_t_c, weight: .regular)))
})
}
}
}
Hi, is there a way to span a cell over multiple columns?
For example, I want 2 cells, and then 1 cell that spans over all columns…
1 2
-3-
4 5
thx
Example code
NavigationView {
WaterfallGrid((0..<10), id: .self) { index in
Image(systemName: "photo")
.resizable()
.aspectRatio(contentMode: .fit)
}
}
Hi there!
I was wondering if there is any way to disable the scrolling option and just have the stack show up? I'm trying to create a 2 by 2 grid.
WaterfallGrid((0..<4), id: .self) { index in
Rectangle()
.foregroundColor(Color("primary"))
.cornerRadius(8)
.frame(height: 125)
}
.gridStyle(
columns: 2,
spacing: 24,
padding: EdgeInsets(top: 20, leading: 30, bottom: 30, trailing: 30)
)
Thank you!
I have created this view, and I noticed when I clicked on the rectangle which have more than 1 item in it, it will go into navigationLink View and then when I back to this main View, the first rectangle goes to the bottom. I have made sure that this is not my code issue. Can you help me?
ThankYou very much.
Group {
WaterfallGrid(viewModel.categories) { category in
CategoryItem(category: category)
}
.gridStyle(columns: 3)
WaterfallGrid(viewModel.products) { product in
ProductCell(product: product)
}
.gridStyle(columns: 2)
}
When I use 2 difference grid in same page , Layout will have two difference scroll.
What I need is only one scroll per screen.
thanks
How do I get the position while scrolling? I will hide the search bar according to it.
Hi,
I am using the WaterfallGrid to layout thumbnails of different aspect ratios. The data (including the JPEG data for the thumbnail images) comes from a local database (using GRDB). But it is read synchronously on the main thread, and the order (and content) of the displayed items does not change.
However the layout does. Sometimes it displays correctly, sometimes it does not (thumbnails overlap). See attached video where I switch back and forth between tabs of the app (not an actual TabView, the tab content is recreated from scratch each time).
The following error/warning shows up in the Xcode console when the thumbnails end up overlapping, but not when the layout works correctly:
Bound preference ElementPreferenceKey tried to update multiple times per frame.
Any idea what is causing this, and how I could fix it?
Btw, as you can see in the video, the layout always animates on initial display. Is that by design? And can that be disabled, ideally without disabling animations during changes?
I am getting this warning when I run my app and go to the view that is using the WaterfallGrid.
I think it's causing what than displays to disappear when I scroll up on the page. I'm not sure.
`///: FAVORITE ITEMS GRID
WaterfallGrid(favoriteItems.favorites, id: \.self) { item in
ZStack(alignment: .topTrailing) {
FavoriteItemView(favoriteItem: item)
} ///: ZSTACK
} //: WATERFALL
.gridStyle(
columnsInPortrait: 2,
columnsInLandscape: 3,
spacing: 10,
animation: .easeInOut(duration: 0.5)
)
.padding(EdgeInsets(top: 0, leading: 8, bottom: 15, trailing: 8))
.scrollOptions(direction: .vertical)
.onAppear(perform: loadFavorites)`
I am pulling the items that display from Cloud Firestore.
`func loadFavorites() {
db.collection("favorites").getDocuments() { (querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
} else {
for document in querySnapshot!.documents {
let docId = document.documentID
let itemInfo = document.data()
let itemType = itemInfo["itemType"] as? String ?? "shareable"
let itemTitle = itemInfo["itemTitle"] as? String ?? "DaySpring"
let itemImage = itemInfo["itemImage"] as? String ?? "No Photo"
let itemAuthor = itemInfo["itemAuthor"] as? String ?? "DaySpring"
db.collection("users").document(userId!).addSnapshotListener { documentSnapshot, error in
guard let userFavs = documentSnapshot else {
print("Error fetching document: \(error!)")
return
}
guard let data = userFavs.data() else {
print("Document data was empty.")
return
}
if let favorites = data["favorites"] as? [String: Any] {
if let favItem = favorites[docId] as? [String: Any] {
if let fav = favItem["itemActive"] as? Bool {
if fav {
if let itemDate = favItem["itemAdded"] as? Timestamp {
let thisDate = itemDate.dateValue()
self.favorites.append(FavoriteItem(id: docId, itemType: itemType, itemImage: itemImage, itemTitle: itemTitle, itemAuthor: itemAuthor, itemDate: thisDate))
DispatchQueue.main.async {
self.favorites.sort(by: { $0.itemDate > $1.itemDate })
}
}
}
}
}
}
}
}
}
}
}`
struct WaterfallGallery: View {
@ObservedObject var loader: ImagesLoader
@State var urlPaths: [String]
init(urlPaths: [String]) {
var URLs = [URL]()
for urlPath in urlPaths {
//TODO: provide defualt error image
URLs.append(URL(string: urlPath)!)
}
self._urlPaths = State(initialValue: urlPaths)
self.loader = ImagesLoader(urls: URLs)
}
var body: some View {
Group {
if self.loader.isLoading {
placeHolder
} else {
gallery
}
}
.onAppear(perform: loader.load)
.onDisappear(perform: loader.cancel)
}
private var gallery: some View {
WaterfallGrid(self.loader.$images) { uiImage in
Image(uiImage: uiImage).resizable().aspectRatio(contentMode: .fit).cornerRadius(10)
}
.gridStyle(columns: 2)
}
private var placeHolder: some View {
Text("Loading")
}
}
I followed the example to use WaterfallGrid but keep getting Cannot convert value of type 'some View' to closure result type '_'
on line:
Image(uiImage: uiImage).resizable().aspectRatio(contentMode: .fit).cornerRadius(10)
Is there any interface like this:
didSelectItemAtIndexPath
Thanks!
How to solve the lazy loading problem, very stuck
I'm using the WaterfallGrid in my own application. Although it's a great help, it seems that it does not adopt itself to the device.
This is how I integrated it:
var body: some View {
Frame(title: "My Personal Data") {
ForEach(self.model.content.rows) { row in
VStack {
WaterfallGrid(row.cards) { card in
SectionCard(content: card,
configuration: card.configuration,
onTapClosure: { id in
debugPrint("Tapped: \(id)")
})
}
.gridStyle(spacing: 50,
padding: EdgeInsets(top: 100, leading: 0, bottom: 0, trailing: 0))
}
}
}
.onAppear(perform: {
self.model.load()
})
}
While on an iPad, everything looks fine:
the iPhone display is not what I expected:
Am I doing something wrong?
When using WaterdallGrid, if there are less than 2 data to display, nothing will be displayed.
Here is a piece of source code that reproduces the bug at my end
ScrollView {
WaterfallGrid(dataArray, id: \.self) { file in
VStack {
if let post = viewModel.posts.first(where: { $0.fileIds.contains(file.id) }) {
NavigationLink(destination: ImageDetailView(item: post, file: file)) {
// View to display images.
ImageGridItem(url: file.url!)
}
}
}
}
}
import SwiftUI
import Kingfisher
// View to display images.
struct ImageGridItem: View {
private var url: String
init(url: String) {
self.url = url
}
var body: some View {
KFImage.url(URL(string: url))
.resizable()
.placeholder{
RoundedRectangle(cornerRadius: 30)
.fill(Color(0xe6e6e6))
}
.scaledToFit()
.clipped()
.cornerRadius(5)
.aspectRatio(contentMode: .fit)
}
}
3 or more | less than two data |
---|---|
Please some advice or fix
When i add more image in my @State variable it scrolls down 🤔
import SwiftUI
import Alamofire
import WaterfallGrid
struct HomeScreen: View {
@State var newPhotos: [HomeImage] = []
@State var pageNumber : Int = 1
@State var isPageRefreshing : Bool = false
@State var didAppear = false
@StateObject var homeImageVm = HomeScreenViewModel()
var body: some View {
NavigationView {
ScrollView(.vertical, showsIndicators: false) {
LazyVStack {
WaterfallGrid(newPhotos) { item in
NavigationLink(destination:
SelectedImage(image: SelectedImageClass(id: item.id, createdAt: item.createdAt, updatedAt: item.updatedAt, promotedAt: item.promotedAt, width: item.width, height: item.height, color: item.color, blur_hash: item.blur_hash, homeImageDescription: item.homeImageDescription, altDescription: item.altDescription, description: item.description, urls: item.urls, user: item.user, categories: item.categories))
) {
AppNetworkImage(imageUrl: item.urls?.small ?? "")
}
}
.gridStyle(
columnsInPortrait: 2,
columnsInLandscape: 3,
spacing: 8,
animation: .linear(duration: 0.5)
)
.scrollOptions(direction: .vertical)
.padding(EdgeInsets(top: 16, leading: 8, bottom: 16, trailing: 8))
}
Button("Load More") {
getHotPhotos(page: pageNumber)
}
.padding()
}
.onAppear(perform: {
if !didAppear {
getHotPhotos(page: pageNumber)
}
didAppear = true
})
.navigationBarTitle("Home", displayMode: .automatic)
.navigationBarItems(
trailing:
NavigationLink(destination:
SearchImageScreen()
) {
Image(systemName: "magnifyingglass")
}
)
}
}
func getHotPhotos(page:Int) {
let parameters: [String: Any] = [
"client_id" : AppConst.clinetid,
"order_by": "latest",
"page":String(page),
"per_page":"20"
]
AF.request(AppConst.baseurl+AppConst.photoUrl,method: .get,parameters: parameters).validate().responseDecodable(of: [HomeImage].self) { (response) in
guard let data = response.value else {
isPageRefreshing = false
return
}
withAnimation {
newPhotos.append(contentsOf: data)
isPageRefreshing = false
pageNumber = pageNumber + 1
}
}
}
}
struct HomeScreen_Previews: PreviewProvider {
static var previews: some View {
HomeScreen()
}
}
Add an option to select the grid scrollable axes.
I am having an issue where when I first come to the screen the images don't go into two columns like they should. After I navigate into one of the image items and return to the initial screen the images go into two columns.
Navigate back to screen, images waterfall as they should:
My code:
`
struct FavoritesView: View {
@EnvironmentObject var viewRouter: ViewRouter
@ObservedObject var favoriteItems = FavoriteItemModel()
var body: some View {
HStack {
WaterfallGrid(favoriteItems.favorites, id: \.self) { item in
FavoriteItemView(favoriteitem: item)
} // Waterfall
.gridStyle(
columnsInPortrait: 2,
columnsInLandscape: 3,
spacing: 8,
padding: EdgeInsets(top: 16, leading: 8, bottom: 16, trailing: 8),
animation: .easeInOut(duration: 0.5)
)
.navigationBarTitle("Favorites", displayMode: .inline)
.onAppear(perform: getFavorites)
} // HStack
}
func getFavorites() {
favoriteItems.loadFavorites()
}
}
`
`
struct FavoriteItemView: View {
let favoriteitem: FavoriteItem
@EnvironmentObject var shareableItemVM: ShareableItemViewModel
@State private var showShareableModal: Bool = false
var body: some View {
HStack{
NavigationLink(destination: ShareableDetailView(shareableId: self.favoriteitem.id, categoryId: 0, categoryType: "", shareItemType: favoriteitem.itemType, showShareableModal: self.$showShareableModal)) {
WebImage(url: URL(string: self.favoriteitem.itemImage))
.resizable()
.aspectRatio(contentMode: .fit)
.cornerRadius(10)
}
.environmentObject(self.shareableItemVM)
.buttonStyle(PlainButtonStyle())
}
}
}
`
I am using similar code with the WaterfallGrid in a couple other views, and I do not have this issue. The only thing I can think of is because these images are at different heights.
Any thoughts?
Hi, your WaterfallGrid seems very nice and could fit in may App. However I wonder if you could giving me some pointers or direct help on how I can adapt your image grid example so that I can pass an array of URLs (URLs of jpg's in my Apps document directory) instead of using Assets.xcassets?
Regards,
Claes
Hi, I am new to SwiftUI and I am a little lost on how to actually incorporate WaterfallGrid into my app / UI. I tried creating a WaterfallGrid as such:
WaterfallGrid(listsViewModel) { listViewModel in
NavigationLink(destination: ItemListView(list: listViewModel.list)) {
CardView(listViewModel: listViewModel)
}
}
But i get the error: Cannot find 'WaterfallGrid' in scope
I see this only iOS13+ ? Realy ?
WaterfallGrid((0..<30), id: .self) { index in
CategoryBook().gesture(LongPressGesture(minimumDuration: 0.5, maximumDistance: 1).onEnded{ v in
self.previewShow = true
})
}
use LongPressGesture can't scroll
ENV: above Xcode 11.2.
Hi, I've tried to set a maxHeight for each item like this:
KFImage(imgUrl)
.resizable()
.aspectRatio(contentMode: .fit).frame(minHeight: 0, maxHeight: 400).border(Color.red)
But finally each item becomes the same height 400. How could I get maxHeight worked?
Are there plans to allow for deleting items, with animation?
scrollView(){
WaterfallGrid{()
}
Not show in NavigationView
Not support lazyload
xcode display "Bound preference ElementPreferenceKey tried to update multiple times per frame".
The view is not displayed, switch page and reload the view before it can be displayed
i see your code
return ScrollView(style.scrollDirection) {
ZStack(alignment: .topLeading) {
ForEach(data, id: self.dataId) { element in
self.content(element)
.frame(width: self.style.scrollDirection == .vertical ? columnWidth : nil,
height: self.style.scrollDirection == .horizontal ? columnWidth : nil)
.background(PreferenceSetter(id: element[keyPath: self.dataId]))
.alignmentGuide(.top, computeValue: { _ in self.alignmentGuides[element[keyPath: self.dataId]]?.y ?? 0 })
.alignmentGuide(.leading, computeValue: { _ in self.alignmentGuides[element[keyPath: self.dataId]]?.x ?? 0 })
.opacity(self.alignmentGuides[element[keyPath: self.dataId]] != nil ? 1 : 0)
}
}
.padding(style.padding)
.frame(maxWidth: .infinity, maxHeight: .infinity)
.animation(self.loaded ? self.style.animation : nil)
}
i think your ForEach can change use List
because when image count > 300 pic, use List can Recycle reuse
What do you think ??
I want to use WaterfallGrid to contain some buttons which are laid out in a grid depending on the size available. WaterfallGrid is in a VStack
with a List
and some other views. I want the grid to just fit the buttons, so to fit the content as tightly as possible, and disable scrolling. Is this possible?
Does this library support lazy loading where it doesn't load everything upfront but instead loads just visible elements?
Thank you!
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.