本文将讨论服务器驱动的UI,使用称为UIComponents的可重用组件的实现,以及创建用于呈现UI组件的通用垂直列表视图。 最后将简要讨论UI组件如何实现不同的目的。
什么是服务器驱动的UI?
这是服务器决定需要在应用程序屏幕上呈现的UI视图的体系结构。
应用程序和服务器之间存在合同。 该合同的基础使服务器可以控制应用程序的UI。
那是什么合同?-服务器定义组件列表。 对于服务器上定义的每个组件,我们在应用程序(UIComponent)中都有一个相应的UI实现。 考虑像Hotstar这样的娱乐应用,其合同定义如下。 左边是服务器中的组件,右边是相应的UI组件。
构建服务器驱动的UI组件
工作中-屏幕上没有预定义的布局,例如情节提要。 而是由一个普通的列表视图组成,根据服务器响应,该列表视图垂直呈现多个不同的视图。 为了使其成为可能,我们必须创建独立的视图,并且可以在整个应用程序中重复使用这些视图。 我们将这些可重用视图称为UIComponent。
合同-对于每个服务器组件,我们都有一个UIComponent。
SwiftUI
Swift是一个UI工具包,可让您以编程的声明性方式设计应用程序屏幕。
struct NotificationView: View {
let notificationMessage: String
var body: some View {
Text(notificationMessage)
}
}
SwiftUI中服务器驱动的UI实施
这是一个三步过程。
- 定义独立的UIComponent。
- 根据API响应构造UIComponent。
- 在屏幕上呈现UIComponents。
1.定义独立的UIComponents
定义独立的UIComponents
输入:首先,要让UIComponent呈现自身,应为其提供数据。
输出:UIComponent定义其UI。 当用于在屏幕内渲染时,它根据提供给它的数据(输入)进行渲染。
UIComponent实现
protocol UIComponent {
var uniqueId: String { get }
func render() -> AnyView
}
所有UI视图都必须符合此UI组件协议。
由于组件是在通用垂直列表中呈现的,因此每个UIComponent必须独立标识。 theuniqueId属性用于实现此目的。
render()是定义组件的UI的位置。 在屏幕上调用此函数将渲染组件。 让我们看一下NotificationComponent。
struct NotificationComponent: UIComponent {
var uniqueId: String
// The data required for rendering is passed as a dependency
let uiModel: NotificationUIModel
// Defines the View for the Component
func render() -> AnyView {
NotificationView(uiModel: uiModel).toAny()
}
}
// Contains the properties required for rendering the Notification View
struct NotificationUIModel {
let header: String
let message: String
let actionText: String
}
// Notification view takes the NotificationUIModel as a dependency
struct NotificationView: View {
let uiModel: NotificationUIModel
var body: some View {
VStack {
Text(uiModel.header)
Text(uiModel.message)
Button(action: {}) {
Text(uiModel.actionText)
}
}
}
}
NotificationUIModel是组件呈现所需的数据。 这是UIComponent的输入。
NotificationView是一个SwiftUI视图,用于定义组件的UI。 它以NotificationUIModel作为依赖项。 当用于在屏幕上呈现时,此视图是UIComponent的输出。
2.根据API响应构造UIComponents
class HomePageController: ObservableObject {
let repository: Repository
@Published var uiComponents: [UIComponent] = []
..
..
func loadPage() {
val response = repository.getHomePageResult()
response.forEach { serverComponent in
let uiComponent = parseToUIComponent(serverComponent)
uiComponents.append(uiComponent)
}
}
}
func parseToUIComponent(serverComponent: ServerComponent) -> UIComponent {
var uiComponent: UIComponent
if serverComponent.type == "NotificationComponent" {
uiComponent = NotificationComponent(serverComponent.data, serverComponent.id)
}
else if serverComponent.type == "GenreListComponent" {
uiComponent = GenreListComponent(serverComponent.data, serverComponent.id)
}
...
...
return uiComponent
}
HomePageController从存储库加载服务器组件,并将它们转换为UIComponents。
uiComponent的属性负责保存UIComponents的列表。 用@Published属性对其进行包装使其可观察。 其值的任何更改都将发布到Observer(View)。 这样可以使View与应用程序状态保持同步。
3.在屏幕上渲染UIComponents
这是最后一部分。 屏幕的唯一责任是渲染UIComponent。 它订阅了可观察到的uiComponents。 每当uiComponents的值更改时,都会通知HomePage,然后更新其UI。 通用ListView用于呈现UIComponent。
struct HomePageView: View {
@ObservedObject var controller: HomePageViewModel
var body: some View {
ScrollView(.vertical) {
VStack {
ForEach(controller.uiComponents, id: \.uniqueId) { uiComponent in
uiComponent.render()
}
}
}
.onAppear(perform: {
self.controller.loadPage()
})
}
}
通用Vstack:使用内部的VStack垂直呈现所有UIComponent。 由于UIComponent是唯一可识别的,因此我们可以使用ForEach构造进行渲染。
由于所有符合UIComponent协议的组件都必须返回公共类型,因此render()函数将返回AnyView。 下面是View的扩展,用于将其转换为AnyView。
extension View {
func toAny() -> AnyView {
return AnyView(self)
}
}
结论
我们看到了如何使用UIComponent来赋予服务器对应用程序UI的控制权。 但是,使用UIComponents可以实现更多目标。
让我们考虑一个没有服务器驱动的用户界面的情况。 UI片段在整个应用程序中经常使用多次。 这导致视图和视图逻辑的重复。 因此,最好将UI分成有意义的,可重复使用的UI组件。
以这种方式拥有它们将使域层/业务层定义和构造UI组件。 此外,业务层可以承担控制UI的责任。