Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FEAT] Support RandomAccessCollection in the initializers rather than fixed Array #135

Closed
TheNoim opened this issue Oct 10, 2020 · 4 comments · Fixed by #142
Closed

[FEAT] Support RandomAccessCollection in the initializers rather than fixed Array #135

TheNoim opened this issue Oct 10, 2020 · 4 comments · Fixed by #142
Labels
enhancement New feature or request

Comments

@TheNoim
Copy link

TheNoim commented Oct 10, 2020

Is your feature request related to a problem? Please describe.
There is currently no good way library out that supports infinitive page swiping with an unknown size. This could be very useful. Example use case: Infinitiv calendar swipe view. The issue is, a calendar does not have a clear end in both directions. First, when I found this library I thought I finally have a good way to implement a week calendar view. However, then I found out that I still need to pass an array with a known size and a positive index.

Describe the solution you'd like
A way to use this without passing an array. Instead of:

@State var page: Int = 0
var items = Array(0..<10)

var body: some View {
    Pager(page: $page,
          data: items,
          id: \.identifier,
          content: { index in
              // create a page based on the data passed
              Text("Page: \(index)")
     })
 }

this

@State var page: Int = 0

var body: some View {
    Pager(page: $page,
          content: { index in
              // create a page based on the data passed
              // Index goes in both directions and can be negative
              Text("Page: \(index)")
     })
 }

Describe alternatives you've considered

The alternative would be to allow to pass custom array implementations. For example: Some struct which implements the RandomAccessCollection. Then you could implement your own "array" which auto extends itself in both directions. Negative and positive. RandomAccessCollection supports this:

    override var startIndex: Self.Index { get }

    override var endIndex: Self.Index { get }

You can update and define your own end and start.

I tried to implement something similar once by myself. But this is a bit buggy and not really nice:
https://gist.github.com/TheNoim/6ca6f35eb830a2d53d6adf04d68e05ba

@TheNoim TheNoim added the enhancement New feature or request label Oct 10, 2020
@fermoya
Copy link
Owner

fermoya commented Oct 12, 2020

@TheNoim I'm a bit confused by "A way to use this without passing an array". How is Pager supposed to know what populates the pages, otherwise? What you're seeing as an argument of the view builder is not an index but a data item. This is needed to populate the page.

All in all, the way you describe Pager behavior is incorrect. In the example provided, which I think you've got from the documentation and I'm gonna rename, index it's not just a random integer but a data item. So some might want to page integers from 0 to 10, some other users might want to iterate them from 10 to 20, some others might want to iterate months as you want. This is why "A way to use this without passing an array" doesn't quite makes sense. Pager takes an data array, a binding and a view builder block. The array is used to build the page at a specific index (that is, next/previous pages). The binding allows you to keep track of the current index.

Just for further clarification:

@State var page: Int = 0
var items = Array(0..<10)

var body: some View {
    Pager(page: $page,
          data: items,
          id: \.identifier,
          content: { index in
              //
              // This isn't just an index. It's an item from data. In this example is an integer
              //
              Text("Page: \(index)")
     })
 }

What you would need to do is something like this:

// 9 as we're in October
@State var page: Int = CalendaryMonth.current.monthIndex
@State var oldPage: Int = CalendaryMonth.current.monthIndex
@State var year = 2020
let months = [CalendarMonth(.january), ..., CalendarMonth(.october), CalendarMonth(.november), CalendarMonth(.december)]

var body: some View {
    Pager(page: $page,
          data: months,
          id: \.monthIndex,
          content: { month in
              // Render month accordingly depending on the year
              CalendarViewMonth(month: month, year: year)
     })
     .loopPages()
     .onPageChanged { newPage in
         if newPage == 0 && page == 11 { year += 1 }
         if newPage == 11 && oldPage == 0 { year -= 1 }
         oldPage = newPage
    }
 }

@TheNoim
Copy link
Author

TheNoim commented Oct 12, 2020

Pager takes an data array, a binding and a view builder block. The array is used to build the page at a specific index (that is, next/previous pages). The binding allows you to keep track of the current index.

This is not really against my post. My suggestion was more like this:
Allow to pass an object conforming to the RandomAccessCollection protocol. This would allow to define your own end and start of Indicies and even updating them dynamically. Currently, you can only pass fixed Data with the index start of zero and a known index end. It would be much cooler if the pager gets no data and the content gets rendered completely dynamic with a passed index. For an infinitive scenario, the pager doesn't need to know the length of an array. It only needs to render the last, the current and next item. It doesn't really need to know what kind of data it is or how many items the data source contains.

@fermoya
Copy link
Owner

fermoya commented Oct 12, 2020

I'm not following, could you please show me an example? Even just pseudo-code. I wanna see how you dynamically add data and change the index.

As far as I know, I think you could have a @State variable for you data and change it dynamically. This is what I do in the example code, InfiniteExampleView.swift, where I append more items on the fly. If going backwards, you could detect that (the way I was doing for year in my previous snippet) and insert more items in your array while incrementing the index (so if you append 5 previous months, then your new index is your current index plus 5).

@fermoya fermoya changed the title [FEAT] Infinitive pages in both directions with unknown count [FEAT] Support RandomAccessCollection in the initializers rather than fixed Array Oct 28, 2020
@fermoya fermoya linked a pull request Oct 28, 2020 that will close this issue
@fermoya
Copy link
Owner

fermoya commented Oct 28, 2020

I'm gonna start supporting RandomAccessCollection with Int indexes on version 1.13.0. I do the conversion to an Array internally. See version 1.13.0-beta.1

As far as I can see in your gist, this would be enough for you. Then, if you implement onPageChanged, you could call your updateCurrentIndex in this callback:

@State var page = 0 // set here the start index
@State var data: MyRandomAccessCollectionImplementation

Pager(page: $page, data: data, id: \.id) {
        // return your View here
    }
    .onPageChanged { index in
        data.updateCurrentIndex(index)
    }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants