# 04.UIKit混编时Dismiss掉HostController

> 场景： 在主`UIKit`的项目中`Present`出一个包含`SwiftUI`的`UIHostingController` 当需要`Dismiss`该控制器的时候遇到了麻烦

## 一：在SwiftUI中Dismiss一个页面的正常操作

下面的实现方式在正常的`SwiftUI`体系下是可以正常工作的 但是在`UIKit`混编时就失效了

```swift
struct ContentView: View, SwiftUIBridgeProtocol {
    @Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>

    var body: some View {
        ZStack {
            VStack {
                StoreTopView(dismissClosure: {
                    presentationMode.wrappedValue.dismiss()
                })
                .padding()
            }
        }
    }
}
```

我们看下打印的信息：

```swift
(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`呢

### 方案1. 一些方案

下面的文正提供了一些尝试的方案，但不够SwiftUI风格

> [Controlling UIHostingController with SwiftUI View](https://medium.com/mobile-app-development-publication/controlling-uihostingcontroller-with-swiftui-view-2f6cd3b2a178)

### 方案2.@Environment

通过一段时间的`SwiftUI`学习，发现通过`@Environment`可以获取到很多当前页面的一些有用信息。 但是通过观察可用的字段并没有发现有关`HostController`的线索。

### 那是否可以添加自定义字段呢？

可以！ 查看`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:

```swift
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:

```swift
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:

```swift
MyView()
    .environment(\.myCustomValue, "Another string")
```

> As a convenience, you can also define a dedicated view modifier to apply this environment value:

```swift
extension View {
    func myCustomValue(_ myCustomValue: String) -> some View {
        environment(\.myCustomValue, myCustomValue)
    }
}
```

> This improves clarity at the call site:

```swift
MyView()
    .myCustomValue("Another string")
```

> To read the value from inside MyView or one of its descendants, use the Environment property wrapper:

```swift
struct MyView: View {
    @Environment(\.myCustomValue) var customValue: String

    var body: some View {
        Text(customValue) // Displays "Another value".
    }
}
```

## 三：下面按照上面的思路实现一下

### 3.1 用以承载`ViewController`的容器

```swift
public struct ViewControllerHolder {
    public weak var value: UIViewController?
    init(_ value: UIViewController?) {
        self.value = value
    }
}
```

### 3.2 EnvironmentKey

```swift
public struct ViewControllerKey: EnvironmentKey {
    public static var defaultValue: ViewControllerHolder { return ViewControllerHolder(nil) }
}
```

### 3.3 Extension

```swift
extension EnvironmentValues {
    public var viewController: ViewControllerHolder {
        get { return self[ViewControllerKey.self] }
        set { self[ViewControllerKey.self] = newValue }
    }
}
```

### 3.4 打开页面

```swift
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 关闭页面

```swift
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](https://github.com/RyukieSama/SwiftUIEx/blob/main/SwiftUIEx/UIKitBridge/Environment%2BUIViewController.swift)，觉得有用欢迎留下一颗⭐️\~

## 思考

通过`@Environment`实现`Dismiss`，了解了其更灵活的使用方式。也为解决之后开发中遇到的问题提供了一点思路。同时也使解决方式更加`SwiftUI`风格
