JamitFoundation is a lightweight collection of useful concepts to enable data and composition oriented development with UIKit.
As an agency, working on many different projects with Swift and UIKit brings up new challenges to deliver high quality solutions in reasonable time. It is important to maintain a common sense of code styles and app structure in the development team, but yet it is difficult to merge the collection of knowledge together when the team is split into smaller project groups working on very different topics. JamitFoundation was designed to merge the team knowledge into a reusable core framework which is used in all applications we deliver. It defines a lightweight protocol-oriented concept and structure to write maintainable and reusable user interfaces with UIKit on iOS platforms. All general purpose solutions are combined together in modules and reusable views to improve quality over time and to stop reinventing the wheel. By sharing the framework with the open source community we invite you all to benefit from the usage and share your knowledge with others by contributing to the project.
File / Swift Packages / Add Package Dependency...
.https://github.com/JamitLabs/JamitFoundation.git
press Next
and follow the wizard steps.Add the package dependency to your Package.swift
file.
let package = Package(
// ...
dependencies: [
+ .package(url: "https://github.com/JamitLabs/JamitFoundation.git", .upToNextMajor(from: "1.5.2"))
]
// ...
)
Add the following line to your Cartfile.
# A lightweight collection of useful concepts to enable data and composition oriented development with UIKit.
git "git@github.com:JamitLabs/JamitFoundation.git"
Add the following line to your Podfile.
pod 'JamitFoundation', :git => 'https://github.com/JamitLabs/JamitFoundation.git', :tag => '1.5.2', :inhibit_warnings => true
If you don't want to include all of JamitFoundation, but only specific microframeworks (e.g. PageView), add the following line to your Podfile.
pod 'JamitFoundation/PageView', :git => 'https://github.com/JamitLabs/JamitFoundation.git', :tag => '1.5.2', :inhibit_warnings => true
Every view is instantiated by using the extension function instantiate(bundle:owner:options:)
. This function will automatically check if a nib
file with the same name as the view class
exists and will load it from the nib
file else it will initialize it as usual. By using xib
files we can reduce the code size dramatically and also create layouts with visual feedback in Xcode.
Example:
// If a nib file with the name `MyView` exists then it will be instantiated,
// else the default initializer of `UIView` will be used.
// `-instantiate` returns an instance of type `MyView` by inferring the type.
let myView: MyView = .instantiate()
// `-instantiate` returns an instance of type `MyOtherView`.
let mySecondView = MyOtherView.instantiate()
The StatefulView is the base view which contains a generic mutable state of type Model
. The model contains all semantical data information required to present the view. Every subclass of the StatefulView
is intended to have a mutable state and should only be configured by assigning it's Model
. Subviews inside a StatefulView
should always be private and not accessible from outside. Callbacks should also be passed as closures inside the Model
so that no methods of the view are called from outside. If a custom view is not ment to be subclassed we always define it as final
and only leave it open
if it is used to compose behaviour into other view classes by using generic parameters.
Example:
import JamitFoundation
struct MyCustomContentViewModel: ViewModelProtocol {
let title: String
init(title: String = Self.default.title) {
self.title = title
}
}
extension MyCustomContentViewModel {
static let `default`: Self = .init(title: "")
}
final class MyCustomContentView: StatefulView<MyCustomContentViewModel> {
private lazy var titleLabel: Label = .instantiate()
override func viewDidLoad() {
super.viewDidLoad()
// Perform view and layout setup logic here...
addSubview(titleLabel)
}
override func didChangeModel() {
super.didChangeModel()
// Perform view updates based on model data here...
titleLabel.text = model.title
}
}
The StatefulViewController
has the same concept like StatefulView with the difference that it derives from UIViewController
instead of UIView
.
Example:
import CarouselView
import JamitFoundation
import UIKit
struct CarouselViewControllerModel: ViewModelProtocol {
let title: String
let content: CarouselViewModel
init(
title: String = Self.default.title,
content: CarouselViewModel = self.default.content
) {
self.title = title
self.content = content
}
}
extension CarouselViewControllerModel {
static var `default`: Self = .init(title: "", content: .default)
}
final class CarouselViewController: StatefulViewController<CarouselViewControllerModel> {
private lazy var carouselView: CarouselView<CarouselItemView> = .instantiate()
override func viewDidLoad() {
super.viewDidLoad()
carouselView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(carouselView)
carouselView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
carouselView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
carouselView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
carouselView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
}
override func didChangeModel() {
super.didChangeModel()
title = model.title
carouselView.model = model.content
}
}
Sharing the code between stateful views and table/collection view cells is straightforward by using the ContainerTableViewCell
or ContainerCollectionViewCell
which encapsulate a statefulview into a TableViewCell
or CollectionViewCell
.
Example:
// Only the inheritance is enough to make use of stateful views inside of table views!
final class MyCustomContentTableViewCell: ContainerTableViewCell<MyCustomContentView> {
override func viewDidLoad() {
super.viewDidLoad()
// Perform additional cell initialization code additional to MyCustomContentView logic
}
// It is still possible to make customizations to the behaviour of the cell by overriding its methods
override func prepareForReuse() {
super.prepareForReuse()
// So additional cleanup on reuse...
}
}
// Same is possible with collection view cells
final class MyCustomContentCollectionViewCell: ContainerCollectionViewCell<MyCustomContentView> {}
Cells are registered by using the extension method register(cellOfType:)
. The behaviour of this method is similar to the -instantiate
method and also checks for existing nib
files to register them or fallback to register the class.
Example:
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(cellOfType: MyCustomContentTableViewCell.self)
}
Cells can be dequeued by using the extension method dequeue(cellOfType: for:)
. This function automatically force casts the dequeued cell to the expected type and returns a strongly typed cell.
Example:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeue(cellOfType: MyCustomContentTableViewCell.self, for: indexPath)
cell.model = model.items[indexPath.row]
return cell
}
// There are also additional extensions to dequeue in different ways...
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
return tableView.dequeue(cellOfType: MyCustomContentTableViewCell.self, for: indexPath) { indexPath in
return model.items[indexPath.row]
}
}
We welcome everyone to work with us together delivering helpful tooling to our open source community. Feel free to create an issue to ask questions, give feedback, report bugs or share your new feature ideas. Before creating pull requests please ensure that you have created a related issue ticket.
This project is licensed under the terms of the MIT license. See the LICENSE file.