SweetUI adds a little sugar to UIKit. Goals:
- π¬ Thin abstraction on top of UIKit (i.e. syntactic sugar) SweetUI only depends on Foundation, UIKit and Combine. The expected behaviour of UIKit classes remains unchanged. This means that SweetUI features can be adopted incrementally.
- π« Banish storyboards and nibs
SweetUI view and view controller subclasses must be defined entirely in code. By subclassing from SweetUI's
View
andViewController
classes you'll be saved from dealing withinit(coder:)
andinit(nibName:bundle:)
. - π Declarative style for view layout
Declarative style for view layout is easier to more concise than imperative style. A layout created using SweetUI is a standard UIKit layout so it can be changed and updated using standard UIKit methods.
SweetUI provides views layout views (e.g.
HStack
) and modifiers that allow standard UIKit view layouts to be expressed declaratively. In most cases you'll never need to directly deal withNSLayoutConstraint
s. - πΊπ³ Integrates with Combine for state management
Combine enables SweetUI to reduce the amount boilerplate code need to keep views up to date. SweetUI also aims to handle storing
Cancellable
s reducing boiler plate code even further. - π And more!
Other features include:
- Modal, sheet and popover presentation with
async
/await
- Management of
UIViewController
properties (e.g.title
,toolbar
,navigationItem
,tabBarItem
) with Combine FlowController
for managing a sequence of view controllers- Keyboard avoidance
- Declarative interfaces for
UICollectionViewCompositionalLayout
andUICollectionViewCompositionalLayout.list
LayoutView
for creating reusable layout templates- Retain cycle detection for easier debugging
- Modal, sheet and popover presentation with
The follow code shows a simple ViewController
subclass. Note:
- The single
init
(neitherinit(nibName:bundle:)
orinit(coder:)
are required) - The view is declared inline and in a declarative style.
import UIKit
import SweetUI
final class SimpleExampleViewController: ViewController {
@Published var name: String
lazy var rootView = ZStack(alignment: .center) {
VStack(alignment: .center) {
UILabel()
.font(.largeTitle)
.text("Hello \(name)!")
UILabel()
.font(.subheadline)
.text("Welcome to SweetUI")
}
}
init(name: String) {
self.name = name
super.init()
}
}
The Demo app contains more examples of what's possible with SweetUI.
- Delete Main.Storyboard from the project
- Navigate to Project settings -> The target for the app -> Build Settings
- Remove the row 'UIKit Main Storyboard File Base Name'
- In Info.plist navigate to 'Application Scene Manifest -> Scene Configuration -> Application Session Role -> Item 0' and delete the row 'Storyboard Name'
- In SceneDelegate.swift, replace the implementation of
scene(_ scene:willConnectTo:options:)
with:
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
guard let scene = scene as? UIWindowScene else {
preconditionFailure("Unexpected scene type \(Self.self) can only connect to a UIWindowScene.")
}
let window = UIWindow(windowScene: scene)
self.window = window
window.windowScene = scene
window.rootViewController = RootViewController() // where RootViewController is the app's initial viewController.
window.makeKeyAndVisible()
}
The reccommend way to use declarative view layout is to subclass one of the provided abstract classes: View
, Control
, CollectionViewCell
and CollectionReusableView
.
If it is not possible to subclass from one of these classes then there are two alternatives approaches:
- Use composition. E.G. create a new view subclass and add an instance as a subview to the existing view.
- Make the view conform to
ViewBodyProvider
.ViewBodyProvider
requires the conforming class to befinal
andinitializeBody()
to be called at the end of the designatedinit
, E.G.:
final class CustomControl: UIControl, ViewBodyProvider {
let body = UILabel()
.text("Hiya!")
init() {
super.init(frame: .zero)
initializeBodyHosting()
}
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
- Create a method on the root object
- Communicate the data via a Combine Publisher