SKStoreProductViewController 必须在模态视图控制器 SWIFTUI 中使用

nir*_*ali 6 swiftui

我正在为我的 SwiftUI 应用程序构建一个信息页面。一个项目应该打开 App Store,另一个邮件。我为每个都编写了 UIViewControllerRepresentable 。

MailView 完全可以正常工作。StoreView 显示正常,但按下取消按钮时,抛出异常

“*** 由于未捕获的异常‘SKUnsupportedPresentationException’而终止应用程序,原因:‘SKStoreProductViewController 必须用于模态视图控制器’”。

MailView 可以很好地进入 didFinish 委托方法,但 StoreView 没有进入 didFinish 委托方法,它在进入这个 didFinish 方法之前崩溃了。请问我做错了什么?


import SwiftUI
import StoreKit
import MessageUI

struct InfoMoreAppsView: View {
    @State var showAppAtStore = false
    @State var reportBug = false
    @State var result: Result<MFMailComposeResult, Error>? = nil
    let otherAppName = "TheoryTest"
    
    var body: some View {
        VStack(alignment: .leading){
            HStack{
                Image(Helper.getOtherAppImageName(otherAppName: otherAppName))
                Button(action: { self.showAppAtStore = true }) {
                    Text(otherAppName)
                }
                .sheet(isPresented: $showAppAtStore){
                    StoreView(appID: Helper.getOtherAppID(otherAppName: otherAppName))
                }
            }
            Button(action: { self.reportBug = true }) {
                Text("Report a bug")
            }
            .sheet(isPresented: $reportBug){
                MailView(result: self.$result)
            }
        }
        .padding()
        .font(.title2)
    }
}

struct StoreView: UIViewControllerRepresentable {
    
    let appID: String
    @Environment(\.presentationMode) var presentation
    
    class Coordinator: NSObject, SKStoreProductViewControllerDelegate {
        @Binding var presentation: PresentationMode
        init(presentation: Binding<PresentationMode> ) {
            _presentation = presentation
        }
        private func productViewControllerDidFinish(viewController: SKStoreProductViewController) {
            $presentation.wrappedValue.dismiss()
            viewController.dismiss(animated: true, completion: nil)
        }
    }
    
    func makeCoordinator() -> Coordinator {
        return Coordinator(presentation: presentation)
    }
    
    func makeUIViewController(context: UIViewControllerRepresentableContext<StoreView>) -> SKStoreProductViewController {
        let skStoreProductViewController = SKStoreProductViewController()
        skStoreProductViewController.delegate = context.coordinator
        let parameters = [ SKStoreProductParameterITunesItemIdentifier : appID]
        skStoreProductViewController.loadProduct(withParameters: parameters)
        return skStoreProductViewController
    }
    
    func updateUIViewController(_ uiViewController: SKStoreProductViewController, context: UIViewControllerRepresentableContext<StoreView>) {
    }
}


struct MailView: UIViewControllerRepresentable {
    
    @Environment(\.presentationMode) var presentation
    @Binding var result: Result<MFMailComposeResult, Error>?
    
    class Coordinator: NSObject, MFMailComposeViewControllerDelegate {
        
        @Binding var presentation: PresentationMode
        @Binding var result: Result<MFMailComposeResult, Error>?
        
        init(presentation: Binding<PresentationMode>,
             result: Binding<Result<MFMailComposeResult, Error>?>) {
            _presentation = presentation
            _result = result
        }
        
        func mailComposeController(_ controller: MFMailComposeViewController,
                                   didFinishWith result: MFMailComposeResult,
                                   error: Error?) {
            defer {
                $presentation.wrappedValue.dismiss()
            }
            guard error == nil else {
                self.result = .failure(error!)
                return
            }
            self.result = .success(result)
        }
    }
    
    func makeCoordinator() -> Coordinator {
        return Coordinator(presentation: presentation,
                           result: $result)
    }
    
    func makeUIViewController(context: UIViewControllerRepresentableContext<MailView>) -> MFMailComposeViewController {
        let mailComposeViewController = MFMailComposeViewController()
        mailComposeViewController.mailComposeDelegate = context.coordinator
        mailComposeViewController.setToRecipients([Constants.SUPPORT_EMAIL])
        mailComposeViewController.setMessageBody(systemInfo(), isHTML: true)
        return mailComposeViewController
    }
    
    func systemInfo() -> String {
        let device = UIDevice.current
        let systemVersion = device.systemVersion
        let model = UIDevice.hardwareModel
        let mailBody = "Model: " + model + ". OS: " + systemVersion
        
        return mailBody
    }
    
    
    func updateUIViewController(_ uiViewController: MFMailComposeViewController,
                                context: UIViewControllerRepresentableContext<MailView>) {
        
    }
}

Run Code Online (Sandbox Code Playgroud)

Luz*_*uzo 9

因为我被困在同一件事上。这是我发现有效的快速解决方案。

import StoreKit
import SwiftUI
import UIKit

struct StoreView: UIViewControllerRepresentable {
  var dismissHandler: () -> Void

  func makeUIViewController(context: UIViewControllerRepresentableContext<StoreView>) -> StoreViewController {

    return StoreViewController(coordinator: context.coordinator)
  }

  func updateUIViewController(_ uiViewController: StoreViewController, context: UIViewControllerRepresentableContext<StoreView>) {

  }

  public func makeCoordinator() -> StoreViewCoordinator {
    .init(dismissHandler: dismissHandler)
  }
}

class StoreViewController: UIViewController {
  let coordinator: StoreViewCoordinator
  var storeController: SKStoreProductViewController?

  init(coordinator: StoreViewCoordinator) {
    self.coordinator = coordinator

    super.init(nibName: nil, bundle: nil)
  }

  @available(*, unavailable)
  required init?(coder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
  }

  override func viewDidLoad() {
    super.viewDidLoad()

    storeController = SKStoreProductViewController()
    storeController?.delegate = coordinator

    storeController?.loadProduct(
      withParameters: [SKStoreProductParameterITunesItemIdentifier: ******]
    )
  }

  override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)

    guard let storeController = storeController else {
      return
    }

    present(storeController, animated: true)
  }
}

class StoreViewCoordinator: NSObject, SKStoreProductViewControllerDelegate {
  private let dismissHandler: () -> Void

  init(dismissHandler: @escaping () -> Void) {
    self.dismissHandler = dismissHandler
  }

  func productViewControllerDidFinish(_ viewController: SKStoreProductViewController) {
    dismissHandler()
  }
}

Run Code Online (Sandbox Code Playgroud)

然后我在 ZStack 中使用它,如下所示:

StoreView(
 dismissHandler: { viewStore.send(.setShowingStore(false)) }
)
.isHidden(!viewStore.isShowingStore, remove: true)
Run Code Online (Sandbox Code Playgroud)

我正在使用 TCA,因此在您的情况下设置属性会有所不同


Eri*_*ric 8

这不是很“Swifty”或漂亮,但我通过不将 SKStoreProductViewController 包装在可表示中来使其工作而不会崩溃。

struct MovieView: View {
var vc:SKStoreProductViewController = SKStoreProductViewController()
var body: some View {
HStack(){
                                Button(action: {
                                    let params = [
                                        SKStoreProductParameterITunesItemIdentifier:"1179624268",
                                        SKStoreProductParameterAffiliateToken:"11l4Cu",
                                        SKStoreProductParameterCampaignToken:"hype_movie"
                                        ] as [String : Any]
                                   // vc!.delegate = self
                                    vc.loadProduct(withParameters: params, completionBlock: { (success,error) -> Void in
                                        UIApplication.shared.windows.first?.rootViewController?.present(vc, animated: true, completion: nil)
                                    })
                                }) {
                                    HStack {
                                        Image(systemName: "play.fill")
                                            .font(.headline)
                                      
                                    }
                                    .padding(EdgeInsets(top: 6, leading:36, bottom: 6, trailing: 36))
                                    .foregroundColor(.white)
                                    .background(Color(red: 29/255, green: 231/255, blue: 130/255))
                                    .cornerRadius(10)
                                }
                                Spacer()
                            }}
Run Code Online (Sandbox Code Playgroud)