InfiniteScrollViews

1.0.1

SwiftUI, UIKit and AppKit infinite ScrollView components, also includes a paged version.
b5i/InfiniteScrollViews

What's New

1.0.1

2023-11-11T19:13:29Z

Fixed:

  • Fixed problem when the size of the all the elements is not more than the frame of the InfiniteScrollView.

Full Changelog: v1.0.0...v1.0.1

InfiniteScrollViews

InfiniteScrollViews groups some useful SwiftUI and UIKit components.

A recursive logic

As we can't really generate an infinity of views and put them in a ScrollView, we need to use a recursive logic. The way UIInfiniteScrollView and UIPagedInfiniteScrollView can display an "infinite" amount of content works thanks to this logic:

  1. You have a generic type ChangeIndex, it is a piece of data that the component will give you in exchange of a View/UIViewController.
  2. When you initialize the component, it takes an argument of type ChangeIndex to draw its first view.
  3. When the user will scroll up/down or left/right the component will give you a ChangeIndex but to get its "next" or "previous" value, it will use the increase and decrease actions that are provided during initialization. It will be used to draw the "next" or "previous" View/UIViewController with the logic in 1. And it goes on and on indefinitely... with one exception: if you return nil when step 3. happens, it will just end the scroll and act like there's nothing more to display. Let's see an example: You want to draw an "infinite" calendar component like the one in the base Calendar app on iOS. All of this in SwiftUI (but it also work on UIKit!)

Example

  1. First let's see how we initialize the view:
    InfiniteScrollView(
         frame: CGRect,
         changeIndex: ChangeIndex,
         content: @escaping (ChangeIndex) -> Content,
         contentFrame: @escaping (ChangeIndex) -> CGRect,
         increaseIndexAction: @escaping (ChangeIndex) -> ChangeIndex?,
         decreaseIndexAction: @escaping (ChangeIndex) -> ChangeIndex?,
         orientation: UIInfiniteScrollView<ChangeIndex>.Orientation,
         refreshAction: ((@escaping () -> Void) -> ())? = nil,
         spacing: CGFloat = 0,
         updateBinding: Binding<Bool>? = nil
    )
    • frame: the frame of the InfiniteScrollView.
    • changeIndex: the first index that will be used to draw the view.
    • content: the query from the InfiniteScrollView to draw a View from a ChangeIndex.
    • contentFrame: the query from the InfiniteScrollView to get the frame of the View from the content query (They are separated so you can directly declare the View in the closure).
    • increaseIndexAction: the query from the InfiniteScrollView to get the value after a certain ChangeIndex (recursive logic).
    • decreaseIndexAction: the query from the InfiniteScrollView to get the value before a certain ChangeIndex (recursive logic).
    • orientation: the orientation of the InfiniteScrollView.
    • refreshAction: action to do when the user pull the InfiniteScrollView to the top to refresh the content, should be nil if there is no need to refresh anything. Gives an action that must be used in order for refresh to end.
    • spacing: space between the views.
    • updateBinding: boolean that can be changed if the InfiniteScrollView's content needs to be updated.
  2. Let's see how content, increaseIndexAction and decreaseIndexAction work:
    1. For our MonthView we need to provide a Date so that it will extract the month to display. It could be declared like this:
      content: { currentDate in
          MonthView(date: currentDate)
              .padding()
      }
    2. Now let's see how increase/decrease work: To increase we need to get the month after the provided Date:
      increaseIndexAction: { currentDate in
          return Calendar.current.date(byAdding: .init(month: 1), to: currentDate)
      }
      It will add one month to the currentDate and if the operation was unsuccessful then it returns nil and the InfiniteScrollView stops.
    3. The same logic applies to the decrease action:
      decreaseIndexAction: { currentDate in
          return Calendar.current.date(byAdding: .init(month: -1), to: currentDate)
      }

Other examples can be found in InfiniteScrollViewsExample.

SwiftUI

InfiniteScrollView

The infinite equivalent of the ScrollView component in SwiftUI.

PagedInfiniteScrollView

The infinite equivalent of the paged TabView component in SwiftUI.

UIKit

UIInfiniteScrollView

The infinite equivalent of the UIScrollView component in UIKit.

UIPagedInfiniteScrollView

A simpler version of UIPageViewController in UIKit.

Description

  • Swift Tools 5.4.0
View More Packages from this Author

Dependencies

  • None
Last updated: Sun Dec 29 2024 08:45:38 GMT-1000 (Hawaii-Aleutian Standard Time)