TestsTested | ✗ |
LangLanguage | SwiftSwift |
License | MIT |
ReleasedLast Release | Dec 2017 |
SwiftSwift Version | 4.0.2 |
SPMSupports SPM | ✗ |
Maintained by Thibault Wittemberg.
Depends on: | |
RxSwift | >= 4.0.0 |
RxCocoa | >= 4.0.0 |
Travis CI | |
Frameworks | |
Platform | |
Licence |
Weavy is a navigation framework for iOS applications based on a weaving pattern
This README is a short story of the whole conception process that leaded me to this framework.
Take a look at this wiki page to learn more about Weavy: Weavy in details
Regarding navigation within an iOS application, two choices are available:
The disadvantage of these two solutions:
In your Cartfile:
github "twittemb/Weavy"
In your Podfile:
pod 'Weavy'
In a real world: Weaving involves using a loom to interlace two sets of threads at right angles to each other: the warp which runs longitudinally and the weft that crosses it
This is how I imagine the weaving pattern in a simple application:
How do I read this ?
Each one of these wefts will be triggered either because of user actions or because of backend state changes.
The crossing between a warp and a weft represented by a colored chip will be a specific navigation action (such as a UIViewController appearance).
As we can see, some wefts are used in multiple warps and their triggering will lead to the display of the same screen, but with different presentation options. Sometimes these screens will popup and sometimes they will be pushed in a navigation stack.
This sketch illustrates how we can factorize UIViewControllers and Storyboards.
Combinaisons of Warps and Wefts describe all the possible navigation patterns within your application.
Each warp defines a clear navigation area (that makes your application divided in well defined parts) in which every weft represent a specific navigation action (push a VC on a stack, pop up a VC, ...).
In the end the knit function has to return an array of stitches. A Stitch helps the Loom in knowing what it will have to deal with for the next navigation steps.
Why an array of stitches ? For instance a UITabbarController is a pattern in which multiple navigations are done at the same time, and we need to tell the Loom something like that is happening.
A Stitch tells the Loom: The next thing you have to handle is this particular Presentable and this particular Weftable. In some cases, the knit function can return an empty array because we know there won't be any further navigation after the one we are doing.
The Demo application shows pretty much every possible cases.
The basic principle of navigation is very simple: it consists of successive views transitions in response to application state changes. These changes are usually due to users interactions, but they can also come from a low level layer of your application. We can imagine that a lose of network session could lead to a signin screen appearance.
We need a way to express these changes. As we saw, a combinaison of a Warp and a Weft represent a navigation action. As well as warps can define your application areas, wefts can define these navigation state changes inside these areas.
Considering this, a Weftable is basically "something" in the application which is able to express a new Weft, and as a consequence a navigation state change, leading to a navigation action.
A Weft can even embed inner values (such as Ids, URLs, ...) that will be propagated to screens presented by the weaving process.
A loom is a just a tool for the developper. Once he has defined the suitable combinations of Warps and Wefts representing the navigation possibilities, the job of the loom is to weave these combinaisons into patterns, according to navigation state changes induced by Weftables.
It is up to the developper to:
The following Warp is used as a Navigation stack.
class WatchedWarp: Warp {
var head: UIViewController {
return self.rootViewController
}
let rootViewController = UINavigationController()
func knit(withWeft weft: Weft) -> [Stitch] {
guard let weft = weft as? AppWeft else { return Stitch.emptyStitches }
switch weft {
case .movieList:
return navigateToMovieListScreen()
case .moviePicked(let movieId):
return navigateToMovieDetailScreen(with: movieId)
case .castPicked(let castId):
return navigateToCastDetailScreen(with: castId)
default:
return Stitch.emptyStitches
}
}
private func navigateToMovieListScreen () -> [Stitch] {
let viewController = WatchedViewController.instantiate()
viewController.title = "Watched"
self.rootViewController.pushViewController(viewController, animated: true)
return [Stitch(nextPresentable: viewController, nextWeftable: viewController)]
}
private func navigateToMovieDetailScreen (with movieId: Int) -> [Stitch] {
let viewController = MovieDetailViewController.instantiate()
self.rootViewController.pushViewController(viewController, animated: true)
return [Stitch(nextPresentable: viewController, nextWeftable: viewController)]
}
private func navigateToCastDetailScreen (with castId: Int) -> [Stitch] {
let viewController = CastDetailViewController.instantiate()
self.rootViewController.pushViewController(viewController, animated: true)
return [Stitch(nextPresentable: viewController, nextWeftable: viewController)]
}
}
As Weft are seen like some states spread across the application, it seems pretty obvious to use an enum to declare them
enum AppWeft: Weft {
case apiKey
case movieList
case moviePicked (withId: Int)
case castPicked (withId: Int)
case settings
}
In theory a Weftable, as it is a protocol, can be anything (a UIViewController for instance) by I suggest to isolate that behavior in a ViewModel or so.
For simple cases (for instance when we only need to bootstrap a Warp with a first Weft and don't want to code a basic Weftable for that), Weavy provides a SingleWeftable class.
class WishlistViewModel: Weftable {
init() {
self.weftSubject.onNext(AppWeft.movieList)
}
@objc func settings () {
self.weftSubject.onNext(AppWeft.settings)
}
}
The weaving process will be bootstrapped in the AppDelegate.
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
var loom = Loom()
let mainWarp = MainWarp()
func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
guard let window = self.window else { return false }
Warps.whenReady(warp: mainWarp, block: { [unowned window] (head) in
window.rootViewController = head
})
loom.weave(fromWarp: mainWarp, andWeftable: SingleWeftable(withInitialWeft: DemoWeft.apiKey))
return true
}
}
As a bonus, the Loom offers a Rx extension that allows you to track the weaving steps (Loom.rx.willKnit and Loom.rx.didKnit).
A demo application is provided to illustrate the core mechanisms. Pretty much every kind of navigation is addressed. The app consists of:
Weavy relies on: