186 lines
6.2 KiB
Markdown
186 lines
6.2 KiB
Markdown
# Create a Backend
|
|
|
|
Learn how to implement a backend.
|
|
|
|
## Overview
|
|
|
|
In this tutorial, [TermKitBackend](https://github.com/david-swift/TermKitBackend) will be used as a sample backend to explain the elements of a backend.
|
|
General information can be found in the <doc:Backends> article.
|
|
|
|
## Package Manifest
|
|
|
|
Set up a new Swift Package (`swift package init`).
|
|
Add the _Meta_ package as well as other dependencies if required to the dependencies section in the manifest.
|
|
|
|
```swift
|
|
let package = Package(
|
|
name: "TermKitBackend",
|
|
platforms: [.macOS(.v10_15), .iOS(.v13), .tvOS(.v13), .watchOS(.v6), .macCatalyst(.v13)],
|
|
products: [
|
|
.library(
|
|
name: "TermKitBackend",
|
|
targets: ["TermKitBackend"]
|
|
)
|
|
],
|
|
dependencies: [
|
|
.package(url: "https://github.com/AparokshaUI/Meta", from: "0.1.0"),
|
|
.package(url: "https://github.com/david-swift/TermKit", branch: "main")
|
|
],
|
|
targets: [
|
|
.target(
|
|
name: "TermKitBackend",
|
|
dependencies: ["TermKit", "Meta"]
|
|
)
|
|
]
|
|
)
|
|
```
|
|
|
|
## Backend-Specific Protocols
|
|
|
|
As mentioned in <doc:Backends>, the backend has to define a backend-specific scene element type.
|
|
Often, it is sensible to define a widget type for regular views.
|
|
|
|
```swift
|
|
import Meta
|
|
|
|
public protocol TermKitSceneElement: SceneElement { }
|
|
public protocol TermKitWidget: Widget { }
|
|
```
|
|
|
|
## The Wrapper Widget
|
|
|
|
In this section, the widget type for regular views will be extended so that it can be used for rendering.
|
|
|
|
With _Meta_, arrays of ``AnyView`` have to be able to be converted into a single widget.
|
|
This allows definitions such as the following one:
|
|
|
|
```swift
|
|
Window {
|
|
Label("Hello")
|
|
Label("World")
|
|
}
|
|
```
|
|
|
|
Create a widget which arranges the child views next to each other (on most platforms, doing this vertically makes most sense).
|
|
It should conform to the platform-specific widget type as well as ``Wrapper``.
|
|
Read the comments for general information about creating widgets.
|
|
|
|
```swift
|
|
import Meta
|
|
import TermKit
|
|
|
|
public struct VStack: Wrapper, TermKitWidget {
|
|
|
|
var content: Body
|
|
|
|
public init(@ViewBuilder content: @escaping () -> Body) { // Use functions and mark them with the result builder to allow the domain-specific language to be used
|
|
self.content = content()
|
|
}
|
|
|
|
public func container<Data>(
|
|
data: WidgetData,
|
|
type: Data.Type
|
|
) -> ViewStorage where Data: ViewRenderData {
|
|
let storages = content.storages(data: data, type: type) // Get the storages of child views
|
|
if storages.count == 1 {
|
|
return .init(storages[0].pointer, content: [.mainContent: storages])
|
|
}
|
|
let view = View()
|
|
for (index, storage) in storages.enumerated() {
|
|
if let pointer = storage.pointer as? TermKit.View {
|
|
view.addSubview(pointer)
|
|
if let previous = (storages[safe: index - 1]?.pointer as? TermKit.View) { // The pointer should be a TermKit view
|
|
pointer.y = .bottom(of: previous)
|
|
}
|
|
}
|
|
}
|
|
return .init(view, content: [.mainContent: storages]) // Save storages of child views in the parent's storage for view updates
|
|
}
|
|
|
|
public func update<Data>(
|
|
_ storage: ViewStorage,
|
|
data: WidgetData,
|
|
updateProperties: Bool,
|
|
type: Data.Type
|
|
) where Data: ViewRenderData {
|
|
guard let storages = storage.content[.mainContent] else {
|
|
return
|
|
}
|
|
content.update(storages, data: data, updateProperties: updateProperties, type: type) // Update the storages of child views
|
|
}
|
|
|
|
}
|
|
|
|
```
|
|
|
|
### Correct Updating
|
|
|
|
Note that the type of the ``ViewStorage/pointer`` differs from backend to backend.
|
|
It is a reference to the widget in the original UI framework.
|
|
|
|
In the update method, update properties of a widget (such as a button's label) only when the `updateProperties` parameter is `true`.
|
|
It indicates that a state variable (see <doc:StateConcept>) of an ancestor view has been updated.
|
|
If state doesn't change, it is impossible for the UI to change.
|
|
However, consider the following exceptions:
|
|
|
|
- _Always_ update view content (using ``AnyView/updateStorage(_:data:updateProperties:type:)`` or ``Swift/Array/storages(data:type:)``). Child views may contain own state.
|
|
- _Always_ update closures (such as the action of a button widget). They may contain reference to state which is updated whenever a view update takes place.
|
|
- _Always_ update bindings. As one can see when looking at ``Binding/init(get:set:)``, they contain two closures which, in most cases, contain a reference to state.
|
|
|
|
### The Render Data Type
|
|
|
|
Now, define a view render data type for the main views.
|
|
|
|
```swift
|
|
public enum MainViewType: ViewRenderData {
|
|
|
|
public typealias WidgetType = TermKitWidget
|
|
public typealias WrapperType = VStack
|
|
|
|
}
|
|
```
|
|
|
|
It is possible to have multiple view render data types in one backend for different situations.
|
|
As an example, you could add another type for menus.
|
|
|
|
## The App Storage
|
|
|
|
An app storage object in the app definition determines which backend to use for rendering.
|
|
Therefore, it must contain information about the scene element.
|
|
|
|
Additionally, the function for executing the app is defined on the object, allowing you to put the setup of the UI into the correct context.
|
|
The quit funtion should terminate the app.
|
|
|
|
```swift
|
|
@_exported import Meta // Export the Meta package
|
|
import TermKit
|
|
|
|
public class TermKitApp: AppStorage {
|
|
|
|
public typealias SceneElementType = TermKitSceneElement
|
|
|
|
public var storage: StandardAppStorage = .init()
|
|
|
|
public required init(id: String) { }
|
|
|
|
public func run(setup: @escaping () -> Void) {
|
|
Application.prepare()
|
|
setup()
|
|
Application.run()
|
|
}
|
|
|
|
public func quit() {
|
|
Application.shutdown()
|
|
}
|
|
|
|
}
|
|
|
|
```
|
|
|
|
## Next Steps
|
|
|
|
Now, you can start implementing scene elements (windows or other "top-level containers"), and views.
|
|
Remember following the instructions for correct updating above for all of the UI element types.
|
|
|
|
If you still have questions, browse code in the [TermKitBackend repository](https://github.com/david-swift/TermKitBackend) or ask a question in the [discussions](https://github.com/AparokshaUI/Meta/discussions). Feedback on the documentation is appreciated!
|