[SwiftUI] UIViewRepresentable 에서 EnvironmentObject의 @Published 구독하기

원래는 RxSwift의 PublishSubject처럼 구독하는게 목표였다. 하지만 SwiftUI의 UIViewRepresentable 은 onAppear() 같은 함수가 없고 최초 1회 호출되는 makeUIView(), 뷰에 변화가 생기면 매번 호출되는 updateUIView() 밖에 없다.

Combine에는 PublishSubject 역할을 할것같은 PassthroughSubject라는게 있는데, 이건 View에서는 구독이 되는데, UIViewRepresentable 에서는 변화가 생겨도 발행을 받지 못한다. 계속 찾다가 포기하고 다른 방법으로 해결했다.

// MapViewEnvironment.swift
class MapViewEnvironment: ObservableObject {
  var didMyStringChange: (()->Void)?
  @Published var myString: String? {
    didSet {
      if let didMyStringChange = didMyStringChange {
        didMyStringChange()
      }
    }
  }
}
// SceneDelegate.swift
let mapViewEnvironment = MapViewEnvironment()
let contentView = ContentView().environmentObject(mapViewEnvironment)
// ContentView.swift
struct ContentView: View {
  @EnvironmentObject var mapViewEnvironment: MapViewEnvironment

  var body: some View {
    ZStack(alignment: .bottom) {
      MintMapView().edgesIgnoringSafeArea(.all)
      Button(action: { self.mapViewEnvironment.myString = "\(Date())"}, label: { Text("button") }).padding(Edge.Set.bottom, 45)
    }
  }
}

MintMapView는 여러 종류의 지도를 감싸기 위해 내가 만든 클래스다.

// MintMapView.swift
struct MintMapView: View {
  @EnvironmentObject var mapViewEnvironment: MapViewEnvironment
  
  var body: some View {
    AppleMapView()
  }
}
// AppleMapView.swift
struct AppleMapView: UIViewRepresentable {
  @EnvironmentObject var mapViewEnvironment: MapViewEnvironment

  func makeUIView(context: Context) -> MKMapView {
    return MKMapView(frame: .zero)
  }

  func updateUIView(_ view: MKMapView, context: UIViewRepresentableContext<AppleMapView>) {
    mapViewEnvironment.didMyStringChange = { it in print(it) }
  }
}

버튼을 눌러서 로그를 찍으면 이렇게 잘 찍힌다.

2019-12-04 13:13:49 +0000
2019-12-04 13:13:49 +0000
2019-12-04 13:13:49 +0000

더 좋은 방법이 있을 것 같긴한데, 아직까지는 모르겠다.