Welcome to our Karrot Listing Framework. This powerful tool is developed with UIKit, but it is designed to be used like a declarative UI API, providing a seamless transition to SwiftUI and reducing migration costs.
Our framework is built with an optimized diffing algorithm, thanks to its dependency on DifferenceKit. This ensures high performance and swift rendering of your lists, allowing your application to run smoothly, even when handling large changes of data.
The API is designed to be simple and intuitive, allowing for rapid development without sacrificing quality or functionality. This means you can spend less time wrestling with complex code and more time creating the perfect user experience.
You can use The Swift Package Manager to install this framework by following these steps:
- Open up Xcode, and navigate to
Project
->Package dependencies
->Add Package Dependency (+)
. - In the search bar, enter the URL of this repository:
https://github.com/daangn/KarrotListKit
. - Specify the version you want to use. You can choose to use the latest version or a specific version.
- Click
Next
andFinish
to complete the installation. After the package is successfully added to your project, import the framework into the files where you want to use it:
import KarrotListKit
Now you're ready to start using the KarrotListKit Framework
See the KarrotListKit DocC documentation hosted on the Swift Package Index.
The CollectionViewAdapter
object serves as an adapter between the UIColletionView
logic and the KarrotListKit logic, encapsulating the core implementation logic of the framework
private let configuration = CollectionViewAdapterConfiguration()
private let layoutAdapter = CollectionViewLayoutAdapter()
private lazy var collectionViewAdapter = CollectionViewAdapter(
configuration: configuration,
collectionView: collectionView,
layoutAdapter: layoutAdapter
)
private lazy var collectionView = UICollectionView(
frame: .zero,
collectionViewLayout: UICollectionViewCompositionalLayout(
sectionProvider: layoutAdapter.sectionLayout
)
)
The Component is the smallest unit within the framework.
It allows for the declarative representation of data and actions to be displayed on the screen. We no longer need to depend on UICollectionViewCell
and UICollectionReusableView
, can write component-based.
The component has an interface very similar to UIViewRepresentable
. This similarity allows us to reduce the cost of migrating to SwiftUI
in the future.
struct ButtonComponent: Component {
typealias Content = Button
typealias ViewModel = Button.ViewModel
typealias Coordinator = Void
let viewModel: ViewModel
init(viewModel: ViewModel) {
self.viewModel = viewModel
}
func renderContent(coordinator: Coordinator) -> Button {
Button()
}
func render(in content: Button, coordinator: Coordinator) {
content.configure(viewModel: viewModel)
}
var layoutMode: ContentLayoutMode {
.flexibleHeight(estimatedHeight: 44.0)
}
}
We represent the list UI using List / Section / Cell. Not only that, but we can also map actions and layouts using modifiers.
let list = List {
Section(id: "Section1") {
Cell(
id: "Cell1",
component: ButtonComponent(viewModel: .init(title: $0.rawValue))
)
Cell(
id: "Cell2",
component: ButtonComponent(viewModel: .init(title: $0.rawValue))
)
.didSelect { context in
// handle selection
}
.willDisplay { context in
// handle displaying
}
}
.withHeader(ButtonComponent(viewModel: .init(title: "Header")))
.withFooter(ButtonComponent(viewModel: .init(title: "Footer")))
.withSectionLayout(.vertical(spacing: 12.0))
}
collectionViewAdapter.apply(
list,
animatingDifferences: true
) {
// after completion
}
The size of a View is actually adjusted when the View is displayed on the screen, and this can be adjusted through sizeThatFits
. Until then, the component can represent its own size as an estimate.
struct ButtonComponent: Component {
typealias Content = Button
// ...
var layoutMode: ContentLayoutMode {
.flexibleHeight(estimatedHeight: 44.0)
}
}
final class Button: UIControl {
// ...
override func sizeThatFits(_ size: CGSize) -> CGSize {
// return size of a Button
}
}
SectionLayout is tightly coupled with UICollectionViewCompositionalLayout
, providing a custom interface to return an NSCollectionLayoutSection
.
Section(id: "Section1") {
// ...
}
.withSectionLayout { [weak self] context -> NSCollectionLayoutSection? in
// return NSCollectionLayoutSection object
}
We often implement pagination functionality. KarrotListKit provides an convenience API that makes it easy to implement pagination functionality.
NextBatchTrigger belongs to Section, and the trigger logic is very simple: threshold >= index of last Cell - index of Cell to will display
Section(id: "Section1") {
// ...
}
.withNextBatchTrigger(NextBatchTrigger(threshold: 10) { context in
// handle trigger
})
Provides prefetching of resources API to improve scroll performance.
The CollectionViewAdapter conforms to the UICollectionViewDataSourcePrefetching
. The framework provides the ComponentResourcePrefetchable
and CollectionViewPrefetchingPlugin
protocols for compatibility.
Below is sample code for Image prefetching.
let collectionViewAdapter = CollectionViewAdapter(
configuration: .init(),
collectionView: collectionView,
layoutAdapter: CollectionViewLayoutAdapter(),
prefetchingPlugins: [
RemoteImagePrefetchingPlugin(
remoteImagePrefetcher: RemoteImagePrefetcher()
)
]
)
extension ImagePrefetchableComponent: ComponentRemoteImagePrefetchable {
var remoteImageURLs: [URL] {
[
URL(string: "imageURL"),
URL(string: "imageURL"),
URL(string: "imageURL")
]
}
}
We warmly welcome and appreciate any contributions to this project! Feel free to submit pull requests to enhance the functionalities of this project.
This project is licensed under the Apache License 2.0. See LICENSE for details.