Starting with iOS16, there was a problem with the implementation of AdMob interstitial advertising. I am using the SwiftUI framework. I looked at many sources, but unfortunately I did not find a solution. Link to on a similar topic, but without a solution. On iOS 15 everything works fine, but on iOS 16 the app crashes with the following error:
SwiftUI/UIViewControllerRepresentable.swift:332: Fatal error:
UIViewControllerRepresentables must be value types: InterstitialAdView
2022-11-20 15:34:32.811071+0300 MyApp[38437:1156286]
SwiftUI/UIViewControllerRepresentable.swift:332: Fatal error:
UIViewControllerRepresentables must be value types: InterstitialAdView
As I understand, to implement the UIViewControllerRepresentable protocol, you must use a struct, but the NSObject and GADFullScreenContentDelegate protocols can only be applied in a class. Question: how can the code be modified to meet the above requirements? I am using the following code (original from here):
// InterstitialAdView
final class InterstitialAdView: NSObject, UIViewControllerRepresentable, GADFullScreenContentDelegate {
//Here's the Ad Object we just created
let interstitialAd = InterstitialAd.shared.interstitialAd
#Binding var isPresented: Bool
var adUnitId: String
init(isPresented: Binding<Bool>, adUnitId: String) {
self._isPresented = isPresented
self.adUnitId = adUnitId
super.init()
interstitialAd?.fullScreenContentDelegate = self //Set this view as the delegate for the ad
}
//Make's a SwiftUI View from a UIViewController
func makeUIViewController(context: Context) -> UIViewController {
let view = UIViewController()
//Show the ad after a slight delay to ensure the ad is loaded and ready to present
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(1)) {
self.showAd(from: view)
}
return view
}
func updateUIViewController(_ uiViewController: UIViewController, context: Context) {
}
//Presents the ad if it can, otherwise dismisses so the user's experience is not interrupted
func showAd(from root: UIViewController) {
if let ad = interstitialAd {
ad.present(fromRootViewController: root)
} else {
self.isPresented.toggle()
}
}
func adDidDismissFullScreenContent(_ ad: GADFullScreenPresentingAd) {
//Prepares another ad for the next time view presented
InterstitialAd.shared.loadAd(withAdUnitId: adUnitId)
//Dismisses the view once ad dismissed
isPresented.toggle()
}
}
Related
I have a GKTurnBasedMatchmakerViewController with an associated GKTurnBasedMatchmakerViewControllerDelegate. According to this documentation page, the function turnBasedMatchmakerViewControllerWasCancelled is called when the matchmaker is dismissed (without inviting any players). But I tried dismissing the matchmaker when testing, and this function is never even entered. Am I misunderstanding something about turnBasedMatchmakerViewControllerWasCancelled?
struct MatchmakerView: UIViewControllerRepresentable {
func makeUIViewController(context: Context) -> GKTurnBasedMatchmakerViewController {
let request = // ...
let matchmakerViewController = GKTurnBasedMatchmakerViewController(matchRequest: request)
let matchmakerDelegate = MatchmakerDelegate()
matchmakerViewController.turnBasedMatchmakerDelegate = matchmakerDelegate
return matchmakerViewController
}
func updateUIViewController(_ uiViewController: GKTurnBasedMatchmakerViewController, context: Context) {}
}
private class MatchmakerDelegate: NSObject, GKTurnBasedMatchmakerViewControllerDelegate {
func turnBasedMatchmakerViewControllerWasCancelled(_ viewController: GKTurnBasedMatchmakerViewController) {
// FIXME: Dismissing the matchmaker does not call this method.
viewController.dismiss(animated: true)
print("This print statement is never reached")
}
}
Resolved: MatchmakerView was missing a makeCoordinator method.
I'm trying to implement a PHPickerViewController using SwiftUI and The Composable Architecture. (Not that I think that's particularly relevant but it might explain why some of my code is like it is).
Sample project
I've been playing around with this to try and work it out. I created a little sample Project on GitHub which removes The Composable Architecture and keeps the UI super simple.
https://github.com/oliverfoggin/BrokenImagePickers/tree/main
It looks like iOS 15 is breaking on both the UIImagePickerViewController and the PHPickerViewController. (Which makes sense as they both use the same UI under the hood).
I guess the nest step is to determine if the same error occurs when using them in a UIKit app.
My code
My code is fairly straight forward. It's pretty much just a reimplementation of the same feature that uses UIImagePickerViewController but I wanted to try with the newer APIs.
My code looks like this...
public struct ImagePicker: UIViewControllerRepresentable {
// Vars and setup stuff...
#Environment(\.presentationMode) var presentationMode
let viewStore: ViewStore<ImagePickerState, ImagePickerAction>
public init(store: Store<ImagePickerState, ImagePickerAction>) {
self.viewStore = ViewStore(store)
}
// UIViewControllerRepresentable required functions
public func makeUIViewController(context: UIViewControllerRepresentableContext<ImagePicker>) -> some UIViewController {
// Configuring the PHPickerViewController
var config = PHPickerConfiguration()
config.filter = PHPickerFilter.images
let picker = PHPickerViewController(configuration: config)
picker.delegate = context.coordinator
return picker
}
public func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) {}
public func makeCoordinator() -> Coordinator {
Coordinator(self)
}
// This is the coordinator that acts as the delegate
public class Coordinator: PHPickerViewControllerDelegate {
let parent: ImagePicker
init(_ parent: ImagePicker) {
self.parent = parent
}
public func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
picker.dismiss(animated: true)
guard let itemProvider = results.first?.itemProvider,
itemProvider.canLoadObject(ofClass: UIImage.self) else {
return
}
itemProvider.loadObject(ofClass: UIImage.self) { [weak self] image, error in
if let image = image as? UIImage {
DispatchQueue.main.async {
self?.parent.viewStore.send(.imagePicked(image: image))
}
}
}
}
}
}
All this works in the simple case
I can present the ImagePicker view and select a photo and it's all fine. I can cancel out of it ok. I can even scroll down the huge collection view of images that I have. I can even see the new image appear in my state object and display it within my app. (Note... this is still WIP and so the code is a bit clunky but that's only to get it working initially).
The problem case
The problem is that when I tap on the search bar in the PHPickerView (which is a search bar provided by Apple in the control, I didn't create it or code it). It seems to start to slide up the keyboard and then the view goes blank with a single message in the middle...
Unable to Load Photos
[Try Again]
I also get a strange looking error log. (I removed the time stamps to shorten the lines).
// These happen on immediately presenting the ImagePicker
AppName[587:30596] [Picker] Showing picker unavailable UI (reason: still loading) with error: (null)
AppName[587:30596] Writing analzed variants.
// These happen when tapping the search bar
AppName[587:30867] [lifecycle] [u A95D90FC-C77B-43CC-8FC6-C8E7C81DD22A:m (null)] [com.apple.mobileslideshow.photospicker(1.0)] Connection to plugin interrupted while in use.
AppName[587:31002] [lifecycle] [u A95D90FC-C77B-43CC-8FC6-C8E7C81DD22A:m (null)] [com.apple.mobileslideshow.photospicker(1.0)] Connection to plugin invalidated while in use.
AppName[587:30596] [Picker] Showing picker unavailable UI (reason: crashed) with error: (null)
AppName[587:30596] viewServiceDidTerminateWithError:: Error Domain=_UIViewServiceInterfaceErrorDomain Code=3 "(null)" UserInfo={Message=Service Connection Interrupted}
Tapping the "Try Again" button reloads the initial scroll screen and I can carry on using it. But tapping the search bar again just shows the same error.
I'm usually the first one to point out that the error is almost definitely not with the Apple APIs but I'm stumped on this one. I'm not sure what it is that I'm doing that is causing this to happen?
Is it the fact that it's in a SwiftUI view?
Recreated the project in UIKit
I remade the same project using UIKit... https://github.com/oliverfoggin/UIKit-Image-Pickers
And I couldn't replicate the crash at all.
Also... if you are taking any sort of screen recording of the device the crash will not happen. I tried taking a recording on the device itself and couldn't replicate it. I also tried doing a movie recording from my Mac using the iPhone screen and couldn't replicate the crash. But... the instant I stopped the recording on QuickTime the crash was replicable again.
This fixed it for me .ignoreSafeArea(.keyboard) like #Frustrated_Student mentions.
To elaborate on #Frustrated_Student this issue has to do with the UIViewControllerRepresentable treating the view like many SwiftUI views to automatically avoid the keyboard. If you are presenting the picker using a sheet as I am then you can simply add the .ignoreSafeArea(.keyboard) to the UIViewControllerRepresentable view in my case I called it ImagePicker here is a better example.
Where to add it the .ignoreSafeArea(.keyboard)
.sheet(isPresented: $imagePicker) {
ImagePicker(store: store)
.ignoresSafeArea(.keyboard)
}
This is #Fogmeister code:
public struct ImagePicker: UIViewControllerRepresentable {
// Vars and setup stuff...
#Environment(\.presentationMode) var presentationMode
let viewStore: ViewStore<ImagePickerState, ImagePickerAction>
public init(store: Store<ImagePickerState, ImagePickerAction>) {
self.viewStore = ViewStore(store)
}
// UIViewControllerRepresentable required functions
public func makeUIViewController(context: UIViewControllerRepresentableContext<ImagePicker>) -> some UIViewController {
// Configuring the PHPickerViewController
var config = PHPickerConfiguration()
config.filter = PHPickerFilter.images
let picker = PHPickerViewController(configuration: config)
picker.delegate = context.coordinator
return picker
}
public func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) {}
public func makeCoordinator() -> Coordinator {
Coordinator(self)
}
// This is the coordinator that acts as the delegate
public class Coordinator: PHPickerViewControllerDelegate {
let parent: ImagePicker
init(_ parent: ImagePicker) {
self.parent = parent
}
public func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
picker.dismiss(animated: true)
guard let itemProvider = results.first?.itemProvider,
itemProvider.canLoadObject(ofClass: UIImage.self) else {
return
}
itemProvider.loadObject(ofClass: UIImage.self) { [weak self] image, error in
if let image = image as? UIImage {
DispatchQueue.main.async {
self?.parent.viewStore.send(.imagePicked(image: image))
}
}
}
}
}
}
Well.. this seems to be an iOS bug.
I have cerated a sample project here that shows the bug... https://github.com/oliverfoggin/BrokenImagePickers
And a replica project here written with UIKit that does not... https://github.com/oliverfoggin/UIKit-Image-Pickers
I tried to take a screen recording of this happening but it appears that if any screen recording is happening (whether on device or via QuickTime on the Mac) this suppresses the bug from happening.
I have filed a radar with Apple and sent them both projects to have a look at and LOTS of detail around what's happening. I'll keep this updated with any progress on that.
Hacky workaround
After a bit of further investigation I found that you can start with SwiftUI and then present a PHPickerViewController without this crash happening.
From SwiftUI if you present a UIViewControllerRepresentable... and then from there if you present the PHPickerViewController it will not crash.
So I came up with a (very tacky) workaround that avoids this crash.
I first create a UIViewController subclass that I use like a wrapper.
class WrappedPhotoPicker: UIViewController {
var picker: PHPickerViewController?
override func viewDidLoad() {
super.viewDidLoad()
if let picker = picker {
present(picker, animated: false)
}
}
}
Then in the SwiftUI View I create this wrapper and set the picker in it.
struct WrappedPickerView: UIViewControllerRepresentable {
#Environment(\.presentationMode) var presentationMode
#Binding var photoPickerResult: PHPickerResult?
let wrappedPicker = WrappedPhotoPicker()
func makeUIViewController(context: Context) -> WrappedPhotoPicker {
var config = PHPickerConfiguration()
config.filter = .images
config.selectionLimit = 1
let picker = PHPickerViewController(configuration: config)
picker.delegate = context.coordinator
wrappedPicker.picker = picker
return wrappedPicker
}
func updateUIViewController(_ uiViewController: WrappedPhotoPicker, context: Context) {}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
class Coordinator: PHPickerViewControllerDelegate {
let parent: WrappedPickerView
init(_ parent: WrappedPickerView) {
self.parent = parent
}
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
parent.presentationMode.wrappedValue.dismiss()
parent.wrappedPicker.dismiss(animated: false)
parent.photoPickerResult = results.first
}
}
}
This is far from ideal as I'm presenting at the wrong time and stuff. But it works until Apple provide a permanent fix for this.
I started getting a weird UI bug after the PHPickerViewController crashed where the keyboard was not visible but my views were still being squashed. So I suspected a keyboard / avoidance issue. I disabled keyboard avoidance in a parent view and managed to stop it from crashing.
.ignoresSafeArea(.keyboard)
.... still a iOS bug in 15.0. I've modified Fogmeister's class Coordinator to return the image in addition to the PHPickerResult.
struct WrappedPickerView: UIViewControllerRepresentable {
#Environment(\.presentationMode) var presentationMode
#Binding var photoPickerResult: PHPickerResult?
#Binding var image: UIImage?
let wrappedPicker = WrappedPhotoPicker()
func makeUIViewController(context: Context) -> WrappedPhotoPicker {
var config = PHPickerConfiguration()
config.filter = .images
config.selectionLimit = 1
let picker = PHPickerViewController(configuration: config)
picker.delegate = context.coordinator
wrappedPicker.picker = picker
return wrappedPicker
}
func updateUIViewController(_ uiViewController: WrappedPhotoPicker, context: Context) {}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
class Coordinator: PHPickerViewControllerDelegate {
let parent: WrappedPickerView
init(_ parent: WrappedPickerView) {
self.parent = parent
}
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
self.parent.presentationMode.wrappedValue.dismiss()
self.parent.wrappedPicker.dismiss(animated: false)
self.parent.photoPickerResult = results.first
print(results)
guard let result = results.first else {
return
}
self.parent.image = nil
DispatchQueue.global().async {
result.itemProvider.loadObject(ofClass: UIImage.self) { (object, error) in
guard let imageLoaded = object as? UIImage else {
return
}
DispatchQueue.main.async {
self.parent.image = imageLoaded
}
}
}
}
}
}
I try to present a CNContactPickerViewController inside a SwiftUI application using the UIViewControllerRepresentable protocol. As I already read, there seems to be a known issue for this not working, but I got it working quite ok using the workaround described here.
However, whenever the CNContactPickerViewController gets presented or dismissed resp., I get the following error in my output log:
[PPT] Error creating the CFMessagePort needed to communicate with PPT.
I tried to find explanations on this, but there seems to be no answer anywhere on the internet. Does someone know where this error comes from and what PPT is? Could this error have something to do with the CNContactPickerViewController not working properly with SwiftUI?
I noticed the error for the first time in the iOS 14 beta together with the Xcode 12 beta, and it is still present in iOS 14.2 with Xcode 12.2.
I don't know if the error appears on iOS 13 as well.
I already issued a feedback report about this.
I wrote a workaround using a hosting UINavigationController and here is my code:
import SwiftUI
import ContactsUI
struct ContactPickerView: UIViewControllerRepresentable {
#Environment(\.presentationMode) var presentationMode
func makeUIViewController(context: Context) -> UINavigationController {
let navController = UINavigationController()
let controller = CNContactPickerViewController()
controller.delegate = context.coordinator
navController.present(controller, animated: false, completion: nil)
return navController
}
func updateUIViewController(_ uiViewController: UINavigationController, context: Context) {
print("Updating the contacts controller!")
}
// MARK: ViewController Representable delegate methods
func makeCoordinator() -> ContactsCoordinator {
return ContactsCoordinator(self)
}
class ContactsCoordinator : NSObject, UINavigationControllerDelegate, CNContactPickerDelegate {
let parent: ContactPickerView
public init(_ parent: ContactPickerView) {
self.parent = parent
}
func contactPickerDidCancel(_ picker: CNContactPickerViewController) {
print("Contact picked cancelled!")
parent.presentationMode.wrappedValue.dismiss()
}
func contactPicker(_ picker: CNContactPickerViewController, didSelect contact: CNContact) {
print("Selected a contact")
parent.presentationMode.wrappedValue.dismiss()
}
}
}
And I use it like:
Button("Select a contact") {
openSelectContact.toggle()
}
.sheet(isPresented: $openSelectContact, onDismiss: nil) {
ContactPickerView()
}
I am new at iOS development and i have some trouble...
I use SwiftUI with architecture - VIPER/B. It is my first step in ios dev with this stack.
I take base project from here
So I have func that build my module and it return me ViewController:
final class HomeModule: ModuleInterface {
typealias View = HomeView
typealias Presenter = HomePresenter
typealias Router = HomeRouter
typealias Interactor = HomeInteractor
func build() -> UIViewController {
let presenter = Presenter()
let interactor = Interactor()
let router = Router()
let viewModel = HomeViewModel()
let view = View(presenter: presenter, viewModel: viewModel)
.environmentObject(AppEnvironment())
presenter.viewModel = viewModel
self.assemble(presenter: presenter, router: router, interactor: interactor)
let viewController = UIHostingController(rootView: view)
router.viewController = viewController
return viewController
}
}
After, i call this func in my Router:
final class LoginRouter : LoginRouterProtocol {
weak var presenter : LoginPresenter!
weak var viewController: UIViewController!
func presentHomeScreen() {
viewController.present(HomeModule().build(), animated: true, completion: nil)
}
}
I didn’t think of any other way to call the builder module except as viewController.present, and besides, weak var viewController: UIViewController! was already announced in the original template, which prompted viewController.present
And as result when I call this func in my View, i get my ViewController modally, but i can't get full screen presentation.
Func in my Presenter:
var router : LoginRouter!
func login() {
router.presentHomeScreen()
}
Func in my View:
Button(action: {
self.login()
}) {
Text("Log in")
}
func login() {
presenter.login()
}
How can I present this in full screen mode? And I want to show the segue like in default NavigationController: when new ViewController appear from left to right. Also, I try to use NavigationLink, but parameter destination must get type View, not ViewController... And it give me an error:
Type '()' cannot conform to 'View'; only struct/enum/class types can conform to protocols
I've been grinding on this issue for quite a few days now and it seems like SwiftUI's relative "newness" doesn't seem to help me with this.
My gut feeling is that I'm somehow using CNContactViewControllerDelegate wrong but both Apple's documentation as well as other questions on SO make it seem like it should work. Maybe, it is also caused by .sheet's handling, but I wasn't able to isolate the issue as well. Not wrapping NewContactView inside NavigationView also made no difference and removed the default navigation bar (as expected).
I'm showing the CNContactViewController with init(forNewContact:) to give the app's users an ability to add new contacts. Persisting and everything works fine, however, none of the delegate's functions seem to get called.
Neither dismissing the modal with the "swipe to dismiss" gesture introduced in iOS 13 calls the delegate function, nor using the navigation buttons provided by CNContactViewController.
import Foundation
import SwiftUI
import ContactsUI
struct NewContactView: UIViewControllerRepresentable {
class Coordinator: NSObject, CNContactViewControllerDelegate, UINavigationControllerDelegate {
func contactViewController(_ viewController: CNContactViewController, didCompleteWith contact: CNContact?) {
if let c = contact {
self.parent.contact = c
}
viewController.dismiss(animated: true)
}
func contactViewController(_ viewController: CNContactViewController, shouldPerformDefaultActionFor property: CNContactProperty) -> Bool {
return true
}
var parent: NewContactView
init(_ parent: NewContactView) {
self.parent = parent
}
}
#Binding var contact: CNContact
init(contact: Binding<CNContact>) {
self._contact = contact
}
typealias UIViewControllerType = CNContactViewController
func makeCoordinator() -> Coordinator {
return Coordinator(self)
}
func makeUIViewController(context: UIViewControllerRepresentableContext<NewContactView>) -> NewContactView.UIViewControllerType {
let vc = CNContactViewController(forNewContact: CNContact())
vc.delegate = makeCoordinator()
return vc
}
func updateUIViewController(_ uiViewController: NewContactView.UIViewControllerType, context: UIViewControllerRepresentableContext<NewContactView>) {
}
}
The view's code for showing the controller looks like this:
.sheet(isPresented: self.$viewModel.showNewContact, onDismiss: { self.viewModel.fetchContacts() }) {
NavigationView() {
NewContactView(contact: self.$viewModel.newContact)
}
}
Thank you for any pointers! Rubberducking sadly didn't help...
SwiftUI creates coordinator by itself and provides it to representable in context, so just use
func makeUIViewController(context: UIViewControllerRepresentableContext<NewContactView>) -> NewContactView.UIViewControllerType {
let vc = CNContactViewController(forNewContact: CNContact())
vc.delegate = context.coordinator // << here !!
return vc
}