From fadf4f1889b422e025d0dac9048780e031e2fc48 Mon Sep 17 00:00:00 2001 From: Lingxi_Li <36816148+lilingxi01@users.noreply.github.com> Date: Mon, 21 Jun 2021 17:10:49 -0400 Subject: [PATCH 01/37] [DVStack] Initial transmission from old repo --- .../View/Stack/DVStack.Container.swift | 31 ++++ .../View/Stack/DVStack.Element.swift | 57 ++++++++ .../Frameworks/View/Stack/DVStack.Main.swift | 138 ++++++++++++++++++ 3 files changed, 226 insertions(+) create mode 100644 Sources/DefinedElements/Frameworks/View/Stack/DVStack.Container.swift create mode 100644 Sources/DefinedElements/Frameworks/View/Stack/DVStack.Element.swift create mode 100644 Sources/DefinedElements/Frameworks/View/Stack/DVStack.Main.swift diff --git a/Sources/DefinedElements/Frameworks/View/Stack/DVStack.Container.swift b/Sources/DefinedElements/Frameworks/View/Stack/DVStack.Container.swift new file mode 100644 index 0000000..0ab065e --- /dev/null +++ b/Sources/DefinedElements/Frameworks/View/Stack/DVStack.Container.swift @@ -0,0 +1,31 @@ +import Foundation + +/// +struct DefinedViewStackContainer { + /// + var stack = [DefinedViewStackElement]() + + /// + func getStack() -> [DefinedViewStackElement] { + return stack + } + + /// + mutating func push(_ target: DefinedViewStackElement) { + guard stack.firstIndex(of: target) == nil else { + // 不同层级间重复出现的Page + return + } + stack.append(target) + } + + /// + mutating func pop() { + _ = stack.popLast() + } + + /// + mutating func removeAll() { + stack.removeAll() + } +} diff --git a/Sources/DefinedElements/Frameworks/View/Stack/DVStack.Element.swift b/Sources/DefinedElements/Frameworks/View/Stack/DVStack.Element.swift new file mode 100644 index 0000000..9a38d22 --- /dev/null +++ b/Sources/DefinedElements/Frameworks/View/Stack/DVStack.Element.swift @@ -0,0 +1,57 @@ +import Foundation +import SwiftUI + +/// 该结构服务于`DefinedViewStack` < 内部组件 > +struct DefinedViewStackElement : Identifiable, Equatable { + /// + static var constantId: Int = 0 + + /// Element 标签 + let id: String + + /// Element 内容 + let content: AnyView + + /// Element 状态栏设定(默认值) + /// + /// - Note: 状态栏设定的变动在ViewManager中而非此处,该值仅用于页面初始化的时候 + var statusBarStyle: UIStatusBarStyle = .darkContent + + /// 构造器 - AnyView + /// + /// - Parameter view: 给定视图 + init(_ view: AnyView) { + self.id = "constant-\(DefinedPageElement.constantId)" + self.content = view + + DefinedPageElement.constantId += 1 + } + + /// 构造器 - DefinedPage + /// + /// - Parameter view: 给定视图 + init(_ view: Page) where Page: DefinedPage { + self.id = "constant-\(DefinedPageElement.constantId)" + self.content = AnyView(view) + self.statusBarStyle = view.statusBarStyle + + DefinedPageElement.constantId += 1 + } + + /// 构造器 - DefinedPage + Tag + /// + /// - Parameters: + /// - tag: 页面标签(用于只出现一次的页面,防止页面被先后推送至不同层次或反复使用) + /// - view: 给定视图 + init(_ tag: String, _ view: Page) where Page: DefinedPage { + self.id = tag + self.content = AnyView(view) + self.statusBarStyle = view.statusBarStyle + } + + /// 用于相同比较 < 内部函数 > + static func == (lhs: Self, rhs: Self) -> Bool { + return lhs.id == rhs.id + } +} + diff --git a/Sources/DefinedElements/Frameworks/View/Stack/DVStack.Main.swift b/Sources/DefinedElements/Frameworks/View/Stack/DVStack.Main.swift new file mode 100644 index 0000000..e9ee8f6 --- /dev/null +++ b/Sources/DefinedElements/Frameworks/View/Stack/DVStack.Main.swift @@ -0,0 +1,138 @@ +import Foundation +import SwiftUI + +/// DefinedViewStack - 视图堆栈 @ DefinedElements +/// +/// - TODO: Be able to use Stack multiple times (means multiple universal managers and controllers) +public struct DefinedViewStack : DefinedView { + /// 页面堆栈管理器 < 环境变量 > + /// + /// 用于进行页面堆栈的管理,以及实现页面堆栈的交互动效 + @ObservedObject var manager: DefinedViewManager = DefinedViewManager.get() + + /// 视图空间 < 内部变量 > + @Namespace private var space + + /// 默认页面进出动效 < 外部常量 > + /// + /// 从右侧滑入,从右侧滑出。下层View通过offset实现位移而非transition。 + public static var defaultTransition: AnyTransition { + .move(edge: .trailing) + } + + /// DefinedViewStack 构造器 - View + /// + /// - Parameter start: 底层视图 + public init(from start: Content) where Content: View { + if self.manager.views.count > 0 { + self.manager.renew() + } + self.manager.viewStack.push(DefinedPageElement(AnyView(start))) + } + + /// DefinedViewStack 构造器 - AnyView + /// + /// - Parameter start: 底层视图 + public init(from start: AnyView) { + if self.manager.views.count > 0 { + self.manager.renew() + } + self.manager.viewStack.push(DefinedPageElement(start)) + } + + /// DefinedViewStack 构造器 - DefinedPage < 推荐构造器 > + /// + /// - Parameter start: 底层页面 + public init(from start: Page) where Page: DefinedPage { + if self.manager.views.count > 0 { + self.manager.renew() + } + self.manager.viewStack.push(DefinedPageElement(start)) + } + + // MARK: - Body + + /// + public var body: some View { + GeometryReader { proxy in + ZStack(alignment: .center) { // MARK: View Part + if self.manager.views.count >= 1 { + ForEach(0...self.manager.views.count-1, id: \.self) { i in + self.manager.views[i].content + .offset(x: i == self.manager.views.count - 1 || i == self.manager.views.count - 2 ? self.manager.offsets[i] : 0) + .matchedGeometryEffect(id: self.manager.views[i].id, in: self.space) + .overlay( + DefinedContent(.overlay) { + if i == self.manager.views.count - 2 { + Color.black.opacity(0.1) + .edgesIgnoringSafeArea(.all) + .transition(.opacity) + .allowsHitTesting(false) + } else { + EmptyView() + } + } + ) + .transition(i == 0 ? .opacity : .move(edge: .trailing)) + .zIndex(Double(i)) + .hidden(when_to_show: self.manager.onAnimated && i >= self.manager.views.count - 2) + } + } else { + EmptyView() + } + } + .overlay( // MARK: Drag Part + DefinedContent(.overlay, alignment: .leading) { + if self.manager.views.count > 1 { + Color.clear + .frame(width: 22) + .definedFullSize(.vertical, priorAlign: .leading) + .background(Color.blue.opacity(0)) + .contentShape(Rectangle()) + .simultaneousGesture( + LongPressGesture(minimumDuration: 0.01) + .onEnded({ gesture in + // do nothing + }), + including: .all + ) + .simultaneousGesture( + DragGesture(coordinateSpace: .local) + .onChanged({ gesture in + withAnimation(.easeInOut(duration: 0.12)) { + self.manager.offsets[self.manager.views.count - 1] = gesture.location.x / 1.2 + self.manager.offsets[self.manager.views.count - 2] = -proxy.size.width / 4 + gesture.location.x / 4.8 + } + }) + .onEnded({ gesture in + if gesture.predictedEndTranslation.width > 150 { + // TODO: + self.manager.pop() + } else { + withAnimation(.easeInOut(duration: 0.15)) { + self.manager.offsets[self.manager.views.count - 1] = 0 + self.manager.offsets[self.manager.views.count - 2] = -proxy.size.width / 4 + } + } + }), + including: .all + ) + } + } + .definedSize(width: .full, height: .full, alignment: .leading) + ) + // .mask(Color.black.opacity(self.manager.overallOpacity).animation(.easeInOut(duration: 0.30)).edgesIgnoringSafeArea(.all)) + .disabled(self.manager.onRootAnimated) + .onAppear { + self.manager.width = proxy.size.width + self.manager.height = proxy.size.height + self.manager.safeAreaInsets = proxy.safeAreaInsets + } + } + } + + /// + public func setStatusBarStyle(_ style: UIStatusBarStyle) { + + } +} From 421846537f25d9ec340c91b43690c3d1ff03c109 Mon Sep 17 00:00:00 2001 From: Lingxi_Li <36816148+lilingxi01@users.noreply.github.com> Date: Mon, 21 Jun 2021 17:10:58 -0400 Subject: [PATCH 02/37] [DVManager] Initial transmission from old repo --- .../View/Manager/DVManager.Main.swift | 103 ++++++++++++++++++ 1 file changed, 103 insertions(+) create mode 100644 Sources/DefinedElements/Frameworks/View/Manager/DVManager.Main.swift diff --git a/Sources/DefinedElements/Frameworks/View/Manager/DVManager.Main.swift b/Sources/DefinedElements/Frameworks/View/Manager/DVManager.Main.swift new file mode 100644 index 0000000..e798ecc --- /dev/null +++ b/Sources/DefinedElements/Frameworks/View/Manager/DVManager.Main.swift @@ -0,0 +1,103 @@ +import Foundation +import SwiftUI + +/// +class DefinedViewManager : ObservableObject { + /// ViewManager 实体 < 内部变量 > + /// + /// - Warning: 需要禁止用户创建多个ViewStack!不符合功能设计,会导致层级错误! + private static var instance: DefinedViewManager = DefinedViewManager() + + /// + @Published var onAnimated: Bool = true // TODO: 下层View仅在侧滑时出现 + + /// + @Published var onRootAnimated: Bool = false + + /// 视图存储器 + @Published var views: [DefinedPageElement] = [] + + /// 状态栏样式存储器 + @Published var statusBarStyles: [UIStatusBarStyle] = [] + + /// 偏移量存储器 + @Published var offsets: [CGFloat] = [0] + + /// 页面宽度 + @Published var width: CGFloat = 0 + + /// 页面高度 + @Published var height: CGFloat = 0 + + /// + @Published var safeAreaInsets: EdgeInsets = EdgeInsets.init() + + /// 页面宽度 + @Published var overallOpacity: Double = 1.0 + + /// 构造器 + init() { + // do nothing + } + + /// + fileprivate var viewStack = DefinedViewContainerStack() { + didSet { + views = viewStack.getStack() + } + } + + /// + func push(_ target: Page) where Page: DefinedPage { + self.offsets.append(0) + withAnimation(.easeInOut(duration: 0.30)) { + self.viewStack.push(DefinedPageElement(target)) + if self.viewStack.stack.count > 1 { + self.offsets[self.offsets.count - 2] = -self.width / 4 + } + } + + } + + /// + func pop() { + if self.viewStack.stack.count > 1 { + withAnimation(.easeInOut(duration: 0.25)) { + if (self.offsets[self.offsets.count - 2] == 0) { + self.viewStack.pop() + } else { + withAnimation(.easeInOut(duration: 0.26)) { + self.viewStack.pop() + } + } + self.offsets[self.offsets.count - 2] = 0 + } + } else { + withAnimation(.easeInOut(duration: 0.26)) { + self.viewStack.pop() + } + } + self.offsets.removeLast() + } + + /// + func jump(_ target: Page) where Page: DefinedPage { + withAnimation(.easeInOut(duration: 0.50)) { + self.renew() + self.viewStack.push(DefinedPageElement(target)) + } + } + + /// + func renew() { + self.viewStack.removeAll() + self.offsets = [0] + } + + // MARK: Outside Functions + + /// + static func get() -> DefinedViewManager { + return Self.instance + } +} From 4971f43c6ba5312553caff7d629734f4035c14cd Mon Sep 17 00:00:00 2001 From: Lingxi_Li <36816148+lilingxi01@users.noreply.github.com> Date: Tue, 22 Jun 2021 21:57:17 -0400 Subject: [PATCH 03/37] [DVStack] Add the docker. --- .../App/iOS/DApp-iOS.StatusBar.swift | 58 +++++++++++++++++++ .../View/Stack/DVStack.Docker.swift | 54 +++++++++++++++++ 2 files changed, 112 insertions(+) create mode 100644 Sources/DefinedElements/Frameworks/App/iOS/DApp-iOS.StatusBar.swift create mode 100644 Sources/DefinedElements/Frameworks/View/Stack/DVStack.Docker.swift diff --git a/Sources/DefinedElements/Frameworks/App/iOS/DApp-iOS.StatusBar.swift b/Sources/DefinedElements/Frameworks/App/iOS/DApp-iOS.StatusBar.swift new file mode 100644 index 0000000..2ada64c --- /dev/null +++ b/Sources/DefinedElements/Frameworks/App/iOS/DApp-iOS.StatusBar.swift @@ -0,0 +1,58 @@ +import Foundation +import SwiftUI + +/// +class DefinedStatusBarController: UIHostingController { + var statusBarStyle: UIStatusBarStyle = .darkContent + var previousStatusBarStyle: UIStatusBarStyle = .darkContent + + /// + override var preferredStatusBarStyle: UIStatusBarStyle { + return statusBarStyle + } + + /// + func changeStatusBarStyle(_ style: UIStatusBarStyle) { + self.previousStatusBarStyle = self.statusBarStyle + self.statusBarStyle = style + self.setNeedsStatusBarAppearanceUpdate() + } + + /// + func getCurrStatusBarStyle() -> UIStatusBarStyle { + return statusBarStyle + } + + /// + func changeToLastStatusBarStyle() { + changeStatusBarStyle(self.previousStatusBarStyle) + } +} + +/// +public extension UIApplication { + /// + class func setController(rootView: Content) where Content : View { + UIApplication.shared.windows.first?.rootViewController?.beginAppearanceTransition(false, animated: false) + UIApplication.shared.windows.first?.rootViewController = DefinedStatusBarController(rootView: AnyView(rootView)) + } + + /// + class func setLastStatusBarStyle() { + if let vc = UIApplication.getKeyWindow()?.rootViewController as? DefinedStatusBarController { + vc.changeToLastStatusBarStyle() + } + } + + /// + class func setStatusBarStyle(_ style: UIStatusBarStyle) { + if let vc = UIApplication.getKeyWindow()?.rootViewController as? DefinedStatusBarController { + vc.changeStatusBarStyle(style) + } + } + + /// + private class func getKeyWindow() -> UIWindow? { + return UIApplication.shared.windows.first{ $0.isKeyWindow } + } +} diff --git a/Sources/DefinedElements/Frameworks/View/Stack/DVStack.Docker.swift b/Sources/DefinedElements/Frameworks/View/Stack/DVStack.Docker.swift new file mode 100644 index 0000000..05349da --- /dev/null +++ b/Sources/DefinedElements/Frameworks/View/Stack/DVStack.Docker.swift @@ -0,0 +1,54 @@ +#if os(iOS) + +import Foundation + +/// +public struct DefinedViewStackDocker { + private var manager: DefinedViewManager? + + public init() { + self.manager = nil + } + + init(manager: DefinedViewManager) { + self.manager = manager + } + + /// + public func link(to target: Page) where Page: DefinedPage { + if (self.manager != nil) { + manager!.push(target) + } else { + // + } + } + + /// + public func jump(to target: Page) where Page: DefinedPage { + if (self.manager != nil) { + manager!.jump(target) + } else { + // + } + } + + /// + public func swap(to target: Page) where Page: DefinedPage { + if (self.manager != nil) { + // + } else { + // + } + } + + /// + public func back() { + if (self.manager != nil) { + manager!.pop() + } else { + // + } + } +} + +#endif From e134741f968a3681978215ae61a948b0bc4728ac Mon Sep 17 00:00:00 2001 From: Lingxi_Li <36816148+lilingxi01@users.noreply.github.com> Date: Thu, 24 Jun 2021 16:08:19 -0400 Subject: [PATCH 04/37] [DVStack] Add iOS-only constrain on `DefinedViewStackContainer` --- .../Frameworks/View/Stack/DVStack.Container.swift | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Sources/DefinedElements/Frameworks/View/Stack/DVStack.Container.swift b/Sources/DefinedElements/Frameworks/View/Stack/DVStack.Container.swift index 0ab065e..1cd2fbe 100644 --- a/Sources/DefinedElements/Frameworks/View/Stack/DVStack.Container.swift +++ b/Sources/DefinedElements/Frameworks/View/Stack/DVStack.Container.swift @@ -1,6 +1,8 @@ +#if os(iOS) + import Foundation -/// +/// [DE Internal] struct DefinedViewStackContainer { /// var stack = [DefinedViewStackElement]() @@ -29,3 +31,5 @@ struct DefinedViewStackContainer { stack.removeAll() } } + +#endif From a51b89f820b0728a77df12822723164baa693a12 Mon Sep 17 00:00:00 2001 From: Lingxi_Li <36816148+lilingxi01@users.noreply.github.com> Date: Mon, 28 Jun 2021 12:22:36 -0400 Subject: [PATCH 05/37] [DPage-iOS] Create Protocol --- .../Page/iOS/DPage-iOS.Protocol.swift | 244 ++++++++++++++++++ 1 file changed, 244 insertions(+) create mode 100644 Sources/DefinedElements/Frameworks/Page/iOS/DPage-iOS.Protocol.swift diff --git a/Sources/DefinedElements/Frameworks/Page/iOS/DPage-iOS.Protocol.swift b/Sources/DefinedElements/Frameworks/Page/iOS/DPage-iOS.Protocol.swift new file mode 100644 index 0000000..591e585 --- /dev/null +++ b/Sources/DefinedElements/Frameworks/Page/iOS/DPage-iOS.Protocol.swift @@ -0,0 +1,244 @@ +#if os(iOS) + +import Foundation +import SwiftUI +import ObjectiveC + + +/// DefinedPage - 页面基础协议 @ DefinedElements +/// +/// 这套页面协议目前来说能够和原生`View`做出区分,能够基于页面框架实现更多专属于页面的功能,以及页面的快速跳转等。 +/// 这都是原生无法实现或者极其繁琐的。 +/// +/// 无法保证渲染效率能抗衡原生,目前暂时没有纯渲染层的优化能力。 +/// +/// - Note: 若您使用`Defined`配套控件,则该协议非常重要。 +/// +/// - Important: `DefinedViewStack`强制依赖于该页面框架! +/// +/// - TODO: StatusBarStyle seemless embed. +public protocol DefinedPage : View, ModifiableStruct { + + // MARK: - Protocol - Main Part + + /// The main controller using to control your current page. + /// + /// This is optional, but highly recommanded because you will lose the control without it. + /// Only NOT implement it when this page is used for an only-one-page stack. + /// + /// - Important: If you do not implement this on your page, you may NOT be able to control the router! + /// Even the short-hand functions like `link(to:)` and `jump(to:)` will all be disabled without controller! + var controller: DefinedPageController { get } + + /// + var id: UUID { get } + + /// + var statusBarStyle: UIStatusBarStyle { get } + + /// + associatedtype Content: View + + /// 页面主体 + @ViewBuilder var main: Content { get } + + /// 在页面加载开始前所执行的事项 + /// + /// - Important: 非强制要求调用 + func beforePageLoading() -> Void + + /// 当页面加载完后所执行的事项 + /// + /// - Important: 非强制要求调用 + func onPageLoaded() -> Void + + /// 当页面结束时所执行的事项 + /// + /// - Important: 非强制要求调用 + func onPageEnded() -> Void +} + +public extension DefinedPage { + var controller: DefinedPageController { + DefinedPageController() + } + + var id: UUID { + controller.id + } +} + +// MARK: - LifeCycle + +/// 核心生命的方法 +public extension DefinedPage { + /// 在页面加载开始前所执行的事项 < 默认方法 > + /// + /// - Important: 非强制要求调用 + func beforePageLoading() -> Void { + // do nothing. + return + } + + /// 当页面加载完后所执行的事项 < 默认方法 > + /// + /// - Important: 非强制要求调用 + func onPageLoaded() -> Void { + // do nothing. + return + } + + /// 当页面结束时所执行的事项 < 默认方法 > + /// + /// - Important: 非强制要求调用 + func onPageEnded() -> Void { + // do nothing. + return + } +} + +// MARK: - StatusBarStyle + +/// StatusBarStyle 默认值 +public extension DefinedPage { + /// 状态栏颜色 < 外部参数 > + /// + /// - Note: 默认为黑色内容 + var statusBarStyle: UIStatusBarStyle { .darkContent } +} + +// MARK: - Link - link() + +/// DefinedLink 子页面跳转功能 +public extension DefinedPage { + /// 纯代码页面跳转 < 前端函数 > + /// + /// 使用时在`DefinedPage`内直接调用即可 + /// + /// link(to: TargetPage()) + /// + /// - Requires: 所有页面必须基于`DefinedPage`框架 + /// + /// - Parameter target: 目标页面 + func link(to target: Page) where Page : DefinedPage { + let pageElement = DefinedViewManager.find(self) + pageElement.link(to: target) + } + + /// 纯代码页面跳转 带延时 < 前端函数 > + /// + /// 使用时在`DefinedPage`内直接调用即可 + /// + /// ``` + /// link(to: TargetPage()) + /// ``` + /// + /// - Requires: 所有页面必须基于`DefinedPage`框架 + /// + /// - Parameters: + /// - target: 目标页面 + /// - delay: 延时(秒) + func link(to target: Page, delay: Double) where Page : DefinedPage { + if delay >= 0.0 { + DispatchQueue.main.asyncAfter(deadline: .now() + delay) { + link(to: target) + } + } + } + + /// 计时器 < 前端函数 > + /// + /// - Parameters: + /// - delay: 延时(秒) + /// - execute: 执行内容 + func timer(delay: Double, execute: @escaping @convention(block) () -> Void) { + DispatchQueue.main.asyncAfter(deadline: .now() + delay) { + execute() + } + } +} + +// MARK: - Link - back() + +/// DefinedLink 子页面返回功能 +extension DefinedPage { + /// 执行返回操作 < 前端函数 > + public func back() { + DefinedViewManager.find(self).back() + } +} + +// MARK: - Scene - jump() + +/// DefinedScene 根页面跳转功能 +public extension DefinedPage { + /// 纯代码根页面跳转 < 前端函数 > + /// + /// - Parameter target: 目标页面 + func jump(to target: Page) where Page : DefinedPage { + DefinedViewManager.find(self).jump(to: target) + } + + /// 纯代码根页面跳转 带延时 < 前端函数 > + /// + /// - Parameters: + /// - target: 目标页面 + /// - delay: 延时(秒) + func jump(to target: Page, delay: Double) where Page : DefinedPage { + if delay >= 0.0 { + DispatchQueue.main.asyncAfter(deadline: .now() + delay) { + jump(to: target) + } + } + } +} + +// MARK: - Docker + +//public extension DefinedPage { +// var viewStackDocker: DefinedViewStackDocker { +// get { +// // by default, create a docker without manager. +// return DefinedViewStackDocker() +// } +// set { +// print("set \(newValue)") +// } +// } +// +// internal func bindPageTo(manager: DefinedViewManager) -> Self { +// var copy = self +// copy.changeViewStackDocker(DefinedViewStackDocker(manager: manager)) +// print("after: \(copy.viewStackDocker)") +// return copy +// } +// +// internal mutating func changeViewStackDocker(_ target: DefinedViewStackDocker) { +// self.viewStackDocker.manager = target.manager +// print("changed: \(self.viewStackDocker)") +// } +//} + +// MARK: - Core - Process + +/// 核心Body的处理 +extension DefinedPage { + /// 根视图 < 内部变量 > + public var body: some View { + beforePageLoading() + return process() + } + + /// [DE Private] + @ViewBuilder private func process() -> some View { + ZStack { + self.main + } + .onAppear(perform: onPageLoaded) + .onDisappear(perform: onPageEnded) + .frame(maxWidth: .infinity, maxHeight: .infinity) + .background(DEColor.bg.light.edgesIgnoringSafeArea(.all)) + } +} + +#endif From 0aa21a72b692792f87ff57f9833e9b64fa665104 Mon Sep 17 00:00:00 2001 From: Lingxi_Li <36816148+lilingxi01@users.noreply.github.com> Date: Mon, 28 Jun 2021 12:23:57 -0400 Subject: [PATCH 06/37] [DPage-iOS] Initial implementation of PageController --- .../Page/iOS/Support/DPage-iOS.Controller.swift | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 Sources/DefinedElements/Frameworks/Page/iOS/Support/DPage-iOS.Controller.swift diff --git a/Sources/DefinedElements/Frameworks/Page/iOS/Support/DPage-iOS.Controller.swift b/Sources/DefinedElements/Frameworks/Page/iOS/Support/DPage-iOS.Controller.swift new file mode 100644 index 0000000..ad02a34 --- /dev/null +++ b/Sources/DefinedElements/Frameworks/Page/iOS/Support/DPage-iOS.Controller.swift @@ -0,0 +1,9 @@ +import Foundation + +public class DefinedPageController { + public let id: UUID = UUID() + + public init() { + // + } +} From 6a5464bd6293ca1716f7550b09a9cc6a9e50e884 Mon Sep 17 00:00:00 2001 From: Lingxi_Li <36816148+lilingxi01@users.noreply.github.com> Date: Mon, 28 Jun 2021 12:24:39 -0400 Subject: [PATCH 07/37] [DVManager] Finish basic implementations on controlling page routers --- .../View/Manager/DVManager.Main.swift | 110 ++++++------------ 1 file changed, 34 insertions(+), 76 deletions(-) diff --git a/Sources/DefinedElements/Frameworks/View/Manager/DVManager.Main.swift b/Sources/DefinedElements/Frameworks/View/Manager/DVManager.Main.swift index e798ecc..83e046b 100644 --- a/Sources/DefinedElements/Frameworks/View/Manager/DVManager.Main.swift +++ b/Sources/DefinedElements/Frameworks/View/Manager/DVManager.Main.swift @@ -1,103 +1,61 @@ +#if os(iOS) + import Foundation import SwiftUI +/// [DE Internal] A core manager controlling the view stack. /// -class DefinedViewManager : ObservableObject { - /// ViewManager 实体 < 内部变量 > - /// - /// - Warning: 需要禁止用户创建多个ViewStack!不符合功能设计,会导致层级错误! - private static var instance: DefinedViewManager = DefinedViewManager() +/// Should be attached to a `DefinedViewStack` for proper using. +class DefinedViewManager : DefinedPotentialWarning { + var name: String = "DefinedViewManager" /// - @Published var onAnimated: Bool = true // TODO: 下层View仅在侧滑时出现 + static var instance = DefinedViewManager() /// - @Published var onRootAnimated: Bool = false - - /// 视图存储器 - @Published var views: [DefinedPageElement] = [] - - /// 状态栏样式存储器 - @Published var statusBarStyles: [UIStatusBarStyle] = [] - - /// 偏移量存储器 - @Published var offsets: [CGFloat] = [0] - - /// 页面宽度 - @Published var width: CGFloat = 0 - - /// 页面高度 - @Published var height: CGFloat = 0 - - /// - @Published var safeAreaInsets: EdgeInsets = EdgeInsets.init() - - /// 页面宽度 - @Published var overallOpacity: Double = 1.0 - - /// 构造器 - init() { - // do nothing - } + var hierarchy: [DefinedViewManagerRootElement] = [] /// - fileprivate var viewStack = DefinedViewContainerStack() { - didSet { - views = viewStack.getStack() - } - } + var pageMap: [UUID: DefinedViewManagerElement] = [:] /// - func push(_ target: Page) where Page: DefinedPage { - self.offsets.append(0) - withAnimation(.easeInOut(duration: 0.30)) { - self.viewStack.push(DefinedPageElement(target)) - if self.viewStack.stack.count > 1 { - self.offsets[self.offsets.count - 2] = -self.width / 4 - } + public static func register(manager stackManager: DefinedViewStackManager, parent: DefinedViewManagerRootElement) { + let rootElement = DefinedViewManagerRootElement(docker: DefinedViewStackDocker(manager: stackManager), + parent: parent) + + if (stackManager.elements.first == nil) { + // ERROR: no initial page! + DefinedWarning.send(from: "DVManager", "the manager (stack manager) does not have any page inside!") + return } + let pageElement = DefinedViewManager.registerPage(id: stackManager.elements.first!.id, parent: rootElement) + + rootElement.hierarchy.append(pageElement) + + DefinedViewManager.instance.hierarchy.append(rootElement) } /// - func pop() { - if self.viewStack.stack.count > 1 { - withAnimation(.easeInOut(duration: 0.25)) { - if (self.offsets[self.offsets.count - 2] == 0) { - self.viewStack.pop() - } else { - withAnimation(.easeInOut(duration: 0.26)) { - self.viewStack.pop() - } - } - self.offsets[self.offsets.count - 2] = 0 - } - } else { - withAnimation(.easeInOut(duration: 0.26)) { - self.viewStack.pop() - } - } - self.offsets.removeLast() + public static func find(_ id: UUID) -> DefinedViewManagerElement { + return DefinedViewManager.instance.pageMap[id] ?? .dummy } /// - func jump(_ target: Page) where Page: DefinedPage { - withAnimation(.easeInOut(duration: 0.50)) { - self.renew() - self.viewStack.push(DefinedPageElement(target)) - } + public static func find(_ page: Page) -> DefinedViewManagerElement where Page: DefinedPage { + return DefinedViewManager.instance.pageMap[page.id] ?? .dummy } /// - func renew() { - self.viewStack.removeAll() - self.offsets = [0] + internal static func registerPage(id: UUID, parent: DefinedViewManagerRootElement) -> DefinedViewManagerElement { + let pageElement = DefinedViewManagerElement(id: id, parent: parent) + DefinedViewManager.instance.pageMap[id] = pageElement + return pageElement } - // MARK: Outside Functions - - /// - static func get() -> DefinedViewManager { - return Self.instance + internal static func unregisterPage(id: UUID) { + DefinedViewManager.instance.pageMap.removeValue(forKey: id) } } + +#endif From c18983e5930277bb61b7f327842cdf134390f466 Mon Sep 17 00:00:00 2001 From: Lingxi_Li <36816148+lilingxi01@users.noreply.github.com> Date: Mon, 28 Jun 2021 12:25:11 -0400 Subject: [PATCH 08/37] [DVManager] Initial implementation of Manager Elements --- .../Manager/Support/DVManager.Element.swift | 129 ++++++++++++++++++ 1 file changed, 129 insertions(+) create mode 100644 Sources/DefinedElements/Frameworks/View/Manager/Support/DVManager.Element.swift diff --git a/Sources/DefinedElements/Frameworks/View/Manager/Support/DVManager.Element.swift b/Sources/DefinedElements/Frameworks/View/Manager/Support/DVManager.Element.swift new file mode 100644 index 0000000..b7186dc --- /dev/null +++ b/Sources/DefinedElements/Frameworks/View/Manager/Support/DVManager.Element.swift @@ -0,0 +1,129 @@ +#if os(iOS) + +import Foundation + +internal class DefinedViewManagerElement : Equatable { + var id: UUID + var parent: DefinedViewManagerRootElement + + init(id: UUID, parent: DefinedViewManagerRootElement) { + self.id = id + self.parent = parent + } + + func link(to target: Page) where Page: DefinedPage { + parent.link(to: target) + } + + func jump(to target: Page) where Page: DefinedPage { + parent.jump(to: target) + } + + func swap(with target: Page) where Page: DefinedPage { + parent.swap(with: target) + } + + func back() { + parent.back() + } + + static func == (lhs: DefinedViewManagerElement, rhs: DefinedViewManagerElement) -> Bool { + return lhs.id == rhs.id + } +} + +extension DefinedViewManagerElement { + /// + static let dummy: DefinedViewManagerElement = DefinedViewManagerDummyElement() +} + +internal class DefinedViewManagerDummyElement : DefinedViewManagerElement { + init() { + super.init(id: UUID(), parent: .dummy) + } + + override func link(to target: Page) where Page: DefinedPage { + // do nothing. + } +} + +internal class DefinedViewManagerRootElement : DefinedPotentialWarning { + var name: String = "DVManagerRootElement" + + var hierarchy: [DefinedViewManagerElement] = [] + + var docker: DefinedViewStackDocker? = nil + var parent: DefinedViewManagerRootElement? = nil + + fileprivate init(docker: DefinedViewStackDocker? = nil, parent: DefinedViewManagerRootElement? = nil) { + self.docker = docker + self.parent = parent + } + + init(docker: DefinedViewStackDocker, parent: DefinedViewManagerRootElement) { + self.docker = docker + self.parent = parent + } + + func link(to target: Page) where Page: DefinedPage { + if (self.docker == nil) { + // ERROR + return + } + self.docker!.link(to: target) + + self.hierarchy.append(DefinedViewManager.registerPage(id: target.id, parent: self)) + } + + func jump(to target: Page) where Page: DefinedPage { + if (self.docker == nil) { + // ERROR + return + } + self.docker!.jump(to: target) + } + + func swap(with target: Page) where Page: DefinedPage { + if (self.docker == nil) { + // ERROR + return + } + self.docker!.swap(with: target) + } + + func back() { + if (self.docker == nil) { + // ERROR + warning("docker is null") + return + } + if (hierarchy.count > 1) { + self.docker!.back() + let last = self.hierarchy.removeLast() + DefinedViewManager.unregisterPage(id: last.id) + } else { + // TODO: back the parent page if back-parent enabled. + // TODO: implement back-parent feature. + } + } +} + +extension DefinedViewManagerRootElement { + /// + internal static let base = DefinedViewManagerRootElement() + + /// + fileprivate static let dummy = DefinedViewManagerRootElement() +} + +internal class DefinedViewManagerRoot: DefinedViewManagerRootElement { + init(docker: DefinedViewStackDocker) { + super.init(docker: docker) + } + + override func back() { + // + } +} + +#endif From bb035b33d3fc872f26e8ac904aa581d2df0a5450 Mon Sep 17 00:00:00 2001 From: Lingxi_Li <36816148+lilingxi01@users.noreply.github.com> Date: Mon, 28 Jun 2021 12:25:42 -0400 Subject: [PATCH 09/37] [DVStack] Add potential warning onto `DVStackDocker` --- .../Frameworks/View/Stack/DVStack.Docker.swift | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/Sources/DefinedElements/Frameworks/View/Stack/DVStack.Docker.swift b/Sources/DefinedElements/Frameworks/View/Stack/DVStack.Docker.swift index 05349da..9dae154 100644 --- a/Sources/DefinedElements/Frameworks/View/Stack/DVStack.Docker.swift +++ b/Sources/DefinedElements/Frameworks/View/Stack/DVStack.Docker.swift @@ -3,14 +3,16 @@ import Foundation /// -public struct DefinedViewStackDocker { - private var manager: DefinedViewManager? +public struct DefinedViewStackDocker : DefinedPotentialWarning { + public var name: String = "DVStackDocker" + + internal var manager: DefinedViewStackManager? public init() { self.manager = nil } - init(manager: DefinedViewManager) { + internal init(manager: DefinedViewStackManager) { self.manager = manager } @@ -19,7 +21,7 @@ public struct DefinedViewStackDocker { if (self.manager != nil) { manager!.push(target) } else { - // + print("null docker") } } @@ -33,7 +35,7 @@ public struct DefinedViewStackDocker { } /// - public func swap(to target: Page) where Page: DefinedPage { + public func swap(with target: Page) where Page: DefinedPage { if (self.manager != nil) { // } else { @@ -46,7 +48,7 @@ public struct DefinedViewStackDocker { if (self.manager != nil) { manager!.pop() } else { - // + warning("the manager is null!") } } } From 69dc1341e3e41d3c35cfc20ed10d26e04b694e50 Mon Sep 17 00:00:00 2001 From: Lingxi_Li <36816148+lilingxi01@users.noreply.github.com> Date: Mon, 28 Jun 2021 12:26:27 -0400 Subject: [PATCH 10/37] [DVStack] Improve the constrains and identification on `DefinedViewStackElement`. --- .../View/Stack/DVStack.Element.swift | 46 +++++++------------ 1 file changed, 17 insertions(+), 29 deletions(-) diff --git a/Sources/DefinedElements/Frameworks/View/Stack/DVStack.Element.swift b/Sources/DefinedElements/Frameworks/View/Stack/DVStack.Element.swift index 9a38d22..f2ec1e4 100644 --- a/Sources/DefinedElements/Frameworks/View/Stack/DVStack.Element.swift +++ b/Sources/DefinedElements/Frameworks/View/Stack/DVStack.Element.swift @@ -1,13 +1,20 @@ +#if os(iOS) + import Foundation import SwiftUI /// 该结构服务于`DefinedViewStack` < 内部组件 > +/// +/// - Note: The reason why we do not use generic type for `DefinedPage` is we will not be able to define an array of this. struct DefinedViewStackElement : Identifiable, Equatable { - /// - static var constantId: Int = 0 + /// [Internal] + static var constantLevel: Int = 0 /// Element 标签 - let id: String + let id: UUID + + /// + let level: Int /// Element 内容 let content: AnyView @@ -17,36 +24,16 @@ struct DefinedViewStackElement : Identifiable, Equatable { /// - Note: 状态栏设定的变动在ViewManager中而非此处,该值仅用于页面初始化的时候 var statusBarStyle: UIStatusBarStyle = .darkContent - /// 构造器 - AnyView - /// - /// - Parameter view: 给定视图 - init(_ view: AnyView) { - self.id = "constant-\(DefinedPageElement.constantId)" - self.content = view - - DefinedPageElement.constantId += 1 - } - /// 构造器 - DefinedPage /// /// - Parameter view: 给定视图 - init(_ view: Page) where Page: DefinedPage { - self.id = "constant-\(DefinedPageElement.constantId)" - self.content = AnyView(view) - self.statusBarStyle = view.statusBarStyle + init(_ page: Page) where Page: DefinedPage { + self.id = page.id + self.level = DefinedViewStackElement.constantLevel + self.content = AnyView(page) + self.statusBarStyle = page.statusBarStyle - DefinedPageElement.constantId += 1 - } - - /// 构造器 - DefinedPage + Tag - /// - /// - Parameters: - /// - tag: 页面标签(用于只出现一次的页面,防止页面被先后推送至不同层次或反复使用) - /// - view: 给定视图 - init(_ tag: String, _ view: Page) where Page: DefinedPage { - self.id = tag - self.content = AnyView(view) - self.statusBarStyle = view.statusBarStyle + DefinedViewStackElement.constantLevel += 1 } /// 用于相同比较 < 内部函数 > @@ -55,3 +42,4 @@ struct DefinedViewStackElement : Identifiable, Equatable { } } +#endif From dadd7693ec2dd94df51eb48565cf3300a5ecb4eb Mon Sep 17 00:00:00 2001 From: Lingxi_Li <36816148+lilingxi01@users.noreply.github.com> Date: Mon, 28 Jun 2021 12:27:01 -0400 Subject: [PATCH 11/37] [DVStack] Finish the basic implementations of `DefinedViewStack` and porting with `DefinedViewManager` --- .../Frameworks/View/Stack/DVStack.Main.swift | 92 +++++++++---------- 1 file changed, 44 insertions(+), 48 deletions(-) diff --git a/Sources/DefinedElements/Frameworks/View/Stack/DVStack.Main.swift b/Sources/DefinedElements/Frameworks/View/Stack/DVStack.Main.swift index e9ee8f6..ef9b80b 100644 --- a/Sources/DefinedElements/Frameworks/View/Stack/DVStack.Main.swift +++ b/Sources/DefinedElements/Frameworks/View/Stack/DVStack.Main.swift @@ -1,3 +1,5 @@ +#if os(iOS) + import Foundation import SwiftUI @@ -8,46 +10,37 @@ public struct DefinedViewStack : DefinedView { /// 页面堆栈管理器 < 环境变量 > /// /// 用于进行页面堆栈的管理,以及实现页面堆栈的交互动效 - @ObservedObject var manager: DefinedViewManager = DefinedViewManager.get() + @ObservedObject var manager: DefinedViewStackManager = DefinedViewStackManager.generate() /// 视图空间 < 内部变量 > @Namespace private var space - /// 默认页面进出动效 < 外部常量 > - /// - /// 从右侧滑入,从右侧滑出。下层View通过offset实现位移而非transition。 - public static var defaultTransition: AnyTransition { - .move(edge: .trailing) - } - - /// DefinedViewStack 构造器 - View - /// - /// - Parameter start: 底层视图 - public init(from start: Content) where Content: View { - if self.manager.views.count > 0 { - self.manager.renew() - } - self.manager.viewStack.push(DefinedPageElement(AnyView(start))) - } - - /// DefinedViewStack 构造器 - AnyView + /// DefinedViewStack 构造器 - DefinedPage < 推荐构造器 > /// - /// - Parameter start: 底层视图 - public init(from start: AnyView) { - if self.manager.views.count > 0 { - self.manager.renew() - } - self.manager.viewStack.push(DefinedPageElement(start)) + /// - Parameters: + /// - from: The start page of this stack. + internal init(from start: StartPage) where StartPage: DefinedPage { + self.manager.viewStack.push(DefinedViewStackElement(start)) + DefinedViewManager.register( + manager: self.manager, + parent: .base + ) } /// DefinedViewStack 构造器 - DefinedPage < 推荐构造器 > /// - /// - Parameter start: 底层页面 - public init(from start: Page) where Page: DefinedPage { - if self.manager.views.count > 0 { - self.manager.renew() - } - self.manager.viewStack.push(DefinedPageElement(start)) + /// - Parameters: + /// - from: The start page of this stack. + /// - at: The parent page (the page holding this stack, NOT the root page of this stack). + public init( + from start: StartPage, + at parent: ParentPage + ) where StartPage: DefinedPage, ParentPage: DefinedPage { + self.manager.viewStack.push(DefinedViewStackElement(start)) + DefinedViewManager.register( + manager: self.manager, + parent: DefinedViewManager.find(parent).parent + ) } // MARK: - Body @@ -56,14 +49,14 @@ public struct DefinedViewStack : DefinedView { public var body: some View { GeometryReader { proxy in ZStack(alignment: .center) { // MARK: View Part - if self.manager.views.count >= 1 { - ForEach(0...self.manager.views.count-1, id: \.self) { i in - self.manager.views[i].content - .offset(x: i == self.manager.views.count - 1 || i == self.manager.views.count - 2 ? self.manager.offsets[i] : 0) - .matchedGeometryEffect(id: self.manager.views[i].id, in: self.space) + if self.manager.elements.count >= 1 { + ForEach(0...self.manager.elements.count-1, id: \.self) { i in + self.manager.elements[i].content + .offset(x: i == self.manager.elements.count - 1 || i == self.manager.elements.count - 2 ? self.manager.offsets[i] : 0) + .matchedGeometryEffect(id: self.manager.elements[i].id, in: self.space) .overlay( DefinedContent(.overlay) { - if i == self.manager.views.count - 2 { + if i == self.manager.elements.count - 2 { Color.black.opacity(0.1) .edgesIgnoringSafeArea(.all) .transition(.opacity) @@ -75,7 +68,7 @@ public struct DefinedViewStack : DefinedView { ) .transition(i == 0 ? .opacity : .move(edge: .trailing)) .zIndex(Double(i)) - .hidden(when_to_show: self.manager.onAnimated && i >= self.manager.views.count - 2) + .visibility(show: self.manager.onAnimated && i >= self.manager.elements.count - 2) } } else { EmptyView() @@ -83,10 +76,10 @@ public struct DefinedViewStack : DefinedView { } .overlay( // MARK: Drag Part DefinedContent(.overlay, alignment: .leading) { - if self.manager.views.count > 1 { + if self.manager.elements.count > 1 { Color.clear .frame(width: 22) - .definedFullSize(.vertical, priorAlign: .leading) + .frame(maxHeight: .infinity, alignment: .leading) .background(Color.blue.opacity(0)) .contentShape(Rectangle()) .simultaneousGesture( @@ -100,18 +93,19 @@ public struct DefinedViewStack : DefinedView { DragGesture(coordinateSpace: .local) .onChanged({ gesture in withAnimation(.easeInOut(duration: 0.12)) { - self.manager.offsets[self.manager.views.count - 1] = gesture.location.x / 1.2 - self.manager.offsets[self.manager.views.count - 2] = -proxy.size.width / 4 + gesture.location.x / 4.8 + self.manager.offsets[self.manager.elements.count - 1] = gesture.location.x / 1.2 + self.manager.offsets[self.manager.elements.count - 2] = -proxy.size.width / 4 + gesture.location.x / 4.8 } }) .onEnded({ gesture in if gesture.predictedEndTranslation.width > 150 { - // TODO: - self.manager.pop() + // call the main manager to pop instead of popping from stack manager directly. + // unregister the page and pop from current stack. + DefinedViewManager.find(self.manager.elements.last!.id).back() } else { withAnimation(.easeInOut(duration: 0.15)) { - self.manager.offsets[self.manager.views.count - 1] = 0 - self.manager.offsets[self.manager.views.count - 2] = -proxy.size.width / 4 + self.manager.offsets[self.manager.elements.count - 1] = 0 + self.manager.offsets[self.manager.elements.count - 2] = -proxy.size.width / 4 } } }), @@ -119,7 +113,7 @@ public struct DefinedViewStack : DefinedView { ) } } - .definedSize(width: .full, height: .full, alignment: .leading) + .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .leading) ) // .mask(Color.black.opacity(self.manager.overallOpacity).animation(.easeInOut(duration: 0.30)).edgesIgnoringSafeArea(.all)) .disabled(self.manager.onRootAnimated) @@ -133,6 +127,8 @@ public struct DefinedViewStack : DefinedView { /// public func setStatusBarStyle(_ style: UIStatusBarStyle) { - + // } } + +#endif From 3324b006a58f5acd239c75e37aa38c00091a39bf Mon Sep 17 00:00:00 2001 From: Lingxi_Li <36816148+lilingxi01@users.noreply.github.com> Date: Mon, 28 Jun 2021 12:27:33 -0400 Subject: [PATCH 12/37] [DVStack] Initial implementation of stand-alone `DefinedViewStackManager` on controlling the stack --- .../View/Stack/DVStack.Manager.swift | 135 ++++++++++++++++++ 1 file changed, 135 insertions(+) create mode 100644 Sources/DefinedElements/Frameworks/View/Stack/DVStack.Manager.swift diff --git a/Sources/DefinedElements/Frameworks/View/Stack/DVStack.Manager.swift b/Sources/DefinedElements/Frameworks/View/Stack/DVStack.Manager.swift new file mode 100644 index 0000000..c5af105 --- /dev/null +++ b/Sources/DefinedElements/Frameworks/View/Stack/DVStack.Manager.swift @@ -0,0 +1,135 @@ +#if os(iOS) + +import Foundation +import SwiftUI + +/// [DE Internal] A core manager controlling the view stack. +/// +/// Should be attached to a `DefinedViewStack` for proper using. +/// One `StackManager` per `DefinedViewStack`. +class DefinedViewStackManager : ObservableObject { + /// + var id: UUID + + /// [Deprecated] + @Published var onAnimated: Bool = true // TODO: 下层View仅在侧滑时出现 + + /// [Deprecated] + @Published var onRootAnimated: Bool = false + + /// All elements in the view stack. + /// + /// Being used for obtaining the data and displaying view stack elements in `DefinedViewStack` module. + /// + /// - Important: You should NOT modify this immediately! + /// This array will be synced automatically with one inside `DefinedViewStackContainer`. + /// So manually modifying this array may result in fatal bugs. + @Published var elements: [DefinedViewStackElement] = [] + + /// All status bar styles correponding to every element. + /// + /// All dynamically modified data should be stored into here instead of inside `DefinedViewStackElement`. + /// The `statusBarStyle` field in `DefinedViewStackElement` is only used on initializing the actual stack. + @Published var statusBarStyles: [UIStatusBarStyle] = [] + + /// An offset storer for all views. + /// + /// Being used for dynamically control the offset of each view inside view stack. + @Published var offsets: [CGFloat] = [0] + + /// The entire width of stack view area. + /// + /// - Note: Not the width of screen! + @Published var width: CGFloat = 0 + + /// The entire height of stack view area. + /// + /// - Note: Not the height of screen! + @Published var height: CGFloat = 0 + + /// The safe area insets of current view stack. + @Published var safeAreaInsets: EdgeInsets = EdgeInsets.init() + + /// [Deprecated] The overall opacity of current view stack. + /// + /// - TODO: We are not using the overall opacity for now. + @Published var overallOpacity: Double = 1.0 + + /// [DE Internal] + init() { + self.id = UUID() + } + + /// + internal var viewStack = DefinedViewStackContainer() { + didSet { + elements = viewStack.getStack() + } + } + + /// + func push(_ target: Page) where Page: DefinedPage { + self.offsets.append(0) + withAnimation(.easeInOut(duration: 0.30)) { + self.viewStack.push(DefinedViewStackElement(target)) + + if self.viewStack.stack.count > 1 { + self.offsets[self.offsets.count - 2] = -self.width / 4 + } + } + } + + /// + func pop() { + if self.viewStack.stack.count > 1 { + withAnimation(.easeInOut(duration: 0.25)) { + if (self.offsets[self.offsets.count - 2] == 0) { + self.viewStack.pop() + } else { + withAnimation(.easeInOut(duration: 0.26)) { + self.viewStack.pop() + } + } + self.offsets[self.offsets.count - 2] = 0 + } + self.offsets.removeLast() + } else { + // ERROR: should NOT be able to pop right now!!! + +// withAnimation(.easeInOut(duration: 0.26)) { +// self.viewStack.pop() +// } +// self.offsets.removeLast() + } + } + + /// + func jump(_ target: Page) where Page: DefinedPage { + withAnimation(.easeInOut(duration: 0.50)) { + self.renew() + self.viewStack.push(DefinedViewStackElement(target)) + } + } + + /// + func renew() { + self.viewStack.removeAll() + self.offsets = [0] + } +} + +extension DefinedViewStackManager { + private static var pool: [UUID: DefinedViewStackManager] = [:] + + internal static func generate() -> DefinedViewStackManager { + let newManager = DefinedViewStackManager() + pool[newManager.id] = newManager + return newManager + } + + internal static func find(id: UUID) -> DefinedViewStackManager? { + return pool[id] + } +} + +#endif From 0ca366e3dce9887c40f8ac5b70b0f83ab336b02b Mon Sep 17 00:00:00 2001 From: Lingxi_Li <36816148+lilingxi01@users.noreply.github.com> Date: Mon, 28 Jun 2021 12:39:11 -0400 Subject: [PATCH 13/37] [DPage] Remove deprecated docker methods and imports and modify the accessibility of some internal values --- .../Page/iOS/DPage-iOS.Protocol.swift | 28 ------------------- .../iOS/Support/DPage-iOS.Controller.swift | 5 +++- 2 files changed, 4 insertions(+), 29 deletions(-) diff --git a/Sources/DefinedElements/Frameworks/Page/iOS/DPage-iOS.Protocol.swift b/Sources/DefinedElements/Frameworks/Page/iOS/DPage-iOS.Protocol.swift index 591e585..94e3b6d 100644 --- a/Sources/DefinedElements/Frameworks/Page/iOS/DPage-iOS.Protocol.swift +++ b/Sources/DefinedElements/Frameworks/Page/iOS/DPage-iOS.Protocol.swift @@ -2,8 +2,6 @@ import Foundation import SwiftUI -import ObjectiveC - /// DefinedPage - 页面基础协议 @ DefinedElements /// @@ -193,32 +191,6 @@ public extension DefinedPage { } } -// MARK: - Docker - -//public extension DefinedPage { -// var viewStackDocker: DefinedViewStackDocker { -// get { -// // by default, create a docker without manager. -// return DefinedViewStackDocker() -// } -// set { -// print("set \(newValue)") -// } -// } -// -// internal func bindPageTo(manager: DefinedViewManager) -> Self { -// var copy = self -// copy.changeViewStackDocker(DefinedViewStackDocker(manager: manager)) -// print("after: \(copy.viewStackDocker)") -// return copy -// } -// -// internal mutating func changeViewStackDocker(_ target: DefinedViewStackDocker) { -// self.viewStackDocker.manager = target.manager -// print("changed: \(self.viewStackDocker)") -// } -//} - // MARK: - Core - Process /// 核心Body的处理 diff --git a/Sources/DefinedElements/Frameworks/Page/iOS/Support/DPage-iOS.Controller.swift b/Sources/DefinedElements/Frameworks/Page/iOS/Support/DPage-iOS.Controller.swift index ad02a34..0c65fcd 100644 --- a/Sources/DefinedElements/Frameworks/Page/iOS/Support/DPage-iOS.Controller.swift +++ b/Sources/DefinedElements/Frameworks/Page/iOS/Support/DPage-iOS.Controller.swift @@ -1,8 +1,11 @@ import Foundation +/// [DE] public class DefinedPageController { - public let id: UUID = UUID() + /// + internal let id: UUID = UUID() + /// public init() { // } From 2466274196512860874ad1a57d488aa6d26acbf7 Mon Sep 17 00:00:00 2001 From: Lingxi_Li <36816148+lilingxi01@users.noreply.github.com> Date: Mon, 28 Jun 2021 18:34:29 -0400 Subject: [PATCH 14/37] [DVManager] Add deprecated managers clean up procedure --- .../View/Manager/DVManager.Main.swift | 28 +++++++++++++++++-- .../Manager/Support/DVManager.Element.swift | 22 ++++++++++++++- 2 files changed, 47 insertions(+), 3 deletions(-) diff --git a/Sources/DefinedElements/Frameworks/View/Manager/DVManager.Main.swift b/Sources/DefinedElements/Frameworks/View/Manager/DVManager.Main.swift index 83e046b..a8e2483 100644 --- a/Sources/DefinedElements/Frameworks/View/Manager/DVManager.Main.swift +++ b/Sources/DefinedElements/Frameworks/View/Manager/DVManager.Main.swift @@ -19,7 +19,11 @@ class DefinedViewManager : DefinedPotentialWarning { var pageMap: [UUID: DefinedViewManagerElement] = [:] /// - public static func register(manager stackManager: DefinedViewStackManager, parent: DefinedViewManagerRootElement) { + public static func registerStack( + manager stackManager: DefinedViewStackManager, + parent: DefinedViewManagerRootElement, + under: DefinedViewManagerElement? = nil + ) { let rootElement = DefinedViewManagerRootElement(docker: DefinedViewStackDocker(manager: stackManager), parent: parent) @@ -33,9 +37,26 @@ class DefinedViewManager : DefinedPotentialWarning { rootElement.hierarchy.append(pageElement) + if (under != nil) { + under!.register(rootElement) + } + DefinedViewManager.instance.hierarchy.append(rootElement) } + /// + public static func unregisterStack(root: DefinedViewManagerRootElement) { + DefinedViewManager.instance.hierarchy.removeAll(where: { curr in + if curr == root { + for page in curr.hierarchy { + DefinedViewManager.unregisterPage(id: page.id) + } + return true + } + return false + }) + } + /// public static func find(_ id: UUID) -> DefinedViewManagerElement { return DefinedViewManager.instance.pageMap[id] ?? .dummy @@ -54,7 +75,10 @@ class DefinedViewManager : DefinedPotentialWarning { } internal static func unregisterPage(id: UUID) { - DefinedViewManager.instance.pageMap.removeValue(forKey: id) + guard let page = DefinedViewManager.instance.pageMap.removeValue(forKey: id) else { + return + } + page.unregister() } } diff --git a/Sources/DefinedElements/Frameworks/View/Manager/Support/DVManager.Element.swift b/Sources/DefinedElements/Frameworks/View/Manager/Support/DVManager.Element.swift index b7186dc..c8d7d8d 100644 --- a/Sources/DefinedElements/Frameworks/View/Manager/Support/DVManager.Element.swift +++ b/Sources/DefinedElements/Frameworks/View/Manager/Support/DVManager.Element.swift @@ -2,10 +2,14 @@ import Foundation +/// [DE Internal] internal class DefinedViewManagerElement : Equatable { var id: UUID + var parent: DefinedViewManagerRootElement + var stacks: [DefinedViewManagerRootElement] = [] + init(id: UUID, parent: DefinedViewManagerRootElement) { self.id = id self.parent = parent @@ -27,6 +31,16 @@ internal class DefinedViewManagerElement : Equatable { parent.back() } + func register(_ root: DefinedViewManagerRootElement) { + self.stacks.append(root) + } + + func unregister() { + for r in stacks { + DefinedViewManager.unregisterStack(root: r) + } + } + static func == (lhs: DefinedViewManagerElement, rhs: DefinedViewManagerElement) -> Bool { return lhs.id == rhs.id } @@ -47,7 +61,9 @@ internal class DefinedViewManagerDummyElement : DefinedViewManagerElement { } } -internal class DefinedViewManagerRootElement : DefinedPotentialWarning { +internal class DefinedViewManagerRootElement : DefinedPotentialWarning, Equatable { + var id: UUID = UUID() + var name: String = "DVManagerRootElement" var hierarchy: [DefinedViewManagerElement] = [] @@ -106,6 +122,10 @@ internal class DefinedViewManagerRootElement : DefinedPotentialWarning { // TODO: implement back-parent feature. } } + + static func == (lhs: DefinedViewManagerRootElement, rhs: DefinedViewManagerRootElement) -> Bool { + return lhs.id == rhs.id + } } extension DefinedViewManagerRootElement { From 4cffc2eac90b21239c0b849150a9cf387a36998a Mon Sep 17 00:00:00 2001 From: Lingxi_Li <36816148+lilingxi01@users.noreply.github.com> Date: Mon, 28 Jun 2021 18:35:37 -0400 Subject: [PATCH 15/37] [DVStack] Fix the bug of double swipe on last two views --- .../Frameworks/View/Stack/DVStack.Main.swift | 25 +++++++++++-------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/Sources/DefinedElements/Frameworks/View/Stack/DVStack.Main.swift b/Sources/DefinedElements/Frameworks/View/Stack/DVStack.Main.swift index ef9b80b..f5c4241 100644 --- a/Sources/DefinedElements/Frameworks/View/Stack/DVStack.Main.swift +++ b/Sources/DefinedElements/Frameworks/View/Stack/DVStack.Main.swift @@ -21,7 +21,7 @@ public struct DefinedViewStack : DefinedView { /// - from: The start page of this stack. internal init(from start: StartPage) where StartPage: DefinedPage { self.manager.viewStack.push(DefinedViewStackElement(start)) - DefinedViewManager.register( + DefinedViewManager.registerStack( manager: self.manager, parent: .base ) @@ -37,9 +37,10 @@ public struct DefinedViewStack : DefinedView { at parent: ParentPage ) where StartPage: DefinedPage, ParentPage: DefinedPage { self.manager.viewStack.push(DefinedViewStackElement(start)) - DefinedViewManager.register( + DefinedViewManager.registerStack( manager: self.manager, - parent: DefinedViewManager.find(parent).parent + parent: DefinedViewManager.find(parent).parent, + under: DefinedViewManager.find(parent) ) } @@ -92,9 +93,11 @@ public struct DefinedViewStack : DefinedView { .simultaneousGesture( DragGesture(coordinateSpace: .local) .onChanged({ gesture in - withAnimation(.easeInOut(duration: 0.12)) { - self.manager.offsets[self.manager.elements.count - 1] = gesture.location.x / 1.2 - self.manager.offsets[self.manager.elements.count - 2] = -proxy.size.width / 4 + gesture.location.x / 4.8 + if self.manager.elements.count > 1 { + withAnimation(.easeInOut(duration: 0.12)) { + self.manager.offsets[self.manager.elements.count - 1] = gesture.location.x / 1.2 + self.manager.offsets[self.manager.elements.count - 2] = -proxy.size.width / 4 + gesture.location.x / 4.8 + } } }) .onEnded({ gesture in @@ -103,9 +106,11 @@ public struct DefinedViewStack : DefinedView { // unregister the page and pop from current stack. DefinedViewManager.find(self.manager.elements.last!.id).back() } else { - withAnimation(.easeInOut(duration: 0.15)) { - self.manager.offsets[self.manager.elements.count - 1] = 0 - self.manager.offsets[self.manager.elements.count - 2] = -proxy.size.width / 4 + if self.manager.elements.count > 1 { + withAnimation(.easeInOut(duration: 0.15)) { + self.manager.offsets[self.manager.elements.count - 1] = 0 + self.manager.offsets[self.manager.elements.count - 2] = -proxy.size.width / 4 + } } } }), @@ -127,7 +132,7 @@ public struct DefinedViewStack : DefinedView { /// public func setStatusBarStyle(_ style: UIStatusBarStyle) { - // + // TODO: } } From 120172af09d9c9d3a1eb63800d392f19b89bd2c5 Mon Sep 17 00:00:00 2001 From: Lingxi_Li <36816148+lilingxi01@users.noreply.github.com> Date: Tue, 29 Jun 2021 12:16:55 -0400 Subject: [PATCH 16/37] [DVManager] Documented and made up the needed warnings. --- .../View/Manager/DVManager.Main.swift | 72 ++++++++++++++----- 1 file changed, 55 insertions(+), 17 deletions(-) diff --git a/Sources/DefinedElements/Frameworks/View/Manager/DVManager.Main.swift b/Sources/DefinedElements/Frameworks/View/Manager/DVManager.Main.swift index a8e2483..4a20d6c 100644 --- a/Sources/DefinedElements/Frameworks/View/Manager/DVManager.Main.swift +++ b/Sources/DefinedElements/Frameworks/View/Manager/DVManager.Main.swift @@ -6,20 +6,29 @@ import SwiftUI /// [DE Internal] A core manager controlling the view stack. /// /// Should be attached to a `DefinedViewStack` for proper using. -class DefinedViewManager : DefinedPotentialWarning { - var name: String = "DefinedViewManager" - - /// +/// +/// - TODO: Reconsider the use case of making this public. +class DefinedViewManager { + /// [DE Internal] Global instance of `DefinedViewManager`. static var instance = DefinedViewManager() + /// An array containing all root elements. /// + /// This array is designed for holding root elements in order to get a sense of stack pool. + /// Each stack corresponds to a root element, so when we need to control or clean up, we can search from here. + /// It is different from normal manager element because that corresponds to a page, which is the sub-item of our root element. var hierarchy: [DefinedViewManagerRootElement] = [] - /// + /// A hash map containing all active pages. var pageMap: [UUID: DefinedViewManagerElement] = [:] + /// [DE Internal] Register a `DefinedViewStack` into global manager. /// - public static func registerStack( + /// - Parameters: + /// - stackManager: The manager property of `DeefinedViewStack`. + /// - parent: The parent stack where this stack stands on. + /// - under: The page that this stack stands on. This is optional, but highly recommanded. If you registered a stack without associating the parent page `under` (not the parent stack), this stack and its pages will not be collected automatically when the parent page is destroyed. + internal static func registerStack( manager stackManager: DefinedViewStackManager, parent: DefinedViewManagerRootElement, under: DefinedViewManagerElement? = nil @@ -34,18 +43,15 @@ class DefinedViewManager : DefinedPotentialWarning { } let pageElement = DefinedViewManager.registerPage(id: stackManager.elements.first!.id, parent: rootElement) - rootElement.hierarchy.append(pageElement) - - if (under != nil) { - under!.register(rootElement) - } - + under?.register(rootElement) DefinedViewManager.instance.hierarchy.append(rootElement) } + /// [DE Internal] Unregister a `DefinedViewStack` into global manager. /// - public static func unregisterStack(root: DefinedViewManagerRootElement) { + /// - Parameter root: The root element of the stack you want to unregister. You can get the root element by `DefinedViewManager.find(page).parent` where `page` could be any page on this stack. + internal static func unregisterStack(root: DefinedViewManagerRootElement) { DefinedViewManager.instance.hierarchy.removeAll(where: { curr in if curr == root { for page in curr.hierarchy { @@ -57,25 +63,57 @@ class DefinedViewManager : DefinedPotentialWarning { }) } + /// [DE Internal] Find the page manager by given page id. /// - public static func find(_ id: UUID) -> DefinedViewManagerElement { - return DefinedViewManager.instance.pageMap[id] ?? .dummy + /// - Parameter id: The page id. + /// - Returns: The page manager corresponding to the given page id. + /// + /// - Note: It will return a dummy and generate a warning if we cannot find it in order to not break the program. + internal static func find(_ id: UUID) -> DefinedViewManagerElement { + guard let element = DefinedViewManager.instance.pageMap[id] else { + DefinedWarning.send(from: "DVManager", "cannot find the page manager by given page id.") + return .dummy + } + return element } + /// [DE Internal] Find the page manager by given page instance. /// - public static func find(_ page: Page) -> DefinedViewManagerElement where Page: DefinedPage { - return DefinedViewManager.instance.pageMap[page.id] ?? .dummy + /// - Parameter page: The `DefinedPage` instance. + /// - Returns: The page manager corresponding to the given page. + /// + /// - Note: It will return a dummy and generate a warning if we cannot find it in order to not break the program. + internal static func find(_ page: Page) -> DefinedViewManagerElement where Page: DefinedPage { + guard let element = DefinedViewManager.instance.pageMap[page.id] else { + DefinedWarning.send(from: "DVManager", "cannot find the page manager by given DefinedPage.") + return .dummy + } + return element } + /// [DE Internal] Register a `DefinedPage` into global manager. /// + /// This may be called automatically. + /// + /// - Parameters: + /// - id: The id of the `DefinedPage` being registered. + /// - parent: The root element of the stack where this page is in. + /// - Returns: The newly created page manager corresponding to the page that is just registered. internal static func registerPage(id: UUID, parent: DefinedViewManagerRootElement) -> DefinedViewManagerElement { let pageElement = DefinedViewManagerElement(id: id, parent: parent) DefinedViewManager.instance.pageMap[id] = pageElement return pageElement } + /// [DE Internal] Unregister a `DefinedPage` from global manager. + /// + /// This may be called automatically for safely collecting the unavailable pages. + /// + /// - Parameters: + /// - id: The id of the `DefinedPage` being unregistered. internal static func unregisterPage(id: UUID) { guard let page = DefinedViewManager.instance.pageMap.removeValue(forKey: id) else { + DefinedWarning.send(from: "DVManager", "the page being unregistered does not exist!") return } page.unregister() From 4a83377baa90fc4f0c3be2a0853aa0615ec3bbd6 Mon Sep 17 00:00:00 2001 From: Lingxi_Li <36816148+lilingxi01@users.noreply.github.com> Date: Tue, 29 Jun 2021 12:17:25 -0400 Subject: [PATCH 17/37] [DVStack] Add needed warnings. --- .../Frameworks/View/Stack/DVStack.Docker.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Sources/DefinedElements/Frameworks/View/Stack/DVStack.Docker.swift b/Sources/DefinedElements/Frameworks/View/Stack/DVStack.Docker.swift index 9dae154..4a20bc4 100644 --- a/Sources/DefinedElements/Frameworks/View/Stack/DVStack.Docker.swift +++ b/Sources/DefinedElements/Frameworks/View/Stack/DVStack.Docker.swift @@ -21,7 +21,7 @@ public struct DefinedViewStackDocker : DefinedPotentialWarning { if (self.manager != nil) { manager!.push(target) } else { - print("null docker") + warning("the manager is null!") } } @@ -30,7 +30,7 @@ public struct DefinedViewStackDocker : DefinedPotentialWarning { if (self.manager != nil) { manager!.jump(target) } else { - // + warning("the manager is null!") } } @@ -39,7 +39,7 @@ public struct DefinedViewStackDocker : DefinedPotentialWarning { if (self.manager != nil) { // } else { - // + warning("the manager is null!") } } From 8b9e8d7cb6133c13baf0d06a2f4dcfc5b9a4c5c5 Mon Sep 17 00:00:00 2001 From: Lingxi_Li <36816148+lilingxi01@users.noreply.github.com> Date: Mon, 5 Jul 2021 16:12:32 -0400 Subject: [PATCH 18/37] [DVStack] Finish the implementation of complex multiple stacks support. --- .../Frameworks/View/Stack/DVStack.Main.swift | 88 +++++++++++++------ 1 file changed, 59 insertions(+), 29 deletions(-) diff --git a/Sources/DefinedElements/Frameworks/View/Stack/DVStack.Main.swift b/Sources/DefinedElements/Frameworks/View/Stack/DVStack.Main.swift index f5c4241..7ccd8ce 100644 --- a/Sources/DefinedElements/Frameworks/View/Stack/DVStack.Main.swift +++ b/Sources/DefinedElements/Frameworks/View/Stack/DVStack.Main.swift @@ -3,50 +3,88 @@ import Foundation import SwiftUI -/// DefinedViewStack - 视图堆栈 @ DefinedElements +/// [DE] A view stack holding `DefinedPage`. /// -/// - TODO: Be able to use Stack multiple times (means multiple universal managers and controllers) +/// - BUG: Swipe back gesture not perfect public struct DefinedViewStack : DefinedView { - /// 页面堆栈管理器 < 环境变量 > + + /// A manager controlling the view stack. + /// + /// Should be observed and generate from `DVStackManager` global instance. + /// To be used on managing pages and interactions. + @ObservedObject var manager: DefinedViewStackManager + + /// The name of this `DefinedViewStack`. /// - /// 用于进行页面堆栈的管理,以及实现页面堆栈的交互动效 - @ObservedObject var manager: DefinedViewStackManager = DefinedViewStackManager.generate() + /// Should be unique per page (if there are multiple embeded view stacks in one page). + private var name: String + + /// The parent page id of this `DefinedViewStack`. + private var parentId: UUID - /// 视图空间 < 内部变量 > + /// The namespace for this view stack. @Namespace private var space - /// DefinedViewStack 构造器 - DefinedPage < 推荐构造器 > + /// [DE Internal] Create a view stack by given start page. + /// + /// This should be used internally on starting the app (the root stack). /// /// - Parameters: /// - from: The start page of this stack. internal init(from start: StartPage) where StartPage: DefinedPage { - self.manager.viewStack.push(DefinedViewStackElement(start)) - DefinedViewManager.registerStack( - manager: self.manager, - parent: .base + self.name = "DVStackCoreRootInternal" + self.parentId = DefinedViewStackManager.rootId + + let shouldRegister = DefinedViewStackManager.shouldRegister(name: name, pageId: parentId) + self.manager = DefinedViewStackManager.get( + name: "DVStackCoreRootInternal", + pageId: parentId, + shouldUseStatusBar: true ) + + if shouldRegister { + self.manager.viewStack.push(DefinedViewStackElement(start)) + DefinedViewManager.registerStack( + manager: self.manager, + parent: .base + ) + } } - /// DefinedViewStack 构造器 - DefinedPage < 推荐构造器 > + /// [DE] Create a view stack by given start page and the parent page. + /// + /// This should be used on developing a stack in another page. /// /// - Parameters: + /// - name: The name of the view stack. /// - from: The start page of this stack. /// - at: The parent page (the page holding this stack, NOT the root page of this stack). + /// - statusBar: True if this stack should change the status bar style while directing. (optional, default false) public init( + name: String, from start: StartPage, - at parent: ParentPage + at parent: ParentPage, + statusBar shouldUseStatusBar: Bool = false ) where StartPage: DefinedPage, ParentPage: DefinedPage { - self.manager.viewStack.push(DefinedViewStackElement(start)) - DefinedViewManager.registerStack( - manager: self.manager, - parent: DefinedViewManager.find(parent).parent, - under: DefinedViewManager.find(parent) - ) + self.name = name + self.parentId = parent.id + + let shouldRegister = DefinedViewStackManager.shouldRegister(name: name, pageId: parentId) + self.manager = DefinedViewStackManager.get(name: name, pageId: parentId, shouldUseStatusBar: shouldUseStatusBar) + + if shouldRegister { + self.manager.viewStack.push(DefinedViewStackElement(start)) + DefinedViewManager.registerStack( + manager: self.manager, + parent: DefinedViewManager.find(parent).parent, + under: DefinedViewManager.find(parent) + ) + } } // MARK: - Body - /// + /// The core body view of this view stack. public var body: some View { GeometryReader { proxy in ZStack(alignment: .center) { // MARK: View Part @@ -58,12 +96,10 @@ public struct DefinedViewStack : DefinedView { .overlay( DefinedContent(.overlay) { if i == self.manager.elements.count - 2 { - Color.black.opacity(0.1) + Color.black.opacity(0.06) .edgesIgnoringSafeArea(.all) .transition(.opacity) .allowsHitTesting(false) - } else { - EmptyView() } } ) @@ -120,7 +156,6 @@ public struct DefinedViewStack : DefinedView { } .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .leading) ) - // .mask(Color.black.opacity(self.manager.overallOpacity).animation(.easeInOut(duration: 0.30)).edgesIgnoringSafeArea(.all)) .disabled(self.manager.onRootAnimated) .onAppear { self.manager.width = proxy.size.width @@ -129,11 +164,6 @@ public struct DefinedViewStack : DefinedView { } } } - - /// - public func setStatusBarStyle(_ style: UIStatusBarStyle) { - // TODO: - } } #endif From 3f8d325ce32c6f4065c0eca08938fe735d1a3cd3 Mon Sep 17 00:00:00 2001 From: Lingxi_Li <36816148+lilingxi01@users.noreply.github.com> Date: Mon, 5 Jul 2021 16:13:36 -0400 Subject: [PATCH 19/37] [DVStack] `DVStackManager` Rebuild a per-page pool and add status bar style support. --- .../View/Stack/DVStack.Manager.swift | 75 +++++++++++++++---- 1 file changed, 59 insertions(+), 16 deletions(-) diff --git a/Sources/DefinedElements/Frameworks/View/Stack/DVStack.Manager.swift b/Sources/DefinedElements/Frameworks/View/Stack/DVStack.Manager.swift index c5af105..13a28c9 100644 --- a/Sources/DefinedElements/Frameworks/View/Stack/DVStack.Manager.swift +++ b/Sources/DefinedElements/Frameworks/View/Stack/DVStack.Manager.swift @@ -9,7 +9,10 @@ import SwiftUI /// One `StackManager` per `DefinedViewStack`. class DefinedViewStackManager : ObservableObject { /// - var id: UUID + var name: String + + /// + var shouldUseStatusBar: Bool /// [Deprecated] @Published var onAnimated: Bool = true // TODO: 下层View仅在侧滑时出现 @@ -56,8 +59,9 @@ class DefinedViewStackManager : ObservableObject { @Published var overallOpacity: Double = 1.0 /// [DE Internal] - init() { - self.id = UUID() + init(name: String, statusBar: Bool) { + self.name = name + self.shouldUseStatusBar = statusBar } /// @@ -69,6 +73,10 @@ class DefinedViewStackManager : ObservableObject { /// func push(_ target: Page) where Page: DefinedPage { + if self.shouldUseStatusBar { + UIApplication.setStatusBarStyle(target.statusBarStyle) + } + self.offsets.append(0) withAnimation(.easeInOut(duration: 0.30)) { self.viewStack.push(DefinedViewStackElement(target)) @@ -82,6 +90,10 @@ class DefinedViewStackManager : ObservableObject { /// func pop() { if self.viewStack.stack.count > 1 { + if self.shouldUseStatusBar { + UIApplication.setStatusBarStyle(self.elements[self.elements.count - 2].statusBarStyle) + } + withAnimation(.easeInOut(duration: 0.25)) { if (self.offsets[self.offsets.count - 2] == 0) { self.viewStack.pop() @@ -95,16 +107,15 @@ class DefinedViewStackManager : ObservableObject { self.offsets.removeLast() } else { // ERROR: should NOT be able to pop right now!!! - -// withAnimation(.easeInOut(duration: 0.26)) { -// self.viewStack.pop() -// } -// self.offsets.removeLast() } } /// func jump(_ target: Page) where Page: DefinedPage { + if self.shouldUseStatusBar { + UIApplication.setStatusBarStyle(target.statusBarStyle) + } + withAnimation(.easeInOut(duration: 0.50)) { self.renew() self.viewStack.push(DefinedViewStackElement(target)) @@ -116,19 +127,51 @@ class DefinedViewStackManager : ObservableObject { self.viewStack.removeAll() self.offsets = [0] } + + /// + func setStatusBarStyle(_ style: UIStatusBarStyle) { + self.elements[self.elements.count - 1].statusBarStyle = style + UIApplication.setStatusBarStyle(style) + } + + /// + func setStatusBarStyle(pageId: UUID, style: UIStatusBarStyle) { + let index = self.elements.firstIndex(where: { elem in return elem.id == pageId }) + if index != nil { + self.elements[index!].statusBarStyle = style + if (index == self.elements.count - 1) { + UIApplication.setStatusBarStyle(style) + } + } + + } } +// MARK: - StackManager Pool + extension DefinedViewStackManager { - private static var pool: [UUID: DefinedViewStackManager] = [:] - - internal static func generate() -> DefinedViewStackManager { - let newManager = DefinedViewStackManager() - pool[newManager.id] = newManager - return newManager + internal static let rootId: UUID = UUID() + + private static var pool: [UUID: [String: DefinedViewStackManager]] = [:] + + internal static func get( + name: String, + pageId: UUID, + shouldUseStatusBar: Bool, + rebuild: Bool = false + ) -> DefinedViewStackManager { + if pool[pageId] == nil { + pool[pageId] = [:] + } + if pool[pageId]![name] == nil || rebuild { + let newManager = DefinedViewStackManager(name: name, statusBar: shouldUseStatusBar) + pool[pageId]![name] = newManager + } + return pool[pageId]![name]! } - internal static func find(id: UUID) -> DefinedViewStackManager? { - return pool[id] + internal static func shouldRegister(name: String, pageId: UUID) -> Bool { + return pool[pageId]?[name] == nil } } From ceaa0ddaf214c7707c7c7b46a9b859e3790a70f2 Mon Sep 17 00:00:00 2001 From: Lingxi_Li <36816148+lilingxi01@users.noreply.github.com> Date: Mon, 5 Jul 2021 16:14:21 -0400 Subject: [PATCH 20/37] [DVStack] `DVStackContainer`: Add a `count` property. --- .../Frameworks/View/Stack/DVStack.Container.swift | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Sources/DefinedElements/Frameworks/View/Stack/DVStack.Container.swift b/Sources/DefinedElements/Frameworks/View/Stack/DVStack.Container.swift index 1cd2fbe..a1857b5 100644 --- a/Sources/DefinedElements/Frameworks/View/Stack/DVStack.Container.swift +++ b/Sources/DefinedElements/Frameworks/View/Stack/DVStack.Container.swift @@ -7,6 +7,10 @@ struct DefinedViewStackContainer { /// var stack = [DefinedViewStackElement]() + var count: Int { + return stack.count + } + /// func getStack() -> [DefinedViewStackElement] { return stack From 893f3166f10567c5151f35806291e1b7391392e5 Mon Sep 17 00:00:00 2001 From: Lingxi_Li <36816148+lilingxi01@users.noreply.github.com> Date: Mon, 5 Jul 2021 16:14:49 -0400 Subject: [PATCH 21/37] [DVManager] Add status bar style support to `DVManagerElement` --- .../View/Manager/Support/DVManager.Element.swift | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/Sources/DefinedElements/Frameworks/View/Manager/Support/DVManager.Element.swift b/Sources/DefinedElements/Frameworks/View/Manager/Support/DVManager.Element.swift index c8d7d8d..79693fd 100644 --- a/Sources/DefinedElements/Frameworks/View/Manager/Support/DVManager.Element.swift +++ b/Sources/DefinedElements/Frameworks/View/Manager/Support/DVManager.Element.swift @@ -1,6 +1,7 @@ #if os(iOS) import Foundation +import UIKit /// [DE Internal] internal class DefinedViewManagerElement : Equatable { @@ -31,6 +32,10 @@ internal class DefinedViewManagerElement : Equatable { parent.back() } + func setStatusBarStyle(_ style: UIStatusBarStyle) { + parent.docker?.setStatusBarStyle(pageId: self.id, style: style) + } + func register(_ root: DefinedViewManagerRootElement) { self.stacks.append(root) } @@ -136,14 +141,4 @@ extension DefinedViewManagerRootElement { fileprivate static let dummy = DefinedViewManagerRootElement() } -internal class DefinedViewManagerRoot: DefinedViewManagerRootElement { - init(docker: DefinedViewStackDocker) { - super.init(docker: docker) - } - - override func back() { - // - } -} - #endif From 9c127d7c5f9ce3eae590f83068e81bf164b0e8eb Mon Sep 17 00:00:00 2001 From: Lingxi_Li <36816148+lilingxi01@users.noreply.github.com> Date: Mon, 5 Jul 2021 16:15:06 -0400 Subject: [PATCH 22/37] [DVStack] Add status bar style support to `DVStackDocker`. --- .../Frameworks/View/Stack/DVStack.Docker.swift | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Sources/DefinedElements/Frameworks/View/Stack/DVStack.Docker.swift b/Sources/DefinedElements/Frameworks/View/Stack/DVStack.Docker.swift index 4a20bc4..99ad16f 100644 --- a/Sources/DefinedElements/Frameworks/View/Stack/DVStack.Docker.swift +++ b/Sources/DefinedElements/Frameworks/View/Stack/DVStack.Docker.swift @@ -1,6 +1,7 @@ #if os(iOS) import Foundation +import UIKit /// public struct DefinedViewStackDocker : DefinedPotentialWarning { @@ -51,6 +52,14 @@ public struct DefinedViewStackDocker : DefinedPotentialWarning { warning("the manager is null!") } } + + public func setStatusBarStyle(pageId: UUID, style: UIStatusBarStyle) { + if (self.manager != nil) { + manager!.setStatusBarStyle(pageId: pageId, style: style) + } else { + warning("the manager is null!") + } + } } #endif From 81ff0cbaf0c4aa48eaa9422625c15ec6ba01a534 Mon Sep 17 00:00:00 2001 From: Lingxi_Li <36816148+lilingxi01@users.noreply.github.com> Date: Mon, 5 Jul 2021 16:27:40 -0400 Subject: [PATCH 23/37] [DVStack] `DVStackElement`: Add the support of status bar style and make it documented --- .../View/Stack/DVStack.Element.swift | 36 +++++++++++-------- 1 file changed, 22 insertions(+), 14 deletions(-) diff --git a/Sources/DefinedElements/Frameworks/View/Stack/DVStack.Element.swift b/Sources/DefinedElements/Frameworks/View/Stack/DVStack.Element.swift index f2ec1e4..51cbb17 100644 --- a/Sources/DefinedElements/Frameworks/View/Stack/DVStack.Element.swift +++ b/Sources/DefinedElements/Frameworks/View/Stack/DVStack.Element.swift @@ -3,30 +3,36 @@ import Foundation import SwiftUI -/// 该结构服务于`DefinedViewStack` < 内部组件 > +/// [DE Internal] A view stack element holder using for `DefinedViewStack`. /// -/// - Note: The reason why we do not use generic type for `DefinedPage` is we will not be able to define an array of this. -struct DefinedViewStackElement : Identifiable, Equatable { - /// [Internal] - static var constantLevel: Int = 0 +/// - Note: The reason why we do NOT use generic type for `DefinedPage` is we will not be able to define an array of this. +internal struct DefinedViewStackElement : Identifiable, Equatable { + /// [Deprecated] + /// + /// This may be deprecated. It is used to make sure that the newer page is always above the elder page. + /// We need to make sure that the z-index things are not affected by multiple stacks support before removing this. + /// Everytime we create a new stack element, it self-increases. + private static var constantLevel: Int = 0 - /// Element 标签 + /// The id of the page held by this stack element. let id: UUID + /// The level of this stack element. /// + /// It should obtained automatically from `constantLevel` property. let level: Int - /// Element 内容 + /// The page held by this stack element. let content: AnyView - /// Element 状态栏设定(默认值) + /// The status bar setup of this page (held by this stack element). /// - /// - Note: 状态栏设定的变动在ViewManager中而非此处,该值仅用于页面初始化的时候 - var statusBarStyle: UIStatusBarStyle = .darkContent + /// This property will be modified synchronously when the `statusBarStyle` property of the page has been changed. + var statusBarStyle: UIStatusBarStyle - /// 构造器 - DefinedPage + /// [DE Internal] Create a ViewStack element by given page. /// - /// - Parameter view: 给定视图 + /// - Parameter page: The page for this stack element. init(_ page: Page) where Page: DefinedPage { self.id = page.id self.level = DefinedViewStackElement.constantLevel @@ -36,8 +42,10 @@ struct DefinedViewStackElement : Identifiable, Equatable { DefinedViewStackElement.constantLevel += 1 } - /// 用于相同比较 < 内部函数 > - static func == (lhs: Self, rhs: Self) -> Bool { + /// Compare to ViewStack Element by comparing their page id. + /// + /// When page ids are the same, they should be the same element. Otherwise, there is a bug. + static func == (lhs: DefinedViewStackElement, rhs: DefinedViewStackElement) -> Bool { return lhs.id == rhs.id } } From a9494a8a0918c1620ef5b3aa5de8bdcff42ebdb6 Mon Sep 17 00:00:00 2001 From: Lingxi_Li <36816148+lilingxi01@users.noreply.github.com> Date: Mon, 5 Jul 2021 16:28:01 -0400 Subject: [PATCH 24/37] [DVStack] Remove a deprecated variable from `DVStackManager`. --- .../Frameworks/View/Stack/DVStack.Manager.swift | 6 ------ 1 file changed, 6 deletions(-) diff --git a/Sources/DefinedElements/Frameworks/View/Stack/DVStack.Manager.swift b/Sources/DefinedElements/Frameworks/View/Stack/DVStack.Manager.swift index 13a28c9..8ff6736 100644 --- a/Sources/DefinedElements/Frameworks/View/Stack/DVStack.Manager.swift +++ b/Sources/DefinedElements/Frameworks/View/Stack/DVStack.Manager.swift @@ -28,12 +28,6 @@ class DefinedViewStackManager : ObservableObject { /// This array will be synced automatically with one inside `DefinedViewStackContainer`. /// So manually modifying this array may result in fatal bugs. @Published var elements: [DefinedViewStackElement] = [] - - /// All status bar styles correponding to every element. - /// - /// All dynamically modified data should be stored into here instead of inside `DefinedViewStackElement`. - /// The `statusBarStyle` field in `DefinedViewStackElement` is only used on initializing the actual stack. - @Published var statusBarStyles: [UIStatusBarStyle] = [] /// An offset storer for all views. /// From ab3b4b9650666f11574cc9a1b779e1640bdf44ee Mon Sep 17 00:00:00 2001 From: Lingxi_Li <36816148+lilingxi01@users.noreply.github.com> Date: Mon, 5 Jul 2021 16:50:30 -0400 Subject: [PATCH 25/37] [DApp-iOS] Rebuild the status bar style internal API. --- .../Frameworks/App/iOS/DApp-iOS.Root.swift | 23 ++++++++ .../App/iOS/DApp-iOS.StatusBar.swift | 58 ------------------- .../iOS/StatusBar/DApp-iOS.StatusBar.swift | 49 ++++++++++++++++ 3 files changed, 72 insertions(+), 58 deletions(-) create mode 100644 Sources/DefinedElements/Frameworks/App/iOS/DApp-iOS.Root.swift delete mode 100644 Sources/DefinedElements/Frameworks/App/iOS/DApp-iOS.StatusBar.swift create mode 100644 Sources/DefinedElements/Frameworks/App/iOS/StatusBar/DApp-iOS.StatusBar.swift diff --git a/Sources/DefinedElements/Frameworks/App/iOS/DApp-iOS.Root.swift b/Sources/DefinedElements/Frameworks/App/iOS/DApp-iOS.Root.swift new file mode 100644 index 0000000..bf871c7 --- /dev/null +++ b/Sources/DefinedElements/Frameworks/App/iOS/DApp-iOS.Root.swift @@ -0,0 +1,23 @@ +#if os(iOS) + +import Foundation +import SwiftUI + +internal struct DefinedAppRoot : Scene where StartPage: DefinedPage { + var start: StartPage + + init(from: StartPage) { + self.start = from + } + + var body: some Scene { + WindowGroup { + EmptyView() + .onAppear(perform: { + UIApplication.startApplication(from: self.start) + }) + } + } +} + +#endif diff --git a/Sources/DefinedElements/Frameworks/App/iOS/DApp-iOS.StatusBar.swift b/Sources/DefinedElements/Frameworks/App/iOS/DApp-iOS.StatusBar.swift deleted file mode 100644 index 2ada64c..0000000 --- a/Sources/DefinedElements/Frameworks/App/iOS/DApp-iOS.StatusBar.swift +++ /dev/null @@ -1,58 +0,0 @@ -import Foundation -import SwiftUI - -/// -class DefinedStatusBarController: UIHostingController { - var statusBarStyle: UIStatusBarStyle = .darkContent - var previousStatusBarStyle: UIStatusBarStyle = .darkContent - - /// - override var preferredStatusBarStyle: UIStatusBarStyle { - return statusBarStyle - } - - /// - func changeStatusBarStyle(_ style: UIStatusBarStyle) { - self.previousStatusBarStyle = self.statusBarStyle - self.statusBarStyle = style - self.setNeedsStatusBarAppearanceUpdate() - } - - /// - func getCurrStatusBarStyle() -> UIStatusBarStyle { - return statusBarStyle - } - - /// - func changeToLastStatusBarStyle() { - changeStatusBarStyle(self.previousStatusBarStyle) - } -} - -/// -public extension UIApplication { - /// - class func setController(rootView: Content) where Content : View { - UIApplication.shared.windows.first?.rootViewController?.beginAppearanceTransition(false, animated: false) - UIApplication.shared.windows.first?.rootViewController = DefinedStatusBarController(rootView: AnyView(rootView)) - } - - /// - class func setLastStatusBarStyle() { - if let vc = UIApplication.getKeyWindow()?.rootViewController as? DefinedStatusBarController { - vc.changeToLastStatusBarStyle() - } - } - - /// - class func setStatusBarStyle(_ style: UIStatusBarStyle) { - if let vc = UIApplication.getKeyWindow()?.rootViewController as? DefinedStatusBarController { - vc.changeStatusBarStyle(style) - } - } - - /// - private class func getKeyWindow() -> UIWindow? { - return UIApplication.shared.windows.first{ $0.isKeyWindow } - } -} diff --git a/Sources/DefinedElements/Frameworks/App/iOS/StatusBar/DApp-iOS.StatusBar.swift b/Sources/DefinedElements/Frameworks/App/iOS/StatusBar/DApp-iOS.StatusBar.swift new file mode 100644 index 0000000..bd0eeaf --- /dev/null +++ b/Sources/DefinedElements/Frameworks/App/iOS/StatusBar/DApp-iOS.StatusBar.swift @@ -0,0 +1,49 @@ +#if os(iOS) + +import Foundation +import SwiftUI + +/// [DE Internal] A root view controller alternative to empower the ability of changing status bar style. +internal class DefinedStatusBarController: UIHostingController { + /// The current status bar style holder. + var statusBarStyle: UIStatusBarStyle = .darkContent + + override var preferredStatusBarStyle: UIStatusBarStyle { + return statusBarStyle + } + + /// Change the style holder inside the controller by given style. + /// + /// - Parameter style: The target style. + func changeStatusBarStyle(_ style: UIStatusBarStyle) { + self.statusBarStyle = style + self.setNeedsStatusBarAppearanceUpdate() + } +} + +internal extension UIApplication { + /// [DE Internal] Set the start page of the app and empower the ability of changing status bar style. + /// + /// - Parameter from: The root page (start page for the root view stack) of an app. + class func startApplication(from page: StartPage) where StartPage: DefinedPage { + UIApplication.shared.windows.first?.rootViewController?.beginAppearanceTransition(false, animated: false) + UIApplication.shared.windows.first?.rootViewController = DefinedStatusBarController(rootView: DefinedViewStack(from: page)) + } + + /// [DE Internal] Set the status bar style over the entire app. + /// + /// Handling the status bar style modification should be in `DefinedViewStack` system instead of here. + /// + /// - Parameter style: The target style. + class func setStatusBarStyle(_ style: UIStatusBarStyle) { + if let viewController = UIApplication.getKeyWindow()?.rootViewController as? DefinedStatusBarController { + viewController.changeStatusBarStyle(style) + } + } + + private class func getKeyWindow() -> UIWindow? { + return UIApplication.shared.windows.first{ $0.isKeyWindow } + } +} + +#endif From eac1e4b3791a2faa870ce33d81a4c8c90a0b6b57 Mon Sep 17 00:00:00 2001 From: Lingxi_Li <36816148+lilingxi01@users.noreply.github.com> Date: Mon, 5 Jul 2021 16:51:14 -0400 Subject: [PATCH 26/37] [DVStack] Fix a bug of stack element that the value may be re-init. --- .../Frameworks/View/Stack/DVStack.Element.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Sources/DefinedElements/Frameworks/View/Stack/DVStack.Element.swift b/Sources/DefinedElements/Frameworks/View/Stack/DVStack.Element.swift index 51cbb17..a7dd707 100644 --- a/Sources/DefinedElements/Frameworks/View/Stack/DVStack.Element.swift +++ b/Sources/DefinedElements/Frameworks/View/Stack/DVStack.Element.swift @@ -6,7 +6,8 @@ import SwiftUI /// [DE Internal] A view stack element holder using for `DefinedViewStack`. /// /// - Note: The reason why we do NOT use generic type for `DefinedPage` is we will not be able to define an array of this. -internal struct DefinedViewStackElement : Identifiable, Equatable { +/// - Note: We should use `class` instead of `struct` to avoid unwanted re-init of the values. +internal class DefinedViewStackElement : Identifiable, Equatable { /// [Deprecated] /// /// This may be deprecated. It is used to make sure that the newer page is always above the elder page. From 15874c8c7cba958d525b6e76fcbebf54843d2349 Mon Sep 17 00:00:00 2001 From: Lingxi_Li <36816148+lilingxi01@users.noreply.github.com> Date: Mon, 5 Jul 2021 16:52:05 -0400 Subject: [PATCH 27/37] [DPage-iOS] Support the dynamic modification of status bar style. --- .../Frameworks/Page/iOS/DPage-iOS.Protocol.swift | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Sources/DefinedElements/Frameworks/Page/iOS/DPage-iOS.Protocol.swift b/Sources/DefinedElements/Frameworks/Page/iOS/DPage-iOS.Protocol.swift index 94e3b6d..b833eb8 100644 --- a/Sources/DefinedElements/Frameworks/Page/iOS/DPage-iOS.Protocol.swift +++ b/Sources/DefinedElements/Frameworks/Page/iOS/DPage-iOS.Protocol.swift @@ -208,8 +208,11 @@ extension DefinedPage { } .onAppear(perform: onPageLoaded) .onDisappear(perform: onPageEnded) + .onChange(of: self.statusBarStyle, perform: { status in + DefinedViewManager.find(self).setStatusBarStyle(self.statusBarStyle) + }) .frame(maxWidth: .infinity, maxHeight: .infinity) - .background(DEColor.bg.light.edgesIgnoringSafeArea(.all)) + .background(DEColor.bg.light.edgesIgnoringSafeArea(.all)) // TODO: customize page background API } } From 242182a7ba2066148686c05861105593263e1261 Mon Sep 17 00:00:00 2001 From: Lingxi_Li <36816148+lilingxi01@users.noreply.github.com> Date: Mon, 5 Jul 2021 17:02:10 -0400 Subject: [PATCH 28/37] [DVManager] `DVManagerMain`: Re-organize the accessibility of the class and its functions. --- .../Frameworks/View/Manager/DVManager.Main.swift | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Sources/DefinedElements/Frameworks/View/Manager/DVManager.Main.swift b/Sources/DefinedElements/Frameworks/View/Manager/DVManager.Main.swift index 4a20d6c..ee09711 100644 --- a/Sources/DefinedElements/Frameworks/View/Manager/DVManager.Main.swift +++ b/Sources/DefinedElements/Frameworks/View/Manager/DVManager.Main.swift @@ -8,7 +8,7 @@ import SwiftUI /// Should be attached to a `DefinedViewStack` for proper using. /// /// - TODO: Reconsider the use case of making this public. -class DefinedViewManager { +internal class DefinedViewManager { /// [DE Internal] Global instance of `DefinedViewManager`. static var instance = DefinedViewManager() @@ -28,7 +28,7 @@ class DefinedViewManager { /// - stackManager: The manager property of `DeefinedViewStack`. /// - parent: The parent stack where this stack stands on. /// - under: The page that this stack stands on. This is optional, but highly recommanded. If you registered a stack without associating the parent page `under` (not the parent stack), this stack and its pages will not be collected automatically when the parent page is destroyed. - internal static func registerStack( + static func registerStack( manager stackManager: DefinedViewStackManager, parent: DefinedViewManagerRootElement, under: DefinedViewManagerElement? = nil @@ -51,7 +51,7 @@ class DefinedViewManager { /// [DE Internal] Unregister a `DefinedViewStack` into global manager. /// /// - Parameter root: The root element of the stack you want to unregister. You can get the root element by `DefinedViewManager.find(page).parent` where `page` could be any page on this stack. - internal static func unregisterStack(root: DefinedViewManagerRootElement) { + static func unregisterStack(root: DefinedViewManagerRootElement) { DefinedViewManager.instance.hierarchy.removeAll(where: { curr in if curr == root { for page in curr.hierarchy { @@ -69,7 +69,7 @@ class DefinedViewManager { /// - Returns: The page manager corresponding to the given page id. /// /// - Note: It will return a dummy and generate a warning if we cannot find it in order to not break the program. - internal static func find(_ id: UUID) -> DefinedViewManagerElement { + static func find(_ id: UUID) -> DefinedViewManagerElement { guard let element = DefinedViewManager.instance.pageMap[id] else { DefinedWarning.send(from: "DVManager", "cannot find the page manager by given page id.") return .dummy @@ -83,7 +83,7 @@ class DefinedViewManager { /// - Returns: The page manager corresponding to the given page. /// /// - Note: It will return a dummy and generate a warning if we cannot find it in order to not break the program. - internal static func find(_ page: Page) -> DefinedViewManagerElement where Page: DefinedPage { + static func find(_ page: Page) -> DefinedViewManagerElement where Page: DefinedPage { guard let element = DefinedViewManager.instance.pageMap[page.id] else { DefinedWarning.send(from: "DVManager", "cannot find the page manager by given DefinedPage.") return .dummy @@ -99,7 +99,7 @@ class DefinedViewManager { /// - id: The id of the `DefinedPage` being registered. /// - parent: The root element of the stack where this page is in. /// - Returns: The newly created page manager corresponding to the page that is just registered. - internal static func registerPage(id: UUID, parent: DefinedViewManagerRootElement) -> DefinedViewManagerElement { + static func registerPage(id: UUID, parent: DefinedViewManagerRootElement) -> DefinedViewManagerElement { let pageElement = DefinedViewManagerElement(id: id, parent: parent) DefinedViewManager.instance.pageMap[id] = pageElement return pageElement @@ -111,7 +111,7 @@ class DefinedViewManager { /// /// - Parameters: /// - id: The id of the `DefinedPage` being unregistered. - internal static func unregisterPage(id: UUID) { + static func unregisterPage(id: UUID) { guard let page = DefinedViewManager.instance.pageMap.removeValue(forKey: id) else { DefinedWarning.send(from: "DVManager", "the page being unregistered does not exist!") return From 84a608424f7a8ffe92264f67362cd31cb917dfe7 Mon Sep 17 00:00:00 2001 From: Lingxi_Li <36816148+lilingxi01@users.noreply.github.com> Date: Tue, 6 Jul 2021 14:23:44 -0400 Subject: [PATCH 29/37] [DPage-iOS] Documented the `DefinedPage`. --- .../Page/iOS/DPage-iOS.Protocol.swift | 283 +++++++++++++----- 1 file changed, 213 insertions(+), 70 deletions(-) diff --git a/Sources/DefinedElements/Frameworks/Page/iOS/DPage-iOS.Protocol.swift b/Sources/DefinedElements/Frameworks/Page/iOS/DPage-iOS.Protocol.swift index b833eb8..a8daa2f 100644 --- a/Sources/DefinedElements/Frameworks/Page/iOS/DPage-iOS.Protocol.swift +++ b/Sources/DefinedElements/Frameworks/Page/iOS/DPage-iOS.Protocol.swift @@ -1,20 +1,16 @@ #if os(iOS) -import Foundation import SwiftUI -/// DefinedPage - 页面基础协议 @ DefinedElements +/// [DE] A protocol for building a page that fits into page system. /// -/// 这套页面协议目前来说能够和原生`View`做出区分,能够基于页面框架实现更多专属于页面的功能,以及页面的快速跳转等。 -/// 这都是原生无法实现或者极其繁琐的。 +/// To distinguish between a Page (an entire area showing some contents) and a View (a content component), we define the `DefinedPage` so that the page can have its special APIs specifying for a page and work much better with the view stack system. /// -/// 无法保证渲染效率能抗衡原生,目前暂时没有纯渲染层的优化能力。 +/// We empower the page the ability of linking to another page, jumping to another page, back to the previous page, etc. Those are not even possible if we mix the Page and View up because a View should not have such things directly. /// -/// - Note: 若您使用`Defined`配套控件,则该协议非常重要。 +/// - Note: Currently, we have done the optimization on algorithm, but I am not able to do the performance optimization on raw executing layer and rendering layer yet (still learning). I am planning to finish all performance optimizations in our 2.0 release. /// -/// - Important: `DefinedViewStack`强制依赖于该页面框架! -/// -/// - TODO: StatusBarStyle seemless embed. +/// - Important: `DefinedViewStack` really relys on this protocol! public protocol DefinedPage : View, ModifiableStruct { // MARK: - Protocol - Main Part @@ -24,43 +20,94 @@ public protocol DefinedPage : View, ModifiableStruct { /// This is optional, but highly recommanded because you will lose the control without it. /// Only NOT implement it when this page is used for an only-one-page stack. /// - /// - Important: If you do not implement this on your page, you may NOT be able to control the router! - /// Even the short-hand functions like `link(to:)` and `jump(to:)` will all be disabled without controller! + /// You only need to do a simple work: + /// + /// ``` swift + /// var controller: DefinedPageController = .init() + /// ``` + /// + /// - Important: If you do not implement this on your page, + /// you may NOT be able to control the router! + /// Even the short-hand functions like ``link(to:)`` and ``jump(to:)`` will all be disabled without controller! var controller: DefinedPageController { get } + /// The page id that will be unique universally. /// + /// Being used on determining the page identity in `DefinedViewStack` system and `DefinedViewManager` system. var id: UUID { get } + /// The status bar style of this page. + /// + /// The default value is `.darkContent` if developer does not define this variable locally. + /// You will not be able to do any change without defining it locally. + /// + /// You can define this variable as the status bar style on starting up this page. + /// + /// ``` swift + /// // make status bar content white on starting up the page. + /// var statusBarStyle: UIStatusBarStyle = .lightContent + /// ``` + /// + /// You can make this variable `@State` to modify the status bar style dynamically. /// + /// ``` swift + /// // make status bar content white on starting up the page + /// // and can be dynamically modify during the runtime. + /// @State var statusBarStyle: UIStatusBarStyle = .lightContent + /// + /// func run() { + /// // make the status bar content black + /// self.statusBarStyle = .darkContent + /// } + /// ``` var statusBarStyle: UIStatusBarStyle { get } + /// [DE ShouldNotUse] The body of the page view. + /// + /// You should implement the body on `main` instead of here. + /// Do NOT implement this variable on your `DefinedPage`! + /// + /// - Important: You should NOT implement anything on `body`! It will break the functionalities of everything (it is due to the original SwiftUI View system, we are planning to build our own RawView system in 2.0 release). + var body: Self.Body { get } + + /// The type of view representing the content of this page. /// + /// This will be associated automatically when you implement the required property ``main``. associatedtype Content: View - /// 页面主体 + /// The main content of this page. Need to be implemented. @ViewBuilder var main: Content { get } - /// 在页面加载开始前所执行的事项 + /// Execute before the page starts loading its content. /// - /// - Important: 非强制要求调用 + /// - Note: You do NOT need to implement it if you do not execute anything at this phase. func beforePageLoading() -> Void - /// 当页面加载完后所执行的事项 + /// Execute after the page finishes loading all of its content. /// - /// - Important: 非强制要求调用 + /// - Note: You do NOT need to implement it if you do not execute anything at this phase. func onPageLoaded() -> Void - /// 当页面结束时所执行的事项 + /// Execute right before the page has been destroyed and collected. /// - /// - Important: 非强制要求调用 + /// - Note: You do NOT need to implement it if you do not execute anything at this phase. func onPageEnded() -> Void } public extension DefinedPage { + /// Inactive `DefinedPageController`. + /// + /// If you do NOT define `controller` property locally, the PageController will be disabled! + /// Even though it is still accessible (we have no way to hide it out), it will not work at all. var controller: DefinedPageController { DefinedPageController() } + /// The page id that will be unique universally. + /// + /// Being used on determining the page identity in `DefinedViewStack` system and `DefinedViewManager` system. + /// + /// - Note: The actual page id is stored inside the PageController since we cannot have stored property in the extension. This is a getter so if developer does not specify another stored `id` in the page, the page can still have a stored ID to be identified. var id: UUID { controller.id } @@ -68,27 +115,26 @@ public extension DefinedPage { // MARK: - LifeCycle -/// 核心生命的方法 public extension DefinedPage { - /// 在页面加载开始前所执行的事项 < 默认方法 > + /// Execute before the page starts loading its content. Do nothing. /// - /// - Important: 非强制要求调用 + /// - Note: You do NOT need to implement it if you do not execute anything at this phase. func beforePageLoading() -> Void { // do nothing. return } - /// 当页面加载完后所执行的事项 < 默认方法 > + /// Execute after the page finishes loading all of its content. Do nothing. /// - /// - Important: 非强制要求调用 + /// - Note: You do NOT need to implement it if you do not execute anything at this phase. func onPageLoaded() -> Void { // do nothing. return } - /// 当页面结束时所执行的事项 < 默认方法 > + /// Execute right before the page has been destroyed and collected. Do nothing. /// - /// - Important: 非强制要求调用 + /// - Note: You do NOT need to implement it if you do not execute anything at this phase. func onPageEnded() -> Void { // do nothing. return @@ -97,92 +143,171 @@ public extension DefinedPage { // MARK: - StatusBarStyle -/// StatusBarStyle 默认值 public extension DefinedPage { - /// 状态栏颜色 < 外部参数 > + /// The status bar style of this page. + /// + /// The default value is `.darkContent` if developer does not define this variable locally. + /// You will not be able to do any change without defining it locally. /// - /// - Note: 默认为黑色内容 + /// For more information, check the `statusBarStyle` property in ``DefinedPage`` protocol. var statusBarStyle: UIStatusBarStyle { .darkContent } } -// MARK: - Link - link() +// MARK: - ShortHand - link() -/// DefinedLink 子页面跳转功能 public extension DefinedPage { - /// 纯代码页面跳转 < 前端函数 > + /// [DE] Link to another page. /// - /// 使用时在`DefinedPage`内直接调用即可 + /// Link means going to the next level (sub-page/child-page). + /// The current page will be the ancestor of the target page. /// + /// When you need to link to another page, just call it in your `DefinedPage`: + /// + /// ``` swift + /// link(to: TargetPage()) + /// ``` + /// + /// You can do something like "link when clicking": + /// + /// ``` swift + /// Button("fake button") { + /// // link to TargetPage when clicking the "fake button". /// link(to: TargetPage()) + /// } + /// ``` /// - /// - Requires: 所有页面必须基于`DefinedPage`框架 + /// - Requires: The target page should conform to ``DefinedPage`` protocol. /// - /// - Parameter target: 目标页面 - func link(to target: Page) where Page : DefinedPage { - let pageElement = DefinedViewManager.find(self) - pageElement.link(to: target) + /// - Parameters: + /// - target: The target page. + func link(to target: Page) where Page: DefinedPage { + DefinedViewManager.find(self).link(to: target) } - /// 纯代码页面跳转 带延时 < 前端函数 > + /// [DE] Link to another page after a few seconds. /// - /// 使用时在`DefinedPage`内直接调用即可 + /// Link means going to the next level (sub-page/child-page). + /// The current page will be the ancestor of the target page. /// + /// When you need to link to another page, just call it in your `DefinedPage`: + /// + /// ``` swift + /// // link to TargetPage after 1 sec + /// link(to: TargetPage(), delay: 1.0) /// ``` - /// link(to: TargetPage()) + /// + /// You can do something like "link when clicking": + /// + /// ``` swift + /// Button("fake button") { + /// // link to TargetPage in 1 sec after clicking + /// link(to: TargetPage(), delay: 1.0) + /// } /// ``` /// - /// - Requires: 所有页面必须基于`DefinedPage`框架 + /// - Requires: The target page should conform to ``DefinedPage`` protocol. /// /// - Parameters: - /// - target: 目标页面 - /// - delay: 延时(秒) - func link(to target: Page, delay: Double) where Page : DefinedPage { + /// - target: The target page. + /// - delay: The time you want to delay (in second). + func link(to target: Page, delay: Double) where Page: DefinedPage { if delay >= 0.0 { DispatchQueue.main.asyncAfter(deadline: .now() + delay) { link(to: target) } } } - - /// 计时器 < 前端函数 > - /// - /// - Parameters: - /// - delay: 延时(秒) - /// - execute: 执行内容 - func timer(delay: Double, execute: @escaping @convention(block) () -> Void) { - DispatchQueue.main.asyncAfter(deadline: .now() + delay) { - execute() - } - } } -// MARK: - Link - back() +// MARK: - ShortHand - back() -/// DefinedLink 子页面返回功能 extension DefinedPage { - /// 执行返回操作 < 前端函数 > + /// [DE] Back to the previous page. + /// + /// Back means pop up the current page and step back to the previous level. + /// The current page will be destroyed and collected. + /// + /// When you need to go back to the previous page, just call it in your `DefinedPage`: + /// + /// ``` swift + /// // back to the previous page when executing + /// back() + /// ``` + /// + /// You can do something like "back button": + /// + /// ``` swift + /// Button("back button") { + /// // back to the previous page when clicking + /// back() + /// } + /// ``` public func back() { DefinedViewManager.find(self).back() } } -// MARK: - Scene - jump() +// MARK: - ShortHand - jump() -/// DefinedScene 根页面跳转功能 public extension DefinedPage { - /// 纯代码根页面跳转 < 前端函数 > + /// [DE] Jump to another page. + /// + /// Jump means cleaning the entire view stack and put target page at the new first level. + /// The current page will be destroyed and collected. + /// And you are not able to use `back()` because the target page becomes the first level page and there is no way to go back from the first level page. + /// + /// When you need to jump to another page, just call it in your `DefinedPage`: /// - /// - Parameter target: 目标页面 - func jump(to target: Page) where Page : DefinedPage { + /// ``` swift + /// // replace all views in stack with the target page + /// jump(to: TargetPage()) + /// ``` + /// + /// You can do something like "jump when clicking": + /// + /// ``` swift + /// Button("fake button") { + /// // jump to the target page when clicking + /// jump(to: TargetPage()) + /// } + /// ``` + /// + /// - Requires: The target page should conform to ``DefinedPage`` protocol. + /// + /// - Parameters: + /// - target: The target page. + func jump(to target: Page) where Page: DefinedPage { DefinedViewManager.find(self).jump(to: target) } - /// 纯代码根页面跳转 带延时 < 前端函数 > + /// [DE] Jump to another page after a few seconds. + /// + /// Jump means cleaning the entire view stack and put target page at the new first level. + /// The current page will be destroyed and collected. + /// And you are not able to use `back()` because the target page becomes the first level page and there is no way to go back from the first level page. + /// + /// When you need to jump to another page, just call it in your `DefinedPage`: + /// + /// ``` swift + /// // jump to the target page after 1 sec + /// jump(to: TargetPage(), delay: 1.0) + /// ``` + /// + /// You can do something like "jump when clicking": + /// + /// ``` swift + /// Button("fake button") { + /// // jump to the target page in 1 sec after clicking + /// jump(to: TargetPage(), delay: 1.0) + /// } + /// ``` + /// + /// - Requires: The target page should conform to ``DefinedPage`` protocol. /// /// - Parameters: - /// - target: 目标页面 - /// - delay: 延时(秒) - func jump(to target: Page, delay: Double) where Page : DefinedPage { + /// - target: The target page. + /// - delay: The time you want to delay (in second). + func jump(to target: Page, delay: Double) where Page: DefinedPage { if delay >= 0.0 { DispatchQueue.main.asyncAfter(deadline: .now() + delay) { jump(to: target) @@ -191,17 +316,35 @@ public extension DefinedPage { } } +// MARK: - ShortHand - Timer + +extension DefinedPage { + /// [DE] A timer. Execute as a function. + /// + /// - Parameters: + /// - delay: The time you want to delay (in second). + /// - execute: The code you want to execute after a few seconds. + public func timer(delay: Double, execute: @escaping @convention(block) () -> Void) { + DispatchQueue.main.asyncAfter(deadline: .now() + delay) { + execute() + } + } +} + // MARK: - Core - Process -/// 核心Body的处理 extension DefinedPage { - /// 根视图 < 内部变量 > + /// [DE ShouldNotUse] The body of the page view. + /// + /// You should implement the body on `main` instead of here. + /// + /// - Important: You should NOT implement anything on `body`! It will break the functionalities of everything (it is due to the original SwiftUI View system, we are planning to build our own RawView system in 2.0 release). public var body: some View { beforePageLoading() return process() } - /// [DE Private] + /// [DE Private] Process the page body. @ViewBuilder private func process() -> some View { ZStack { self.main From c778af6559a372b3368b38c3be6ee39943141d06 Mon Sep 17 00:00:00 2001 From: Lingxi_Li <36816148+lilingxi01@users.noreply.github.com> Date: Tue, 6 Jul 2021 14:41:16 -0400 Subject: [PATCH 30/37] [DPage-iOS] `DPage-iOS.Controller`: finish the initial development with `link(to:)`, `jump(to:)`, and `back()`. --- .../iOS/Support/DPage-iOS.Controller.swift | 55 ++++++++++++++++++- 1 file changed, 52 insertions(+), 3 deletions(-) diff --git a/Sources/DefinedElements/Frameworks/Page/iOS/Support/DPage-iOS.Controller.swift b/Sources/DefinedElements/Frameworks/Page/iOS/Support/DPage-iOS.Controller.swift index 0c65fcd..81cb827 100644 --- a/Sources/DefinedElements/Frameworks/Page/iOS/Support/DPage-iOS.Controller.swift +++ b/Sources/DefinedElements/Frameworks/Page/iOS/Support/DPage-iOS.Controller.swift @@ -1,12 +1,61 @@ import Foundation -/// [DE] +/// [DE] A controller managing the page and all of its functionalities. +/// +/// You should only use this on your `DefinedPage`, like: +/// +/// ``` swift +/// var controller: DefinedPageController = .init() +/// ``` public class DefinedPageController { + /// The page id associated with the controlled page. /// + /// Should be internal only. internal let id: UUID = UUID() - /// + /// [DE] Simply create a normal PageController. public init() { - // + // do nothing. + } + + /// [DE] Link to another page. + /// + /// Link means going to the next level (sub-page/child-page). + /// The current page will be the ancestor of the target page. + /// + /// - Note: Please be aware that when you are executing `link(to:)`, you are adding a page onto the stack that this page is at. It is NOT adding the new page directly above the this page (if you mean to insert, then it is NOT). So when you are executing this outside the current page, you need to make sure where the target page will be. + /// + /// - Requires: The target page should conform to ``DefinedPage`` protocol. + /// + /// - Parameters: + /// - target: The target page. + public func link(to target: Page) where Page: DefinedPage { + DefinedViewManager.find(self.id).link(to: target) + } + + /// [DE] Back to the previous page. + /// + /// Back means pop up the current page and step back to the previous level. + /// The current page will be destroyed and collected. + /// + /// - Note: Please be aware that when you are executing `back()`, you are poping the top page of the stack that this page is at. It is NOT poping current page wherever it is. So when you are executing this outside the current page, you need to make sure which page will be popped. + public func back() { + DefinedViewManager.find(self.id).back() + } + + /// [DE] Jump to another page. + /// + /// Jump means cleaning the entire view stack and put target page at the new first level. + /// The current page will be destroyed and collected. + /// And you are not able to use `back()` because the target page becomes the first level page and there is no way to go back from the first level page. + /// + /// - Note: Please be aware that when you are executing `jump(to:)`, you are putting target page to the stack that this page is at and replace all old pages. The risk may be that the page you are at will be destroyed and collected since the original stack (whatever embeded or not) is cleaned. And if you are doing this to another stack that differs from the stack you are at, you also have to know that the page may not display per following the hierarchy, but it actually executed. So when you are executing this outside the current page, you need to make sure where the target page will be. + /// + /// - Requires: The target page should conform to ``DefinedPage`` protocol. + /// + /// - Parameters: + /// - target: The target page. + public func jump(to target: Page) where Page: DefinedPage { + DefinedViewManager.find(self.id).jump(to: target) } } From a7954e434091435f6536844c482e422160b4718b Mon Sep 17 00:00:00 2001 From: Lingxi_Li <36816148+lilingxi01@users.noreply.github.com> Date: Sun, 11 Jul 2021 13:22:08 -0400 Subject: [PATCH 31/37] [DVStack] Documented the `DVStackManager`. --- .../View/Stack/DVStack.Manager.swift | 96 +++++++++++++++---- 1 file changed, 79 insertions(+), 17 deletions(-) diff --git a/Sources/DefinedElements/Frameworks/View/Stack/DVStack.Manager.swift b/Sources/DefinedElements/Frameworks/View/Stack/DVStack.Manager.swift index 8ff6736..028a917 100644 --- a/Sources/DefinedElements/Frameworks/View/Stack/DVStack.Manager.swift +++ b/Sources/DefinedElements/Frameworks/View/Stack/DVStack.Manager.swift @@ -1,23 +1,34 @@ #if os(iOS) -import Foundation import SwiftUI /// [DE Internal] A core manager controlling the view stack. /// /// Should be attached to a `DefinedViewStack` for proper using. /// One `StackManager` per `DefinedViewStack`. -class DefinedViewStackManager : ObservableObject { +/// +/// - TODO: Support `onAnimated` and optimize the display procedure algorithm. +internal class DefinedViewStackManager : ObservableObject { + /// The name of the view stack. /// + /// It should be the same with the `name` property in corresponding `DefinedViewStack`. + /// For identification purpose (per page). var name: String + /// Should this view stack modify the status bar on changing the page? /// + /// Sometimes, our view stack is not showing fullscreen. In this case, we should not modify the status bar style per the page is changed or the status bar style has been set manually because the page has not covered the status bar area. var shouldUseStatusBar: Bool - /// [Deprecated] - @Published var onAnimated: Bool = true // TODO: 下层View仅在侧滑时出现 + /// [Deprecated] A flag showing if the view stack is animating. + /// + /// It should only be `true` when we have a sub-page animates. + /// Should not be `true` when we have a root animation. + @Published var onAnimated: Bool = true - /// [Deprecated] + /// [Deprecated] A flag showing if the root of a view stack is animating. + /// + /// It should only be `true` when we have a root animation like `jump` or root `swap`. @Published var onRootAnimated: Bool = false /// All elements in the view stack. @@ -52,26 +63,39 @@ class DefinedViewStackManager : ObservableObject { /// - TODO: We are not using the overall opacity for now. @Published var overallOpacity: Double = 1.0 - /// [DE Internal] - init(name: String, statusBar: Bool) { + /// [DE Internal] Create the view stack manager with corresponding name and status bar flag. + /// + /// - Parameters: + /// - name: The name of the view stack. + /// - statusBar: Should this view stack modify the status bar on changing the page? + internal init(name: String, statusBar: Bool) { self.name = name self.shouldUseStatusBar = statusBar } + /// The view stack container. /// + /// It will automatically sync with `elements` property when view stack has been changed. internal var viewStack = DefinedViewStackContainer() { didSet { + // get update from the container. elements = viewStack.getStack() } } + /// [DE Internal] Manual push maneuver. /// - func push(_ target: Page) where Page: DefinedPage { + /// Push a page element onto the view stack. + /// + /// - Parameter target: The sub-page going to be pushed. + internal func push(_ target: Page) where Page: DefinedPage { if self.shouldUseStatusBar { UIApplication.setStatusBarStyle(target.statusBarStyle) } + // push a 0 first so the view can be updated safely. self.offsets.append(0) + withAnimation(.easeInOut(duration: 0.30)) { self.viewStack.push(DefinedViewStackElement(target)) @@ -81,8 +105,10 @@ class DefinedViewStackManager : ObservableObject { } } + /// [DE Internal] Manual pop maneuver. /// - func pop() { + /// Pop a page element onto the view stack. + internal func pop() { if self.viewStack.stack.count > 1 { if self.shouldUseStatusBar { UIApplication.setStatusBarStyle(self.elements[self.elements.count - 2].statusBarStyle) @@ -90,22 +116,32 @@ class DefinedViewStackManager : ObservableObject { withAnimation(.easeInOut(duration: 0.25)) { if (self.offsets[self.offsets.count - 2] == 0) { + // immediately pop when we clicked the back button (no drag gesture). self.viewStack.pop() } else { - withAnimation(.easeInOut(duration: 0.26)) { + // this is an animation for the case that we drag the view. + + // BUG: there is still a time gap between popping up the top page and correctly positioning the second top page into the right place. + + withAnimation(.easeInOut(duration: 0.27)) { self.viewStack.pop() } } + // reset the offset and get ready for next navigation action. self.offsets[self.offsets.count - 2] = 0 } self.offsets.removeLast() } else { - // ERROR: should NOT be able to pop right now!!! + // ERROR: should NOT be able to pop right now!!! We only have 1 page left! } } + /// [DE Internal] Manual replace maneuver. + /// + /// Replace all pages in the stack with a new page as the new root. /// - func jump(_ target: Page) where Page: DefinedPage { + /// - Parameter target: The page going to replace others. + internal func jump(_ target: Page) where Page: DefinedPage { if self.shouldUseStatusBar { UIApplication.setStatusBarStyle(target.statusBarStyle) } @@ -116,21 +152,27 @@ class DefinedViewStackManager : ObservableObject { } } - /// - func renew() { + /// Renew the view stack (remove all page elements and place an original 0 to offset in order to avoid bugging when pushing the first page). + private func renew() { self.viewStack.removeAll() self.offsets = [0] } + /// [DE Internal] Set the status bar style of top page manually. /// - func setStatusBarStyle(_ style: UIStatusBarStyle) { + /// - Parameter style: The target style. + internal func setStatusBarStyle(_ style: UIStatusBarStyle) { self.elements[self.elements.count - 1].statusBarStyle = style UIApplication.setStatusBarStyle(style) } + /// [DE Internal] Set the status bar style of given page (UUID) manually. /// + /// - Parameters: + /// - pageId: The UUID of the page you want to change. + /// - style: The target style. func setStatusBarStyle(pageId: UUID, style: UIStatusBarStyle) { - let index = self.elements.firstIndex(where: { elem in return elem.id == pageId }) + let index = self.elements.firstIndex(where: { $0.id == pageId }) if index != nil { self.elements[index!].statusBarStyle = style if (index == self.elements.count - 1) { @@ -144,14 +186,27 @@ class DefinedViewStackManager : ObservableObject { // MARK: - StackManager Pool extension DefinedViewStackManager { + /// The UUID correspond to the root view stack. internal static let rootId: UUID = UUID() + /// The global pool of `DefinedViewStackManager`. private static var pool: [UUID: [String: DefinedViewStackManager]] = [:] + /// [DE Internal] Get corresponding `DefinedViewStackManager` from the pool. + /// + /// - Parameters: + /// - name: The name of the stack. + /// - pageId: The page id (where the stack is AT, not the top page of the stack). + /// - shouldUseStatusBar: Should this stack modify the status bar style during navigation? + /// - rebuild: Should we recreate a new `DVStackManager` if there already exists one? + /// + /// - Note: If we already have a corresponding `DVStackManager` and you choose not to rebuild one, the `shouldUseStatusBar` will NOT be modified. It will keep the old configuration. + /// + /// - Returns: The corresponding `DVStackManager`. internal static func get( name: String, pageId: UUID, - shouldUseStatusBar: Bool, + shouldUseStatusBar: Bool = false, rebuild: Bool = false ) -> DefinedViewStackManager { if pool[pageId] == nil { @@ -164,6 +219,13 @@ extension DefinedViewStackManager { return pool[pageId]![name]! } + /// [DE Internal] Check if we should register this stack (check if it exists). + /// + /// - Parameters: + /// - name: The name of the stack. + /// - pageId: The page id (where the stack is AT, not the top page of the stack). + /// + /// - Returns: True for we should register it. False for we should not re-register it. internal static func shouldRegister(name: String, pageId: UUID) -> Bool { return pool[pageId]?[name] == nil } From 51a78cf7bdb4035b5443fe14988a4327fda92da7 Mon Sep 17 00:00:00 2001 From: Lingxi_Li <36816148+lilingxi01@users.noreply.github.com> Date: Sun, 11 Jul 2021 13:52:10 -0400 Subject: [PATCH 32/37] [DVStack] Documented the `DVStackDocker`. --- .../View/Stack/DVStack.Docker.swift | 42 ++++++++++++------- 1 file changed, 27 insertions(+), 15 deletions(-) diff --git a/Sources/DefinedElements/Frameworks/View/Stack/DVStack.Docker.swift b/Sources/DefinedElements/Frameworks/View/Stack/DVStack.Docker.swift index 99ad16f..33ffa18 100644 --- a/Sources/DefinedElements/Frameworks/View/Stack/DVStack.Docker.swift +++ b/Sources/DefinedElements/Frameworks/View/Stack/DVStack.Docker.swift @@ -1,24 +1,27 @@ #if os(iOS) -import Foundation -import UIKit +import SwiftUI -/// -public struct DefinedViewStackDocker : DefinedPotentialWarning { - public var name: String = "DVStackDocker" +/// [DE Internal] A docker connecting between `DVStackManager` and `DVManagerElement`. +internal struct DefinedViewStackDocker : DefinedPotentialWarning { + internal var name: String = "DVStackDocker" + /// The corresponding `DVStackManager`. + /// + /// - Note: Was planned to use it directly on building the page so it kept optional. Don't worry! For now we forced an required `DVStackManager` in `init(manager:)`. internal var manager: DefinedViewStackManager? - public init() { - self.manager = nil - } - + /// [DE Internal] Create a docker with given `DVStackManager`. + /// + /// - Parameter manager: The corresponding `DVStackManager`. internal init(manager: DefinedViewStackManager) { self.manager = manager } + /// [DE Internal] Link to the target page. /// - public func link(to target: Page) where Page: DefinedPage { + /// - Parameter target: The target page. + internal func link(to target: Page) where Page: DefinedPage { if (self.manager != nil) { manager!.push(target) } else { @@ -26,8 +29,10 @@ public struct DefinedViewStackDocker : DefinedPotentialWarning { } } + /// [DE Internal] Jump to the target page. /// - public func jump(to target: Page) where Page: DefinedPage { + /// - Parameter target: The target page. + internal func jump(to target: Page) where Page: DefinedPage { if (self.manager != nil) { manager!.jump(target) } else { @@ -35,8 +40,10 @@ public struct DefinedViewStackDocker : DefinedPotentialWarning { } } + /// [DE Internal] Swap with the target page. /// - public func swap(with target: Page) where Page: DefinedPage { + /// - Parameter target: The target page. + internal func swap(with target: Page) where Page: DefinedPage { if (self.manager != nil) { // } else { @@ -44,8 +51,8 @@ public struct DefinedViewStackDocker : DefinedPotentialWarning { } } - /// - public func back() { + /// [DE Internal] Go back to previous page. + internal func back() { if (self.manager != nil) { manager!.pop() } else { @@ -53,7 +60,12 @@ public struct DefinedViewStackDocker : DefinedPotentialWarning { } } - public func setStatusBarStyle(pageId: UUID, style: UIStatusBarStyle) { + /// [DE Internal] Set status bar style by given page id and target style. + /// + /// - Parameters: + /// - pageId: The id of the page that going to change its status bar style. + /// - style: The target style. + internal func setStatusBarStyle(pageId: UUID, style: UIStatusBarStyle) { if (self.manager != nil) { manager!.setStatusBarStyle(pageId: pageId, style: style) } else { From e47557088468b0a10f5c57732f2a2b9486d33e37 Mon Sep 17 00:00:00 2001 From: Lingxi_Li <36816148+lilingxi01@users.noreply.github.com> Date: Sun, 11 Jul 2021 13:52:25 -0400 Subject: [PATCH 33/37] [DVStack] Documented the `DVStackContainer`. --- .../View/Stack/DVStack.Container.swift | 21 ++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/Sources/DefinedElements/Frameworks/View/Stack/DVStack.Container.swift b/Sources/DefinedElements/Frameworks/View/Stack/DVStack.Container.swift index a1857b5..634706d 100644 --- a/Sources/DefinedElements/Frameworks/View/Stack/DVStack.Container.swift +++ b/Sources/DefinedElements/Frameworks/View/Stack/DVStack.Container.swift @@ -2,35 +2,42 @@ import Foundation -/// [DE Internal] -struct DefinedViewStackContainer { - /// +/// [DE Internal] A view stack container instance holding page elements. +internal struct DefinedViewStackContainer { + /// The stack instance holding page elements. var stack = [DefinedViewStackElement]() + /// The count of elements in this container. var count: Int { return stack.count } + /// [DE Internal] Get the stack directly. /// - func getStack() -> [DefinedViewStackElement] { + /// - Note: For `elements` property updating purpose only! + internal func getStack() -> [DefinedViewStackElement] { return stack } - /// + /// [DE Internal] Push a page element onto the stack. mutating func push(_ target: DefinedViewStackElement) { guard stack.firstIndex(of: target) == nil else { - // 不同层级间重复出现的Page + // if there exists a page element having same id with target page, it means that something went wrong. + // we should never push the same page twice (we can have different instances of the same page, but no exactly same page instance). return } stack.append(target) } + /// [DE Internal] Pop a page element onto the stack. /// + /// We do NOT return the element since it becomes totally useless once it has been popped. + /// There makes no sense to get it back outside. mutating func pop() { _ = stack.popLast() } - /// + /// [DE Internal] Clean the entire container. mutating func removeAll() { stack.removeAll() } From 1849d8a938223415277c266f4218455130a4af9f Mon Sep 17 00:00:00 2001 From: Lingxi_Li <36816148+lilingxi01@users.noreply.github.com> Date: Sun, 11 Jul 2021 14:21:08 -0400 Subject: [PATCH 34/37] [DVManager] Documented the `DVManagerElement` and more. --- .../Manager/Support/DVManager.Element.swift | 105 +++++++++++++++--- 1 file changed, 91 insertions(+), 14 deletions(-) diff --git a/Sources/DefinedElements/Frameworks/View/Manager/Support/DVManager.Element.swift b/Sources/DefinedElements/Frameworks/View/Manager/Support/DVManager.Element.swift index 79693fd..bc5ab46 100644 --- a/Sources/DefinedElements/Frameworks/View/Manager/Support/DVManager.Element.swift +++ b/Sources/DefinedElements/Frameworks/View/Manager/Support/DVManager.Element.swift @@ -1,47 +1,67 @@ #if os(iOS) -import Foundation -import UIKit +import SwiftUI -/// [DE Internal] +/// [DE Internal] A ViewManager element representing a page instance. internal class DefinedViewManagerElement : Equatable { + /// The id of the corresponding page. var id: UUID + /// The parent stack's ViewManager element. var parent: DefinedViewManagerRootElement + /// The stacks that are hold in this page. var stacks: [DefinedViewManagerRootElement] = [] - init(id: UUID, parent: DefinedViewManagerRootElement) { + /// [DE Internal] Create a `DVManagerElement`. + /// + /// - Parameters: + /// - id: The page id. + /// - parent: The parent stack's ViewManager element. + internal init(id: UUID, parent: DefinedViewManagerRootElement) { self.id = id self.parent = parent } + /// [DE Internal] Link. func link(to target: Page) where Page: DefinedPage { parent.link(to: target) } + /// [DE Internal] Jump. func jump(to target: Page) where Page: DefinedPage { parent.jump(to: target) } + /// [DE Internal] Swap. func swap(with target: Page) where Page: DefinedPage { parent.swap(with: target) } + /// [DE Internal] Back. func back() { parent.back() } + /// [DE Internal] Set status bar style of this page. func setStatusBarStyle(_ style: UIStatusBarStyle) { parent.docker?.setStatusBarStyle(pageId: self.id, style: style) } + /// [DE Internal] Register a new stack under this page. + /// + /// - Parameter root: The ViewManager element of the new stack (not a page!). func register(_ root: DefinedViewManagerRootElement) { self.stacks.append(root) } + /// [DE Internal] Unregister all stacks under current page. + /// + /// It should be called when the current page is destroyed or closed. + /// For garbage clean up purpose. func unregister() { for r in stacks { + // run unregister stack procedure for every stack under the current page. DefinedViewManager.unregisterStack(root: r) } } @@ -52,10 +72,19 @@ internal class DefinedViewManagerElement : Equatable { } extension DefinedViewManagerElement { + /// The dummy page's ViewManager element. + /// + /// Being used when we cannot find the corresponding page's ViewManager element. Being designed to avoid a bunch of optional wrappers and to throw no error in order to keep all other functions work fine. + /// + /// When we cannot find the ViewManager element for a page by whatever reason, we will return this dummy element. + /// It can work as the same as all other elements but it does nothing. So the program will not crash. + /// The only thing going to happen is that you will find a warning in terminal and no expected navigation will be triggered. /// + /// The reason why we do not make it an optional maneuver is that you actually should NOT try to catch it on runtime. You should absolutely avoid it before making it a production! So keep an eye on the terminal if you find something unexpected! static let dummy: DefinedViewManagerElement = DefinedViewManagerDummyElement() } +/// [DE Internal] A dummy element class for `DVManagerElement`. internal class DefinedViewManagerDummyElement : DefinedViewManagerElement { init() { super.init(id: UUID(), parent: .dummy) @@ -64,29 +93,74 @@ internal class DefinedViewManagerDummyElement : DefinedViewManagerElement { override func link(to target: Page) where Page: DefinedPage { // do nothing. } + + override func jump(to target: Page) where Page: DefinedPage { + // do nothing. + } + + override func swap(with target: Page) where Page: DefinedPage { + // do nothing. + } + + override func back() { + // do nothing. + } + + override func setStatusBarStyle(_ style: UIStatusBarStyle) { + // do nothing. + } + + override func register(_ root: DefinedViewManagerRootElement) { + // do nothing. + } + + override func unregister() { + // do nothing. + } } +/// [DE Internal] A ViewManager element representing a stack instance (not a page!). internal class DefinedViewManagerRootElement : DefinedPotentialWarning, Equatable { - var id: UUID = UUID() - var name: String = "DVManagerRootElement" + /// The id of this element. + /// + /// It is neither an ID from outside nor syncing with other components. + var id: UUID = UUID() + + /// All pages' ViewManager elements from current view stack. var hierarchy: [DefinedViewManagerElement] = [] - + + /// A docker linking to the actual view stack instance behind this ViewManager element. var docker: DefinedViewStackDocker? = nil + + /// The parent stack's ViewManager element (should not have one only if it is the root stack). var parent: DefinedViewManagerRootElement? = nil + /// [DE Private] Create a root or dummy `DVManagerRootElement` that might not have a docker and a parent. + /// + /// - Parameters: + /// - docker: The docker linking to the actual view stack instance behind this ViewManager element. + /// - parent: The parent stack's ViewManager element. fileprivate init(docker: DefinedViewStackDocker? = nil, parent: DefinedViewManagerRootElement? = nil) { self.docker = docker self.parent = parent } - init(docker: DefinedViewStackDocker, parent: DefinedViewManagerRootElement) { + /// [DE Internal] Create a `DVManagerRootElement`. + /// + /// Create the root stack's root element by using another initializer with optional docker and parent. Thus, this initializer requires the both. + /// + /// - Parameters: + /// - docker: The docker linking to the actual view stack instance behind this ViewManager element. + /// - parent: The parent stack's ViewManager element. + internal init(docker: DefinedViewStackDocker, parent: DefinedViewManagerRootElement) { self.docker = docker self.parent = parent } - func link(to target: Page) where Page: DefinedPage { + /// [DE Internal] Link. + internal func link(to target: Page) where Page: DefinedPage { if (self.docker == nil) { // ERROR return @@ -96,7 +170,8 @@ internal class DefinedViewManagerRootElement : DefinedPotentialWarning, Equatabl self.hierarchy.append(DefinedViewManager.registerPage(id: target.id, parent: self)) } - func jump(to target: Page) where Page: DefinedPage { + /// [DE Internal] Jump. + internal func jump(to target: Page) where Page: DefinedPage { if (self.docker == nil) { // ERROR return @@ -104,7 +179,8 @@ internal class DefinedViewManagerRootElement : DefinedPotentialWarning, Equatabl self.docker!.jump(to: target) } - func swap(with target: Page) where Page: DefinedPage { + /// [DE Internal] Swap. + internal func swap(with target: Page) where Page: DefinedPage { if (self.docker == nil) { // ERROR return @@ -112,7 +188,8 @@ internal class DefinedViewManagerRootElement : DefinedPotentialWarning, Equatabl self.docker!.swap(with: target) } - func back() { + /// [DE Internal] Back. + internal func back() { if (self.docker == nil) { // ERROR warning("docker is null") @@ -134,10 +211,10 @@ internal class DefinedViewManagerRootElement : DefinedPotentialWarning, Equatabl } extension DefinedViewManagerRootElement { - /// + /// Representing the root stack of entire page system. internal static let base = DefinedViewManagerRootElement() - /// + /// To be used as the parent of the dummy page's ViewManager element. fileprivate static let dummy = DefinedViewManagerRootElement() } From 1f09aeb880da48140fe92710ad5a6b4565dba1a1 Mon Sep 17 00:00:00 2001 From: Lingxi_Li <36816148+lilingxi01@users.noreply.github.com> Date: Sun, 11 Jul 2021 14:24:32 -0400 Subject: [PATCH 35/37] [DPage-iOS] Add a todo for future development plan. --- .../Frameworks/Page/iOS/Support/DPage-iOS.Controller.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Sources/DefinedElements/Frameworks/Page/iOS/Support/DPage-iOS.Controller.swift b/Sources/DefinedElements/Frameworks/Page/iOS/Support/DPage-iOS.Controller.swift index 81cb827..577cc6e 100644 --- a/Sources/DefinedElements/Frameworks/Page/iOS/Support/DPage-iOS.Controller.swift +++ b/Sources/DefinedElements/Frameworks/Page/iOS/Support/DPage-iOS.Controller.swift @@ -7,6 +7,8 @@ import Foundation /// ``` swift /// var controller: DefinedPageController = .init() /// ``` +/// +/// - TODO: `swap` and `jump` features. public class DefinedPageController { /// The page id associated with the controlled page. /// From 776e14502e1d7c12c474447a02d90c77ae1458d0 Mon Sep 17 00:00:00 2001 From: Lingxi_Li <36816148+lilingxi01@users.noreply.github.com> Date: Sun, 11 Jul 2021 14:24:46 -0400 Subject: [PATCH 36/37] [DVManager] Add a comment. --- .../Frameworks/View/Manager/Support/DVManager.Element.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Sources/DefinedElements/Frameworks/View/Manager/Support/DVManager.Element.swift b/Sources/DefinedElements/Frameworks/View/Manager/Support/DVManager.Element.swift index bc5ab46..75e81bc 100644 --- a/Sources/DefinedElements/Frameworks/View/Manager/Support/DVManager.Element.swift +++ b/Sources/DefinedElements/Frameworks/View/Manager/Support/DVManager.Element.swift @@ -197,6 +197,7 @@ internal class DefinedViewManagerRootElement : DefinedPotentialWarning, Equatabl } if (hierarchy.count > 1) { self.docker!.back() + // unregister it from the ViewManager after pop it out to avoid double execute. let last = self.hierarchy.removeLast() DefinedViewManager.unregisterPage(id: last.id) } else { From c84a8ed08c1b58958896e8c00af078063e47718a Mon Sep 17 00:00:00 2001 From: Lingxi_Li <36816148+lilingxi01@users.noreply.github.com> Date: Sun, 11 Jul 2021 14:24:56 -0400 Subject: [PATCH 37/37] [DVStack] Add known bug comments. --- .../Frameworks/View/Stack/DVStack.Main.swift | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Sources/DefinedElements/Frameworks/View/Stack/DVStack.Main.swift b/Sources/DefinedElements/Frameworks/View/Stack/DVStack.Main.swift index 7ccd8ce..c171a8a 100644 --- a/Sources/DefinedElements/Frameworks/View/Stack/DVStack.Main.swift +++ b/Sources/DefinedElements/Frameworks/View/Stack/DVStack.Main.swift @@ -5,7 +5,8 @@ import SwiftUI /// [DE] A view stack holding `DefinedPage`. /// -/// - BUG: Swipe back gesture not perfect +/// - BUG: Swipe over-edge bug. +/// - BUG: Swipe back gesture detection is not perfect. public struct DefinedViewStack : DefinedView { /// A manager controlling the view stack. @@ -111,7 +112,9 @@ public struct DefinedViewStack : DefinedView { EmptyView() } } - .overlay( // MARK: Drag Part + .overlay( + // MARK: Drag Part + // TODO: make drag better DefinedContent(.overlay, alignment: .leading) { if self.manager.elements.count > 1 { Color.clear