{"id":18270079,"url":"https://github.com/liscio/CollectionThing","last_synced_at":"2025-04-04T23:31:41.834Z","repository":{"id":139400249,"uuid":"222817001","full_name":"liscio/CollectionThing","owner":"liscio","description":"Scroll fast using pure SwiftUI","archived":false,"fork":false,"pushed_at":"2019-11-21T18:03:32.000Z","size":20,"stargazers_count":78,"open_issues_count":4,"forks_count":3,"subscribers_count":3,"default_branch":"master","last_synced_at":"2024-11-05T11:52:19.277Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/liscio.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2019-11-20T00:33:49.000Z","updated_at":"2024-07-23T03:46:51.000Z","dependencies_parsed_at":null,"dependency_job_id":"29ef91c4-b9de-4d44-a9b9-4b244236f4b4","html_url":"https://github.com/liscio/CollectionThing","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/liscio%2FCollectionThing","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/liscio%2FCollectionThing/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/liscio%2FCollectionThing/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/liscio%2FCollectionThing/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/liscio","download_url":"https://codeload.github.com/liscio/CollectionThing/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247266476,"owners_count":20910831,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-05T11:38:16.402Z","updated_at":"2025-04-04T23:31:41.824Z","avatar_url":"https://github.com/liscio.png","language":"Swift","readme":"#  A CollectionView-y Thing For SwiftUI\n\nThis is a sketch of an approach that lets you put a _ton_ of items into a SwiftUI `ScrollView` while maintaining decent performance. Even with *50,000* elements, the view appears almost immediately, and memory usage is not terrible.\n\nNo weird uses of `DispatchQueue.async`, and (as far as I am concerned) it doesn't _really_ contain any gross hacks. Beauty is in the eye of the beholder, etc…\n\n## How does it work?\n\nIt's a lot like a `{UI,NS}CollectionView` in that you're responsible for maintaining the layout logic of views by yourself. But—as you can see—the `WrappedLayout` struct that I supplied isn't overly complicated. It just takes your model objects, and packages them up into rows. Those rows have `frame`s, and the layout itself has an overall `contentSize`.\n\nThe `ContentView` calculates the current `visibleRect` using `PreferenceKey`s, and on changing preference values, the `layout` is queried for the rows that overlap the current `visibleRect` (plus a bit of \"slop factor\" to reduce flashing—play around for your own needs).\n\nA `@State` variable tracks the current set of `visibleRows`, and those are only updated when we start to get close to the edge of the rows we've already cached.\n\nWhen everything's laid out, the content of your `ScrollView` will look like this:\n\n```\n+++++++++++++++++++++++++\n|     Color(.clear)     |\n|                       |\n|                       |\n+++++++++++++++++++++++++\n|  VStack(visibleRows)  |\n|                       +++\n|                       | |\n|                       | | visibleRect \n|                       | |\n|                       +++\n|                       |\n+++++++++++++++++++++++++\n|                       |\n|                       |\n|                       |\n+++++++++++++++++++++++++\n```\n\nEffectively, the \"magic\" here is in the fact that a `VStack` contains _only_ as many rows as you'll need, and no more. It is positioned at the same spot where those visible rows would normally appear if you had a `VStack` containing _all_ of the rows in the layout. It looks an awful lot like the way `UICollectionView` works—only creating views that are visible, while defining a larger content area.\n\nAs you scroll, the inner `VStack` is _only_ updated when the `visibleRows` change. So you'll experience the native scrolling speed until it is deemed that new rows need to get \"faulted in\" to the view. Even then, a reasonably new device should be able to retain smooth scrolling since `SwiftUI` can generate that new set of views very quickly. _Much faster_ than trying to calculate the viewport for the entire data set.\n\nWhen the `visibleRows` _do_ change, they are mostly the same—the amount of churn inside the inner `VStack` _should_ be minimal because the `Row`s themselves are `Identifiable`.\n\n## Keys to Performance\n\nThere are a few things that (I think) are important here:\n\n1. The root-level `@ObservedObject` whose `value` does not change\n2. The `@State` variables that _only_ get set _when necessary_\n3. `Row` values that are identifiable, used in concert with the inner `VStack` to try and keep churn to a minimum\n\n## Known Issues\n\nThe implementation is obviously incomplete, and there many details that you'll need to get sorted out.\n\nStuff like:\n\n* Incorporating the `safeAreaInsets` into your layout (which are readable from the outer `GeometryProxy` on the `ScrollView`)\n* Dealing with rotation\n* Insertion/removal animations\n* Being smarter/faster about querying your `Row`s\n* Selection management\n\nPlenty of exercises for the reader. :)\n\n## Credits/etc.\n\nThanks to the folks at [swiftui-lab](https://swiftui-lab.com) for [their post](https://swiftui-lab.com/scrollview-pull-to-refresh/) that gave me a few nifty ideas that helped me narrow down my initial work on this.\n\nIf you find this repo helpful, that's great! To repay me, you can go and check out [Capo](http://capoapp.com). Then, tell your friends to do the same.\n\nAlso, pull requests are welcome if you find any opportunities for making this go _even faster_ without resorting to anything gross. \n","funding_links":[],"categories":["etc"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fliscio%2FCollectionThing","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fliscio%2FCollectionThing","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fliscio%2FCollectionThing/lists"}