# 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`风格


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://ryukiedev.gitbook.io/wiki/swiftui/04.uikit-hun-bian-shi-dismiss-diao-hostcontroller.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
