Hi 👋
| 我的个人项目 | 扫雷Elic 无尽天梯 | 梦见账本 |
|---|---|---|
| 类型 | 游戏 | 财务 |
| AppStore | Elic | Umemi |
前言
前段时间使用SwiftUI重构了自己的独立项目梦见账本的商店页面。
项目主体是基于UIKit的,这这里的商店页面是Present出一个SwiftUI页面容器UIHostingController。
当需要Dismiss该控制器的时候遇到了麻烦,不知道怎么关掉。
一:在SwiftUI中Dismiss一个页面的正常操作
下面的实现方式在正常的SwiftUI体系下是可以正常工作的
但是在UIKit混编时就失效了
struct ContentView: View, SwiftUIBridgeProtocol {
@Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>
var body: some View {
ZStack {
VStack {
StoreTopView(dismissClosure: {
presentationMode.wrappedValue.dismiss()
})
.padding()
}
}
}
}
我们看下打印的信息:
(lldb) po presentationMode
▿ Binding<PresentationMode>
▿ transaction : Transaction
▿ plist : []
- elements : nil
▿ location : <LocationBox<FunctionalLocation<PresentationMode>>: 0x2838313c0>
▿ _value : PresentationMode
- isPresented : false
(lldb) po presentationMode.wrappedValue
▿ PresentationMode
- isPresented : false
二:如何才能获取到HostController呢
2.1 方案一
下面的文正提供了一些尝试的方案,但不够SwiftUI风格
2.2 方案二 @Environment
通过一段时间的SwiftUI学习,发现通过@Environment可以获取到很多当前页面的一些有用信息。
但是通过观察可用的字段并没有发现有关HostController的线索。
2.3 那是否可以添加自定义字段呢?
可以!
查看EnvironmentKey相关的文档或者注释我们可以发现,其中已经提供了自定义的示例代码:
You can create custom environment values by extending the EnvironmentValues structure with new properties. First declare a new environment key type and specify a value for the required defaultValue property:
private struct MyEnvironmentKey: EnvironmentKey {
static let defaultValue: String = "Default value"
}
The Swift compiler automatically infers the associated Value type as the type you specify for the default value. Then use the key to define a new environment value property:
extension EnvironmentValues {
var myCustomValue: String {
get { self[MyEnvironmentKey.self] }
set { self[MyEnvironmentKey.self] = newValue }
}
}
Clients of your environment value never use the key directly. Instead, they use the key path of your custom environment value property. To set the environment value for a view and all its subviews, add the environment(::) view modifier to that view:
MyView()
.environment(\.myCustomValue, "Another string")
As a convenience, you can also define a dedicated view modifier to apply this environment value:
extension View {
func myCustomValue(_ myCustomValue: String) -> some View {
environment(\.myCustomValue, myCustomValue)
}
}
This improves clarity at the call site:
MyView()
.myCustomValue("Another string")
To read the value from inside MyView or one of its descendants, use the Environment property wrapper:
struct MyView: View {
@Environment(\.myCustomValue) var customValue: String
var body: some View {
Text(customValue) // Displays "Another value".
}
}
三:下面按照上面的@Environment思路实现一下
3.1 用以承载ViewController的容器
public struct ViewControllerHolder {
public weak var value: UIViewController?
init(_ value: UIViewController?) {
self.value = value
}
}
3.2 EnvironmentKey
public struct ViewControllerKey: EnvironmentKey {
public static var defaultValue: ViewControllerHolder { return ViewControllerHolder(nil) }
}
3.3 Extension
extension EnvironmentValues {
public var viewController: ViewControllerHolder {
get { return self[ViewControllerKey.self] }
set { self[ViewControllerKey.self] = newValue }
}
}
3.4 打开页面
extension UIViewController {
public func present<Content: View>(presentationStyle: UIModalPresentationStyle = .automatic, transitionStyle: UIModalTransitionStyle = .coverVertical, animated: Bool = true, completion: @escaping () -> Void = {}, @ViewBuilder builder: () -> Content) {
let toPresent = UIHostingController(rootView: AnyView(EmptyView()))
toPresent.modalPresentationStyle = presentationStyle
toPresent.rootView = AnyView(
builder()
.environment(\.viewController, ViewControllerHolder(toPresent))
)
if presentationStyle == .overCurrentContext {
toPresent.view.backgroundColor = .clear
}
self.present(toPresent, animated: animated, completion: completion)
}
}
3.5 关闭页面
struct ShopContentView: View {
...
// MARK: - Body
var body: some View {
ZStack(alignment: .top) {
...
viewControllerHolder.value?.dismiss(animated: false, completion: nil)
...
})
}
...
// MARK: - Var: Environment
@Environment(\.viewController) var viewControllerHolder
...
}
3.6 SwiftUIEx
这里我进行了封装SwiftUIEx,觉得有用欢迎留下一颗⭐️~
思考
通过@Environment实现Dismiss,了解了其更灵活的使用方式,也使解决方式更加SwiftUI风格
👋
My apps
| - | 扫雷Elic 无尽天梯 | 梦见账本 |
|---|---|---|
| 类型 | 游戏 | 财务 |
| AppStore | Elic | Umemi |