GithubHelp home page GithubHelp logo

crelies / advancedlist Goto Github PK

View Code? Open in Web Editor NEW
282.0 3.0 30.0 4.93 MB

Advanced List View for SwiftUI with pagination & different states

License: MIT License

Swift 100.00%
swiftui-components swiftui swiftui-example ios listview swift swift-package pagination pagination-functionality pagination-swiftui

advancedlist's Introduction

AdvancedList

Swift 5.3 Platforms Current version Build status Code coverage License

This package provides a wrapper view around the SwiftUI List view which adds pagination (through my ListPagination package) and an empty, error and loading state including a corresponding view.

๐Ÿ“ฆ Installation

Add this Swift package in Xcode using its Github repository url. (File > Swift Packages > Add Package Dependency...)

๐Ÿš€ How to use

The AdvancedList view is similar to the List and ForEach views. You have to pass data (RandomAccessCollection) and a view provider ((Data.Element) -> some View) to the initializer. In addition to the List view the AdvancedList expects a list state and corresponding views. Modify your data anytime or hide an item through the content block if you like. The view is updated automatically ๐ŸŽ‰.

import AdvancedList

@State private var listState: ListState = .items

AdvancedList(yourData, content: { item in
    Text("Item")
}, listState: listState, emptyStateView: {
    Text("No data")
}, errorStateView: { error in
    Text(error.localizedDescription)
        .lineLimit(nil)
}, loadingStateView: {
    Text("Loading ...")
})

๐Ÿ†• Custom List view

Starting from version 6.0.0 you can use a custom list view instead of the SwiftUI List used under the hood. As an example you can now easily use the LazyVStack introduced in iOS 14 if needed.

Upgrade from version 5.0.0 without breaking anything. Simply add the listView parameter after the upgrade:

AdvancedList(yourData, listView: { rows in
    if #available(iOS 14, macOS 11, *) {
        ScrollView {
            LazyVStack(alignment: .leading, content: rows)
                .padding()
        }
    } else {
        List(content: rows)
    }
}, content: { item in
    Text("Item")
}, listState: listState, emptyStateView: {
    Text("No data")
}, errorStateView: { error in
    Text(error.localizedDescription)
        .lineLimit(nil)
}, loadingStateView: {
    Text("Loading ...")
})

๐Ÿ†• Custom Content view

Starting from version 8.0.0 you have full freedom & control over the content view rendered in the items state of your AdvancedList. Use a SwiftUI List or a custom view.

Upgrade from version 7.0.0 without breaking anything and use the new API:

AdvancedList(listState: yourListState, content: {
    VStack {
        Text("Row 1")
        Text("Row 2")
        Text("Row 3")
    }
}, errorStateView: { error in
    VStack(alignment: .leading) {
        Text("Error").foregroundColor(.primary)
        Text(error.localizedDescription).foregroundColor(.secondary)
    }
}, loadingStateView: ProgressView.init)

๐Ÿ“„ Pagination

The Pagination functionality is now (>= 5.0.0) implemented as a modifier. It has three different states: error, idle and loading. If the state of the Pagination changes the AdvancedList displays the view created by the view builder of the specified pagination object (AdvancedListPagination). Keep track of the current pagination state by creating a local state variable (@State) of type AdvancedListPaginationState. Use this state variable in the content ViewBuilder of your pagination configuration object to determine which view should be displayed in the list (see the example below).

If you want to use pagination you can choose between the lastItemPagination and the thresholdItemPagination. Both concepts are described here. Just specify the type of the pagination when adding the .pagination modifier to your AdvancedList.

The view created by the content ViewBuilder of your pagination configuration object will only be visible below the List if the last item of the List appeared! That way the user is only interrupted if needed.

Example:

@State private var paginationState: AdvancedListPaginationState = .idle

AdvancedList(...)
.pagination(.init(type: .lastItem, shouldLoadNextPage: {
    paginationState = .loading
    DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
        items.append(contentsOf: moreItems)
        paginationState = .idle
    }
}) {
    switch paginationState {
    case .idle:
        EmptyView()
    case .loading:
        if #available(iOS 14, *) {
            ProgressView()
        } else {
            Text("Loading ...")
        }
    case let .error(error):
        Text(error.localizedDescription)
    }
})

๐Ÿ“ Move and ๐Ÿ—‘๏ธ delete items

To enable the move or delete function just use the related onMove or onDelete view modifier. Per default the functions are disabled if you don't add the view modifiers.

import AdvancedList

@State private var listState: ListState = .items

AdvancedList(yourData, content: { item in
    Text("Item")
}, listState: listState, emptyStateView: {
    Text("No data")
}, errorStateView: { error in
    Text(error.localizedDescription)
        .lineLimit(nil)
}, loadingStateView: {
    Text("Loading ...")
})
.onMove { (indexSet, index) in
    // move me
}
.onDelete { indexSet in
    // delete me
}

๐ŸŽ›๏ธ Filtering

You can hide items in your list through the content block. Only return a view in the content block if a specific condition is met.

๐ŸŽ Example

The following code shows how easy-to-use the view is:

import AdvancedList

@State private var listState: ListState = .items

AdvancedList(yourData, content: { item in
    Text("Item")
}, listState: listState, emptyStateView: {
    Text("No data")
}, errorStateView: { error in
    VStack {
        Text(error.localizedDescription)
            .lineLimit(nil)
        
        Button(action: {
            // do something
        }) {
            Text("Retry")
        }
    }
}, loadingStateView: {
    Text("Loading ...")
})

For more examples take a look at the Example directory.

Migration

Migration 2.x -> 3.0

The AdvancedList was dramatically simplified and is now more like the List and ForEach SwiftUI views.

  1. Delete your list service instances and directly pass your data to the list initializer
  2. Create your views through a content block (initializer parameter) instead of conforming your items to View directly (removed type erased wrapper AnyListItem)
  3. Pass a list state binding to the initializer (before: the ListService managed the list state)
  4. Move and delete: Instead of setting AdvancedListActions on your list service just pass a onMoveAction and/or onDeleteAction block to the initializer

Before:

import AdvancedList

let listService = ListService()
listService.supportedListActions = .moveAndDelete(onMove: { (indexSet, index) in
    // please move me
}, onDelete: { indexSet in
    // please delete me
})
listService.listState = .loading

AdvancedList(listService: listService, emptyStateView: {
    Text("No data")
}, errorStateView: { error in
    VStack {
        Text(error.localizedDescription)
            .lineLimit(nil)
        
        Button(action: {
            // do something
        }) {
            Text("Retry")
        }
    }
}, loadingStateView: {
    Text("Loading ...")
}, pagination: .noPagination)

listService.listState = .loading
// fetch your items ...
listService.appendItems(yourItems)
listService.listState = .items

After:

import AdvancedList

@State private var listState: ListState = .items

AdvancedList(yourData, content: { item in
    Text("Item")
}, listState: $listState, onMoveAction: { (indexSet, index) in
    // move me
}, onDeleteAction: { indexSet in
    // delete me
}, emptyStateView: {
    Text("No data")
}, errorStateView: { error in
    VStack {
        Text(error.localizedDescription)
            .lineLimit(nil)
        
        Button(action: {
            // do something
        }) {
            Text("Retry")
        }
    }
}, loadingStateView: {
    Text("Loading ...")
}, pagination: .noPagination)
Migration 3.0 -> 4.0

Thanks to a hint from @SpectralDragon I could refactor the onMove and onDelete functionality to view modifiers.

Before:

import AdvancedList

@State private var listState: ListState = .items

AdvancedList(yourData, content: { item in
    Text("Item")
}, listState: $listState, onMoveAction: { (indexSet, index) in
    // move me
}, onDeleteAction: { indexSet in
    // delete me
}, emptyStateView: {
    Text("No data")
}, errorStateView: { error in
    VStack {
        Text(error.localizedDescription)
            .lineLimit(nil)
        
        Button(action: {
            // do something
        }) {
            Text("Retry")
        }
    }
}, loadingStateView: {
    Text("Loading ...")
}, pagination: .noPagination)

After:

import AdvancedList

@State private var listState: ListState = .items

AdvancedList(yourData, content: { item in
    Text("Item")
}, listState: $listState, emptyStateView: {
    Text("No data")
}, errorStateView: { error in
    VStack {
        Text(error.localizedDescription)
            .lineLimit(nil)
        
        Button(action: {
            // do something
        }) {
            Text("Retry")
        }
    }
}, loadingStateView: {
    Text("Loading ...")
}, pagination: .noPagination)
.onMove { (indexSet, index) in
    // move me
}
.onDelete { indexSet in
    // delete me
}
Migration 4.0 -> 5.0

Pagination is now implemented as a modifier ๐Ÿ’ช And last but not least the code documentation arrived ๐Ÿ˜€

Before:

private lazy var pagination: AdvancedListPagination<AnyView, AnyView> = {
    .thresholdItemPagination(errorView: { error in
        AnyView(
            VStack {
                Text(error.localizedDescription)
                    .lineLimit(nil)
                    .multilineTextAlignment(.center)

                Button(action: {
                    // load current page again
                }) {
                    Text("Retry")
                }.padding()
            }
        )
    }, loadingView: {
        AnyView(
            VStack {
                Divider()
                Text("Loading...")
            }
        )
    }, offset: 25, shouldLoadNextPage: {
        // load next page
    }, state: .idle)
}()

@State private var listState: ListState = .items

AdvancedList(yourData, content: { item in
    Text("Item")
}, listState: $listState, emptyStateView: {
    Text("No data")
}, errorStateView: { error in
    VStack {
        Text(error.localizedDescription)
            .lineLimit(nil)
        
        Button(action: {
            // do something
        }) {
            Text("Retry")
        }
    }
}, loadingStateView: {
    Text("Loading ...")
}, pagination: pagination)

After:

@State private var listState: ListState = .items
@State private var paginationState: AdvancedListPaginationState = .idle

AdvancedList(yourData, content: { item in
    Text("Item")
}, listState: $listState, emptyStateView: {
    Text("No data")
}, errorStateView: { error in
    VStack {
        Text(error.localizedDescription)
            .lineLimit(nil)
        
        Button(action: {
            // do something
        }) {
            Text("Retry")
        }
    }
}, loadingStateView: {
    Text("Loading ...")
})
.pagination(.init(type: .lastItem, shouldLoadNextPage: {
    paginationState = .loading
    DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
        items.append(contentsOf: moreItems)
        paginationState = .idle
    }
}) {
    switch paginationState {
    case .idle:
        EmptyView()
    case .loading:
        if #available(iOS 14, *) {
            ProgressView()
        } else {
            Text("Loading ...")
        }
    case let .error(error):
        Text(error.localizedDescription)
    }
})
Migration 6.0 -> 7.0

I replaced the unnecessary listState Binding and replaced it with a simple value parameter.

Before:

import AdvancedList

@State private var listState: ListState = .items

AdvancedList(yourData, content: { item in
    Text("Item")
}, listState: $listState, emptyStateView: {
    Text("No data")
}, errorStateView: { error in
    VStack {
        Text(error.localizedDescription)
            .lineLimit(nil)
        
        Button(action: {
            // do something
        }) {
            Text("Retry")
        }
    }
}, loadingStateView: {
    Text("Loading ...")
}, pagination: .noPagination)

After:

import AdvancedList

@State private var listState: ListState = .items

AdvancedList(yourData, content: { item in
    Text("Item")
}, listState: listState, emptyStateView: {
    Text("No data")
}, errorStateView: { error in
    VStack {
        Text(error.localizedDescription)
            .lineLimit(nil)
        
        Button(action: {
            // do something
        }) {
            Text("Retry")
        }
    }
}, loadingStateView: {
    Text("Loading ...")
}, pagination: .noPagination)
Migration 7.0 -> 8.0 Nothing to do ๐ŸŽ‰

advancedlist's People

Contributors

celies-e avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

advancedlist's Issues

Scroll to the bottom when new element is added in the list ?

Hi, I stumbled upon this awesome library which does solve a lot of issues which are yet to be perfected by apple SwiftUI team, but the main problem which I was having is, I am building a chat screen in my app and cannot scroll to the bottom when a new chat is added in the list, I tried my own way of making a custom scroll view but I am worried that it might have a lot of performance issues and is not that optimized.

Support the Pagination for both sides of List View

Hello,

I did check your AdvanceList supporting Pagination. This is good. However , I've a requirement to support pagination on both sides i.e at the top and at the bottom of the list. Top side to fetch previous records and Bottom to fetch the next records.

Please provide this enhancement,

Thanks for your support

-Janakiram

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.