Dismiss UIViewController sheet from SwiftUI with Binding/Presentation Mode? - swift

I have a SwiftUI view with a sheet that directs to a UIViewControllerRepresentable struct, which uses a Google Ad Manager extension. I want the UIKit extension to dismiss the sheet when it's done running using presentation mode, but using presentation mode through the environment isn't working. Is the code not running the way I've set it up, or is it not possible to dismiss the sheet from the Google Ads Manager?
Alternatively, when I try to dismiss the sheet using a Binding to update isPresented in AdManager, I get the error message: "Property 'self.isPresented' not initialized at super.init call". When I try to initialize it, however, it says I can't do it before super.init, and when I put it after super.init, it says I can't call it after super.init. Is there any way I can dismiss the sheet when the ad is done loading (the function rewardedAdDidDismiss)?
Edit: This post was originally marked as a duplicate, but the linked question asked about dismissing a SwiftUI modal from a UIViewController, not the other way around.
The SwiftUI view:
import SwiftUI
struct AdRevenue: View {
#State var playAd = false
#Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>
var body: some View {
Button(action: {
self.playAd = true
})
{
Text("Play Ad")
}.sheet(isPresented: $playAd) {
Ads()}
}
}
This is my UIViewControllerRepresentable:
import GoogleMobileAds
import SwiftUI
import UIKit
struct Ads: UIViewControllerRepresentable {
typealias UIViewControllerType = UIViewController
func makeUIViewController(context: Context) -> UIViewController {
return ViewController()
}
func updateUIViewController(_ uiView: UIViewController, context: Context) {
}
class ViewController: UIViewController, GADRewardedAdDelegate, AdManagerRewardDelegate {
var rewardedAd: GADRewardedAd?
#Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>
override func viewDidLoad() {
super.viewDidLoad()
AdManager.shared.loadAndShowRewardAd(AdIds.rewarded.rawValue, viewController: self)
AdManager.shared.delegateReward = self
presentationMode.wrappedValue.dismiss()
}
func rewardedAd(_ rewardedAd: GADRewardedAd, userDidEarn reward: GADAdReward) {
print("Reward received: \(reward.type), amount \(reward.amount).")
}
}
}
This is my Google Ads Manager:
public protocol AdManagerRewardDelegate{
func rewardAdGiveRewardToUser(type:String, amount: NSDecimalNumber)
func rewardAdFailedToLoad()
func rewardAdDidReceive(
rewardViewController: UIViewController?,
rewardedAd: GADRewardedAd?,
delegate: AdManager
)
func rewardAdDidOpen()
func rewardAdDidClose()
func rewardAdFailedToPresent()
}
//default implementation AdManagerRewardDelegate
public extension AdManagerRewardDelegate{
func rewardAdGiveRewardToUser(type:String, amount: NSDecimalNumber) {}
func rewardAdFailedToLoad() {}
func rewardAdDidReceive(
rewardViewController: UIViewController?,
rewardedAd: GADRewardedAd?,
delegate: AdManager
) {
if rewardedAd?.isReady == true {
if let rewardViewController = rewardViewController {
rewardedAd?.present(fromRootViewController: rewardViewController, delegate: delegate)
}
}
}
func rewardAdDidOpen() {}
func rewardAdDidClose() {}
func rewardAdFailedToPresent() {}
}
public class AdManager: NSObject {
public static let shared = AdManager()
public var ADS_DISABLED = false
public var delegateReward: AdManagerRewardDelegate?
private var viewController:UIViewController?
private var rewardViewController:UIViewController?
private var testDevices:[String] = [""]
private var rewardedAd: GADRewardedAd?
#Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>
let borderSizeBetweenBannerAndContent:CGFloat = 5
public override init() {
super.init()
}
public func configureWithApp(){
GADMobileAds.sharedInstance().requestConfiguration.testDeviceIdentifiers = testDevices
}
public func setTestDevics(testDevices: [String]){
self.testDevices = testDevices
self.testDevices += [kGADSimulatorID as! String ] //all simulator
}
private func getGADRequest() -> GADRequest{
let request = GADRequest()
return request
}
// MARK:- Reward Video Ads
public func loadAndShowRewardAd(_ adUnit: String, viewController: UIViewController){
self.rewardViewController = viewController
rewardedAd = GADRewardedAd(adUnitID: adUnit)
rewardedAd?.load(getGADRequest()) { error in
if let error = error {
// Handle ad failed to load case.
print("Reward based video ad failed to load. \(error.debugDescription)")
self.delegateReward?.rewardAdFailedToLoad()
} else {
// Ad successfully loaded.
print("Reward based video ad is received.")
self.delegateReward?.rewardAdDidReceive(
rewardViewController: self.rewardViewController,
rewardedAd: self.rewardedAd,
delegate: self
)
}
}
}
}
// MARK:- GADRewardBasedVideoAdDelegate
extension AdManager : GADRewardedAdDelegate {
public func rewardedAdDidPresent(_ rewardedAd: GADRewardedAd) {
print("Rewarded ad presented.")
delegateReward?.rewardAdDidOpen()
}
public func rewardedAd(_ rewardedAd: GADRewardedAd, didFailToPresentWithError error: Error) {
print("Rewarded ad failed to present.")
delegateReward?.rewardAdFailedToPresent()
}
public func rewardedAdDidDismiss(_ rewardedAd: GADRewardedAd) {
presentationMode.wrappedValue.dismiss()
print("Rewarded ad dismissed.")
delegateReward?.rewardAdDidClose()
}
public func rewardedAd(_ rewardedAd: GADRewardedAd, userDidEarn reward: GADAdReward) {
print("Reward received with currency: \(reward.type), amount \(reward.amount).")
delegateReward?.rewardAdGiveRewardToUser(type: reward.type, amount: reward.amount)
}
}

Related

swiftui performance downgrade when adding Google Maps Places

I have an iOS app and after adding address autocomplete, the app started lagging.
I added the bundle and the sdk manually: https://developers.google.com/maps/documentation/places/ios-sdk/config#install-manually.
Around 14mb:
Can the fact that adding the sdk manually(just drag and drop) be a problem and affecting performance?
After that I created the button that opens the autocompleter sheet:
import SwiftUI
struct ProfileUserView: View {
#State var openPlacePicker = false
#State var address = ""
var body: some View {
NavigationView{
VStack{
VStack {
Text(address)
Button {
openPlacePicker.toggle()
} label: {
Text("Schimba adresa")
}
}.sheet(isPresented: $openPlacePicker) {
PlacePicker(address: $address)
}
}
}
And the autocompleter:
import SwiftUI
import GooglePlaces
struct PlacePicker: UIViewControllerRepresentable {
func makeCoordinator() -> GooglePlacesCoordinator {
GooglePlacesCoordinator(self)
}
#Environment(\.presentationMode) var presentationMode
#Binding var address: String
func makeUIViewController(context: UIViewControllerRepresentableContext<PlacePicker>) -> GMSAutocompleteViewController {
GMSPlacesClient.provideAPIKey("xxxxx")
let autocompleteController = GMSAutocompleteViewController()
autocompleteController.delegate = context.coordinator
let fields: GMSPlaceField = GMSPlaceField(rawValue:UInt(GMSPlaceField.name.rawValue) |
UInt(GMSPlaceField.placeID.rawValue) |
UInt(GMSPlaceField.coordinate.rawValue) |
GMSPlaceField.addressComponents.rawValue |
GMSPlaceField.formattedAddress.rawValue)
autocompleteController.placeFields = fields
let filter = GMSAutocompleteFilter()
filter.type = .address
autocompleteController.autocompleteFilter = filter
return autocompleteController
}
func updateUIViewController(_ uiViewController: GMSAutocompleteViewController, context: UIViewControllerRepresentableContext<PlacePicker>) {
}
class GooglePlacesCoordinator: NSObject, UINavigationControllerDelegate, GMSAutocompleteViewControllerDelegate {
var parent: PlacePicker
init(_ parent: PlacePicker) {
self.parent = parent
}
func viewController(_ viewController: GMSAutocompleteViewController, didAutocompleteWith place: GMSPlace) {
DispatchQueue.main.async {
print(place.description.description as Any)
self.parent.address = place.name!
self.parent.presentationMode.wrappedValue.dismiss()
print("latitude: \(place.coordinate.latitude)")
print("longitude: \(place.coordinate.longitude)")
}
}
func viewController(_ viewController: GMSAutocompleteViewController, didFailAutocompleteWithError error: Error) {
print("Error: ", error.localizedDescription)
}
func wasCancelled(_ viewController: GMSAutocompleteViewController) {
parent.presentationMode.wrappedValue.dismiss()
}
}
}
Any issue of how I do the autocomplete in SwiftUI.
It starts lagging whenever I open the sheet of the PlacePicker.

Admob rewarded ad won't load

I have build a SwiftUI .sheet() that appears so the user can see their statistics and can click on a button that then will show a rewarded video to give the user some extra points. The code is as follows:
import SwiftUI
import GoogleMobileAds
struct Scoresheet: View {
#State private var rewardedadd = ViewController()
var body: some View {
VStack {
// all sorts of content.
// if a user clicks on a specific button it will call func loadadd()
}
.onAppear {
rewardedadd.loadRewardedAd()
}
}
func loadadd() {
rewardedadd.show()
}
}
In another file I have the following:
import UIKit
import GoogleMobileAds
class ViewController: UIViewController, GADFullScreenContentDelegate {
var rewardedAd: GADRewardedAd?
func loadRewardedAd() {
let request = GADRequest()
GADRewardedAd.load(withAdUnitID:"ca-app-pub-3940256099942544/1712485313",
request: request,
completionHandler: { [self] ad, error in
if let error = error {
print("Failed to load rewarded ad with error: \(error.localizedDescription)")
return
}
rewardedAd = ad
print("Rewarded ad loaded.")
rewardedAd?.fullScreenContentDelegate = self
})
}
func show() {
if let ad = rewardedAd {
ad.present(fromRootViewController: self) {
let reward = ad.adReward
print("Reward received with currency \(reward.amount), amount \(reward.amount.doubleValue)")
// TODO: Reward the user.
}
} else {
print("Ad wasn't ready")
}
}
func ad(_ ad: GADFullScreenPresentingAd, didFailToPresentFullScreenContentWithError error: Error) {
print("Ad did fail to present full screen content. Error: \(error.localizedDescription)")
}
func adWillPresentFullScreenContent(_ ad: GADFullScreenPresentingAd) {
print("Ad will present full screen content.")
}
func adDidDismissFullScreenContent(_ ad: GADFullScreenPresentingAd) {
print("Ad did dismiss full screen content.")
}
}
I keep getting the following error:
Ad did fail to present full screen content. Error: The provided view controller is not being presented.
That is the error from func ad in class ViewController. Does anyone know what I can do to fix this issue?
---- edit ----
I have rebuild my viewcontroller class to the following:
import SwiftUI
import GoogleMobileAds
struct RewardedAd: UIViewRepresentable {
#State private var rewardedAd: GADRewardedAd
let adUnitId = "ca-app-pub-3940256099942544/1712485313"
func makeCoordinator() -> CoordinatorVideoAd {
return CoordinatorVideoAd()
}
func makeUIView(context: Context) -> GADRewardedAd {
let request = GADRequest()
rewardedAd.load(withAdUnitID: adUnitId,
request: request, completionHandler: { [self] ad, error in
if let error = error {
print("Failed to load rewarded ad with error: \(error.localizedDescription)")
return
}
rewardedAd = ad
print("Rewarded ad loaded.")
rewardedAd?.fullScreenContentDelegate = self
})
return rewardedAd
}
func updateUIView(_ uiView: GADRewardedAd, context: Context) { }
class CoordinatorVideoAd: NSObject, GADFullScreenContentDelegate {
func ad(_ ad: GADFullScreenPresentingAd, didFailToPresentFullScreenContentWithError error: Error) {
print("Ad did fail to present full screen content.")
}
func adWillPresentFullScreenContent(_ ad: GADFullScreenPresentingAd) {
print("Ad will present full screen content.")
}
func adDidDismissFullScreenContent(_ ad: GADFullScreenPresentingAd) {
print("Ad did dismiss full screen content.")
}
}
}
I new have two error's:
Type 'RewardedAd' does not conform to protocol 'UIViewRepresentable'
Static member 'load' cannot be used on instance of type 'GADRewardedAd'
How can I fix this?
I believe that's now how you present a UIKit component to a swiftUI. You should read it in the apple docs about how to do it.
Here's an example how to do it :
First, create a struct that inherit the UIViewControllerRepresentable protocol
it'll look like this
struct SomethingViewController: UIViewControllerRepresentable {
typealias UIViewControllerType = [enter your UIViewController type]
}
add the type, for example, here i;m creating a FakeViewController
typealias UIViewControllerType = FakeViewController
it'll generate the nescessary method
Last, just customize it whahtever you want
struct SomethingViewController: UIViewControllerRepresentable {
#Binding var colorIDX: Int
let colors: [UIColor] = [.red,.blue,.green]
func makeUIViewController(context: Context) -> FakeViewController {
//Construct your ViewController
let viewController = FakeViewController()
viewController.bgColor = .systemPink
return viewController
}
func updateUIViewController(_ uiViewController: FakeViewController, context: Context) {
// Update your ViewController
uiViewController.view.backgroundColor = colors[colorIDX]
}
}
and setup in the SwiftUIView to look like this (depending on how do you want it, navigate to UIViewController or present it)
struct ContentView: View {
#State var isShow: Bool = false
#State var colorIDX: Int = 0
var body: some View {
VStack {
Button("show") {
if colorIDX >= 2 {
colorIDX = 0
} else {
colorIDX += 1
}
isShow.toggle()
}
}.sheet(isPresented: $isShow) {
} content: {
SomethingViewController(colorIDX: $colorIDX)
}
}
}
you see, every time i update something in the swiftUI View, it updates my ViewController and everytime I push the button, it'll present the ViewController
if you want to use a navigationController, just use it like any other swiftUI component/views
And that;s how you use a UIViewController in SwiftUI

How can I access an SwiftUI environment object from an NSObject?

I'm trying to increment the variable adsWatched by 1 when an ad is dismissed by changing the environment object from a UIViewController and passing it to a SwiftUI view. I've tried putting an environment object in my NSObject class AdManager, which contains the function I want to change the environment object in, but I get the error message "Unknown attribute 'EnvironmentObject'":
public protocol AdManagerRewardDelegate{
func rewardAdGiveRewardToUser(type:String, amount: NSDecimalNumber)
func rewardAdFailedToLoad()
func rewardAdDidReceive(
rewardViewController: UIViewController?,
rewardedAd: GADRewardedAd?,
delegate: AdManager
)
func rewardAdDidOpen()
func rewardAdDidClose()
func rewardAdFailedToPresent()
}
//default implementation AdManagerRewardDelegate
public extension AdManagerRewardDelegate{
func rewardAdGiveRewardToUser(type:String, amount: NSDecimalNumber) {}
func rewardAdFailedToLoad() {}
func rewardAdDidReceive(
rewardViewController: UIViewController?,
rewardedAd: GADRewardedAd?,
delegate: AdManager
) {
if rewardedAd?.isReady == true {
if let rewardViewController = rewardViewController {
rewardedAd?.present(fromRootViewController: rewardViewController, delegate: delegate)
}
}
}
func rewardAdDidOpen() {}
func rewardAdDidClose() {}
func rewardAdFailedToPresent() {}
}
public class AdManager: NSObject {
public static let shared = AdManager()
public var ADS_DISABLED = false
public var delegateReward: AdManagerRewardDelegate?
private var viewController:UIViewController?
private var bannerViewContainer:UIView?
private var rewardViewController:UIViewController?
var interestial:GADInterstitial?
private var testDevices:[String] = [""]
private var rewardedAd: GADRewardedAd?
let borderSizeBetweenBannerAndContent:CGFloat = 5
#EnvironmentObject var settings: UserSettings
public override init() {
super.init()
}
public func configureWithApp(){
GADMobileAds.sharedInstance().requestConfiguration.testDeviceIdentifiers = testDevices
}
public func setTestDevics(testDevices: [String]){
self.testDevices = testDevices
self.testDevices += [kGADSimulatorID as! String ] //all simulator
}
private func getGADRequest() -> GADRequest{
let request = GADRequest()
return request
}
// MARK:- Reward Video Ads
public func loadAndShowRewardAd(_ adUnit: String, viewController: UIViewController){
self.rewardViewController = viewController
rewardedAd = GADRewardedAd(adUnitID: adUnit)
rewardedAd?.load(getGADRequest()) { error in
if let error = error {
// Handle ad failed to load case.
print("Reward based video ad failed to load. \(error.debugDescription)")
self.delegateReward?.rewardAdFailedToLoad()
} else {
// Ad successfully loaded.
print("Reward based video ad is received.")
self.delegateReward?.rewardAdDidReceive(
rewardViewController: self.rewardViewController,
rewardedAd: self.rewardedAd,
delegate: self
)
}
}
}
}
// MARK:- GADRewardBasedVideoAdDelegate
extension AdManager : GADRewardedAdDelegate {
public func rewardedAdDidPresent(_ rewardedAd: GADRewardedAd) {
print("Rewarded ad presented.")
delegateReward?.rewardAdDidOpen()
}
public func rewardedAd(_ rewardedAd: GADRewardedAd, didFailToPresentWithError error: Error) {
print("Rewarded ad failed to present.")
delegateReward?.rewardAdFailedToPresent()
}
public func rewardedAdDidDismiss(_ rewardedAd: GADRewardedAd) {
print("Rewarded ad dismissed.")
// i want to increase adsWatched by 1 here
settings.adsWatched += 1
delegateReward?.rewardAdDidClose()
}
public func rewardedAd(_ rewardedAd: GADRewardedAd, userDidEarn reward: GADAdReward) {
print("Reward received with currency: \(reward.type), amount \(reward.amount).")
delegateReward?.rewardAdGiveRewardToUser(type: reward.type, amount: reward.amount)
}
}
I've tried passing the object to it from my view controller, but I get the error message: "Instance member 'environmentObject' of type 'View' cannot be used on instance of nested type 'Ads.ViewController'" for this:
struct Ads: UIViewControllerRepresentable {
#EnvironmentObject var settings: UserSettings
#Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>
typealias UIViewControllerType = UIViewController
func makeUIViewController(context: Context) -> UIViewController {
return ViewController()
}
func updateUIViewController(_ uiView: UIViewController, context: Context) {
}
class ViewController: UIViewController, GADRewardedAdDelegate, AdManagerRewardDelegate {
var rewardedAd: GADRewardedAd?
#Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>
#EnvironmentObject var settings: UserSettings
override func viewDidLoad() {
super.viewDidLoad()
AdManager.shared.loadAndShowRewardAd(AdIds.rewarded.rawValue, viewController: self, environmentObject(settings))
// error messages here^: "Extra argument in call","Instance member 'environmentObject' of type 'View' cannot be used on instance of nested type 'Ads.ViewController'"
AdManager.shared.delegateReward = self
}
func rewardedAd(_ rewardedAd: GADRewardedAd, userDidEarn reward: GADAdReward) {
print("Reward received: \(reward.type), amount \(reward.amount).")
}
}
}
Is there any way I can change the variable from an NSObject?
The settings here is just a reference so you can pass it via arguments
public func loadAndShowRewardAd(_ adUnit: String, viewController: UIViewController,
settings: UserSettings) {
self.rewardViewController = viewController
// ... other code
}
and in your representable
struct Ads: UIViewControllerRepresentable {
#EnvironmentObject var settings: UserSettings
#Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>
typealias UIViewControllerType = UIViewController
func makeUIViewController(context: Context) -> UIViewController {
return ViewController(settings: settings, mode: presentationMode)
}
func updateUIViewController(_ uiView: UIViewController, context: Context) {
}
class ViewController: UIViewController, GADRewardedAdDelegate, AdManagerRewardDelegate {
var rewardedAd: GADRewardedAd?
var presentationMode: Binding<PresentationMode>
var settings: UserSettings
init(settings: UserSettings, mode: Binding<PresentationMode>) {
self.settings = settings
self.presentationMode = mode
}
override func viewDidLoad() {
super.viewDidLoad()
AdManager.shared.loadAndShowRewardAd(AdIds.rewarded.rawValue, viewController: self, settings: settings)
// error messages here^: "Extra argument in call","Instance member 'environmentObject' of type 'View' cannot be used on instance of nested type 'Ads.ViewController'"
AdManager.shared.delegateReward = self
}
func rewardedAd(_ rewardedAd: GADRewardedAd, userDidEarn reward: GADAdReward) {
print("Reward received: \(reward.type), amount \(reward.amount).")
}
}
}

Determine When Google Banner Ad is loaded in SwiftUI

This is coded in Swift
I have successfully added a Google Banner Ad to a settings page in my application. I have implemented it below:
import Foundation
import SwiftUI
import GoogleMobileAds
struct AdView : UIViewRepresentable
{
#Binding var AdLoaded : Bool
func makeUIView(context: UIViewRepresentableContext<AdView>) -> GADBannerView
{
let banner = GADBannerView(adSize: kGADAdSizeBanner)
banner.adUnitID = "realAdId"
banner.rootViewController = UIApplication.shared.windows.first?.rootViewController
banner.load(GADRequest())
return banner
}
func updateUIView(_ uiView: GADBannerView, context: UIViewRepresentableContext<AdView>){}
public func adViewDidReceiveAd(_ bannerView: GADBannerView)
{
AdLoaded = true
if (DEBUG_ADS ) { print("Banner Ad Did Find Ad!") }
}
public func adView(_ bannerView: GADBannerView, didFailToReceiveAdWithError error: GADRequestError)
{
AdLoaded = false
if (DEBUG_ADS ) { print(error) }
}
}
I am aware that since I don't have GADBannerViewDelegate in this struct, that I don't have access to the functions I have implemented (adViewDidReceiveAd and adView...didFailToReceiveAdWithError). They never get called as is.
I have messed around a lot but I can not find a proper way to implemented these delegate functions.
My intended functionality is to implement this AdView in my settings page as such:
if ( someBindingVariable??? )
{
AdView(AdLoaded: self.$AdLoaded)
}
The way it currently works is that a space is left for the AdView before it is loaded. Once the ad is loaded, the space is filled with the ad. I want the space to only be added once an ad is loaded.
Thanks for you help, and let me know if I am not clear on something!
From #Asperi comment, I implemented a Coordinator with my AdView and it worked!
See below:
import Foundation
import SwiftUI
import GoogleMobileAds
struct AdView : UIViewRepresentable
{
#Binding public var AdLoaded : Bool
public var bannerId : String
func makeCoordinator() -> Coordinator
{
Coordinator(self)
}
func makeUIView(context: UIViewRepresentableContext<AdView>) -> GADBannerView
{
let banner = GADBannerView(adSize: kGADAdSizeBanner)
banner.adUnitID = bannerId
banner.rootViewController = UIApplication.shared.windows.first?.rootViewController
banner.load(GADRequest())
banner.delegate = context.coordinator
return banner
}
func updateUIView(_ uiView: GADBannerView, context: UIViewRepresentableContext<AdView>){}
class Coordinator: NSObject, GADBannerViewDelegate
{
var parent: AdView
init(_ parent: AdView)
{
self.parent = parent
}
func adViewDidReceiveAd(_ bannerView: GADBannerView)
{
parent.AdLoaded = true
if ( DEBUG_ADS ) { print("Ad View Did Receive Ad For ID: \(parent.bannerId)")}
}
func adView(_ bannerView: GADBannerView, didFailToReceiveAdWithError error: GADRequestError)
{
parent.AdLoaded = false
if ( DEBUG_ADS ) { print("Ad View Failed To Receive Ad For ID: \(parent.bannerId)")}
}
}
}

iOS 13 - Camera hangs in VNDocumentCameraViewController

When using VisionKit's VNDocumentCameraViewController for scanning documents the camera hangs after some seconds. The scan is implemented in a ViewController, which is used in SwiftUI.
The implementation of a DocumentScannerViewController:
import UIKit
import VisionKit
import SwiftUI
final class DocumentScannerViewController: UIViewController, VNDocumentCameraViewControllerDelegate, UIViewControllerRepresentable {
public typealias UIViewControllerType = DocumentScannerViewController
public func makeUIViewController(context: UIViewControllerRepresentableContext<DocumentScannerViewController>) -> DocumentScannerViewController {
return DocumentScannerViewController()
}
public func updateUIViewController(_ uiViewController: DocumentScannerViewController, context: UIViewControllerRepresentableContext<DocumentScannerViewController>) {
}
override func viewDidLoad() {
super.viewDidLoad()
let scannerViewController = VNDocumentCameraViewController()
scannerViewController.delegate = self as VNDocumentCameraViewControllerDelegate
view.addSubview(scannerViewController.view)
}
func documentCameraViewController(_ controller: VNDocumentCameraViewController, didFinishWith scan: VNDocumentCameraScan) {
}
func documentCameraViewControllerDidCancel(_ controller: VNDocumentCameraViewController) {
}
func documentCameraViewController(_ controller: VNDocumentCameraViewController, didFailWithError error: Error) {
}
}
And the implementation of the ContentView:
import SwiftUI
struct ContentView: View {
var body: some View {
DocumentScannerViewController()
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
The document scan camera launches and works for a short period of time. Then camera just stops moving.
Any idea what causes this behavior?
Apple does provide a way to use ViewControllers inside SwiftUI view with UIViewControllerRepresentable which you have to implement this way.
First declare your View as follows:
import SwiftUI
import UIKit
import Vision
import VisionKit
struct ScanningVNDocumentView: UIViewControllerRepresentable {
// implement your custom init() in case ..
typealias UIViewControllerType = VNDocumentCameraViewController
func makeUIViewController(context: UIViewControllerRepresentableContext<ScanningVNDocumentView>) -> VNDocumentCameraViewController {
let viewController = VNDocumentCameraViewController()
viewController.delegate = context.coordinator
return viewController
}
func updateUIViewController(_ uiViewController: VNDocumentCameraViewController, context: UIViewControllerRepresentableContext<ScanningVNDocumentView>) {
}
func makeCoordinator() -> Coordinator {
//Coordinator is Apple bridge between SwiftUI and ViewController
return Coordinator() `// this basically call init of the UIViewControllerRepresentable above`
}
final class Coordinator: NSObject, VNDocumentCameraViewControllerDelegate {
#Environment(\.presentationMode) var presentationMode
init() {
}
// implement VNDocumentCameraViewControllerDelegate methods where you can dismiss the ViewController
func documentCameraViewController(_ controller: VNDocumentCameraViewController, didFinishWith scan: VNDocumentCameraScan) {
print("user did press save with scanned docs numbers \(scan.pageCount) ")
}
func documentCameraViewControllerDidCancel(_ controller: VNDocumentCameraViewController) {
print("Did press cancel")
}
func documentCameraViewController(_ controller: VNDocumentCameraViewController, didFailWithError error: Error) {
print("Document camera view controller did finish with error ", error)
}
}
}
You can now call your view this way:
var body: some View {
ScanningVNDocumentView()
}