Open7

【iOS】UIKitからSwiftUIに移行するメモ(UaaL / WebViewを利用)

だーら(Flamers / Memotia)だーら(Flamers / Memotia)

SwiftUIや@Stateについて

SwiftUIについて

チュートリアルが分かりやすい。UIKitからの移行についても書かれていてよい。

https://developer.apple.com/tutorials/swiftui
https://zenn.dev/kyuko/articles/cc1f2512ee6b3f

@Stateについて

https://ios-docs.dev/swiftui-state/

  • 値が更新されたときにビューも更新されるようにする
  • その値が必要な最上位viewでprivateで宣言すること。subviewに対しては、readonlyとして直接渡すか、read-writeできるようにbindingとして渡すか。
実装

シンプルなState

struct PlayButton: View {
    @State private var isPlaying: Bool = false

    var body: some View {
        Button(isPlaying ? "Pause" : "Play") {
            isPlaying.toggle()
        }
    }
}


ボタンを押す前


ボタンを押した後

Bindingとしてsubviewにわたす

PlayerView.swift
struct PlayerView: View {
    @State private var isPlaying: Bool = false

    var body: some View {
        Text(isPlaying ? "Playing now" : "Pausing now")
        PlayButton(isPlaying: $isPlaying)
    }
}
PlayButton.swift
struct PlayButton: View {
    @Binding var isPlaying: Bool
    
    var body: some View {
        Button(isPlaying ? "Pause" : "Play") {
            isPlaying.toggle()
        }
    }
}

だーら(Flamers / Memotia)だーら(Flamers / Memotia)

UaaL部分の表示

  • 参考はこの記事

https://www.am10.blog/archives/576

  • ただし、SwiftUIのAppの中からUnity.shared.startを実行すると、Metal関連のエラーがでた
'Texture Descriptor Validation MTLTextureDescriptor has width of zero.
  • AppDelegateのapplicationの中で初期化し、pauseしておいて対処
AppDelegate.swift
class AppDelegate: UIResponder, UIApplicationDelegate, MessagingDelegate {
  func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
  ) -> Bool {
    ......
    Unity.shared.start(application, didFinishLaunchingWithOptions: launchOptions)
    DispatchQueue.main.asyncAfter(deadline: .now() + 3.0) {
      Unity.shared.pause()
    }
UnityView.swift
struct UnityView: UIViewRepresentable {
  func makeUIView(context: Context) -> UIView {
    return Unity.shared.view
  }
  
  func updateUIView(_ view: UIView, context: Context) {
  }
}
だーら(Flamers / Memotia)だーら(Flamers / Memotia)

Unity部分が表示されたときに強制的にlandscape向きに回転させる

  • 参考

https://seeking-star.com/prog/swiftui/device_orientation/

  • UIKitのときには、以下のよぅにsupportedInterfaceOrientationsで向きを変更していたが、SwiftUIではこれは出来ない模様。
UnityViewController.swift
class UnityViewController: UIViewController {
......
  override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
    return [.landscapeLeft, .landscapeRight]
  }
  • また、iOS16以上では UIDevice.current.setValueは使えない
だーら(Flamers / Memotia)だーら(Flamers / Memotia)

WebViewの表示

WebView.swift
struct WebView: UIViewRepresentable {
  @Binding var isLoading: Bool

  func makeUIView(context: Context) -> WKWebView {
    let url = URL(string: "https://example.com/")
    let request = URLRequest(url: url!)
    let webView = WKWebView()

    let coordinator = context.coordinator
    webView.navigationDelegate = coordinator
    webView.scrollView.delegate = coordinator
    webView.configuration.userContentController.add(coordinator, name: "event")

    webView.load(request)
    return webView
  }

  func updateUIView(_ uiView: WKWebView, context: Context) {}

  func makeCoordinator() -> Coordinator {
    return Coordinator(self)
  }


  class Coordinator: NSObject, WKNavigationDelegate, WKScriptMessageHandler, UIScrollViewDelegate {
    private let parent: WebView
    init(_ webView: WebView) {
      self.parent = webView
    }

    func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction) async
      -> WKNavigationActionPolicy
    {
...以下略(プロトコルに準拠した実装をしていく...

他参考

https://tech.amefure.com/swift-uiviewrepresentable