PDFView causes app to freeze on iOS 16, only when autoscales = true - ios

I am using a PDFView to display images in my app (built using SwifUI), simply for quick and easy pinch-to-zoom functionality. This worked perfectly in iOS 15, but since updating to iOS 16, the app freezes when attempting to load the image viewer (PhotoDetailView below). The issue persists across both the simulator and a physical device.
Here is the code I'm using:
import SwiftUI
import PDFKit
struct PhotoDetailView: UIViewRepresentable {
let image: UIImage
func makeUIView(context: Context) -> PDFView {
let view = PDFView()
view.document = PDFDocument()
guard let page = PDFPage(image: image) else { return view }
view.document?.insert(page, at: 0)
view.autoScales = true
return view
}
func updateUIView(_ uiView: PDFView, context: Context) {
// empty
}
}
When I run the code on iOS 16.0 in the simulator, I get 2 errors in the console:
[Assert] -[UIScrollView _clampedZoomScale:allowRubberbanding:]: Must be called with non-zero scale
[Unknown process name] CGAffineTransformInvert: singular matrix.
I have been able to isolate the issue to view.autoscales = true. If I print view.scaleFactor, I can see that it is 1.0 before the autoscale and 0.0 afterward (which is what appears to be prompting the errors). These errors also show up in the console when using iOS 15 in the simulator, but the images load as expected.
When view.autoscales = true is commented out, the image loads, albeit at a size that is much larger than the device screen.
Does anyone have any idea what may be causing this? I'd really like to avoid having to build a custom viewer, since I'm just trying to let users quickly pinch to zoom on images.

I managed to resolve this issue. I'm posting my solution here, in case anyone else runs into the same type of problem.
The issue only occurred when using a NavigationLink to navigate to PhotoDetailView. This led me to look more closely at my navigation stack.
After some digging, I found that the problem was related to my use of .navigationViewStyle(.stack) on the NavigationView. I needed this modifier to get things to display correctly on iOS 15, but the same modifier was breaking my image viewer with iOS 16.
My solution was to create a custom container to replace NavigationView, which conditionally uses NavigationStack for iOS 16, or NavigationView + .navigationViewStyle(.stack) for iOS 15. That worked like a charm and my image viewer is back in action.
I found inspiration for my custom container here: https://developer.apple.com/forums/thread/710377

Related

swift ios macCatalyst app does not load some views on startup on macOS Monterey

since the release of the new macOS Moterey operating system (12) my MacCatalyst application does not load some views at startup until I resize the window or change focus.
If I look at XCode's Debug View Hierarchy these views are not present.
If, on the other hand, I use the classic debug (while they are not shown) it turns out that they have the correct frame (origin and size), superviews and window.
To show them I have to resize the window (manually, using the mouse) or remove the focus from the app (for example by clicking on another window and then clicking the app window again).
Only after doing one of these two things are the views loaded.
Has anyone experienced the same behavior?
The application is developed in Swift only with UIKit and various Storyboards (without SwiftUI).
Thanks a lot to everyone.
I understand what happens. 
If it can be useful to anyone ... 
I use custom margins for my UIViewControllers. To calculate a margin, I use the minimum width of a scene as a variable.
I take this value in this way: 
var minSceneWidth:CGFloat = 400
scenes.forEach { windowScene in
if let w = windowScene.sizeRestrictions?.minimumSize.width {
if minSceneWidth > w {
minSceneWidth = w
}
}
}
Since the latest version of macOS the value "minimumSize.width" seems to be also "0" and this is not good for the calculation of my margins. 
Thank you all.

Rendering a View as an image in SwiftUI project

I am currently handling an end-to-end SwiftUI project which involves a ClockKit complication. As one of complication's templates requires an image (UIImage) I need to render (snapshot) an existing SwiftUI View to an image. I have found a couple of possible solutions to this problem, including the following View extension:
extension View {
func snapshot() -> UIImage {
let controller = UIHostingController(rootView: self)
let view = controller.view
let targetSize = controller.view.intrinsicContentSize
view?.bounds = CGRect(origin: .zero, size: targetSize)
view?.backgroundColor = .clear
let renderer = UIGraphicsImageRenderer(size: targetSize)
return renderer.image { _ in
view?.drawHierarchy(in: controller.view.bounds, afterScreenUpdates: true)
}
}
}
The problem I have is that wherever I implement this extension I keep getting "not in scope" Xcode errors ("Cannot find 'UIHostingController' in scope", "Cannot find 'UIGraphicsImageRenderer' in scope", "Cannot infer contextual base in reference to member 'clear'").
SwiftUI and UIKit are imported in these Swift files (but I think the latter shouldn't be necessary). Xcode 12.5.
Please do share some tips that could help overcome this hurdle... I am a rather inexperienced developer and I could be missing something rather obvious, but at this point I'm pulling my hair out. Whenever I implement a reasonable solution that happened to be in other questions or websites it always comes to UIKit specific commands not being in scope.
Edit: So it turns out this extension works only if implemented in main ContentView for iOS target. Does not work if put in any other directory. However I need this extension to work in files of (watchOS) Extension files (as I said it's needed for CK Complication) and it's not possible to set target membership of the main iOS ContentView also for (watchOS) Extension, as then two ContentViews would apply to one watchOS version and error occurs. How then effectively place the above extension code so it could apply for where it's needed?
The problem for the errors pop up (like Cannot find UIHostingController in scope and others) is because UIHostingController and UIGraphicsImageRenderer are not available on watchOS, meaning the extension, as written, will not compile on that platform.
Refer to my other answered question.

Default RealityKit application isn't working

When running the generated Augmented Reality app with RealityKit from Xcode, nothing appears on the screen and I'm getting weird logs on the console:
2020-09-16 16:54:29.246883+0200 TestAR[494:29654] Metal GPU Frame Capture Enabled
2020-09-16 16:54:29.247203+0200 TestAR[494:29654] Metal API Validation Enabled
2020-09-16 16:54:29.792709+0200 TestAR[494:29654] Compiler failed to build request
2020-09-16 16:54:29.792957+0200 TestAR[494:29654] [Graphics] makeRenderPipelineState failed [output of type ushort is not compatible with a MTLPixelFormatR16Float color attachement.].
2020-09-16 16:54:29.792989+0200 TestAR[494:29654] [Graphics] makeRenderPipelineState failed.
I have tried to run the app on my two devices and the result is the same. Also tried to use Xcode 12, same result. What's even more disturbing is that I got the app to work like 2 times on the 20+ tries at random without changing anything, just killing the app and restarting or closing xcode and restaring.
Here is the code, but this is just the basic generated code:
struct ContentView : View {
var body: some View {
return ARViewContainer().edgesIgnoringSafeArea(.all)
}
}
struct ARViewContainer: UIViewRepresentable {
func makeUIView(context: Context) -> ARView {
let arView = ARView(frame: .zero)
let boxAnchor = try! Experience.loadBox()
arView.scene.anchors.append(boxAnchor)
return arView
}
func updateUIView(_ uiView: ARView, context: Context) {}
}
At this point I don't really understand what I'm doing wrong, here are the additionnal informations:
macOS Catalina v10.15.5
iPhone X 13.5.1
iPhone X 13.7
iPhone 6S Plus 13.5.1
Xcode 11.7
Xcode 12
At first you have to make sure that you didn't change a content in AppDelegate.swift file. A window's root view controller must come from contentView:
let contentView = ContentView()
window.rootViewController = UIHostingController(rootView: contentView)
window.makeKeyAndVisible()
Solution I
Sometimes in Xcode you may observe a bug – your app can't be launched or run due to some errors found in intermediate compiled data folder. All you need to do is to empty a DerivedData directory.
For that in Finder just press a shortcut Cmd-Shift-G and specify the following path to get to desired directory:
~/Library/Developer/Xcode/DerivedData/
After the deletion of a content of DerivedData directory your compiled app must be working as expected.
Additional info about DerivedData:
DerivedData is a folder located in ~/Library/Developer/Xcode/DerivedData by default. It’s the location where Xcode stores all kinds of intermediate build results, generated indexes, etc. DerivedData location can be configured in Xcode preferences (Locations tab).
Solution II
If the above two steps didn't help, you should reinstall Xcode.

PlaygroundBook liveView display a SceneView error

I am currently playing with PlaygroundBook. From the Doc, it says that you can use LiveView.swift to present a always-on View in the liveView. So I create a SceneViewController in the Source Folder. In the SceneViewController, I create a Sphere which lightingModel is physicallyBased, and I using a 3D map as the background contents.
Then in the LiveView.swift
import PlaygroundSupport
let page = PlaygroundPage.current
page.current.liveView = SceneViewController()
But when I run it in the iPad Air Playground. I get no error, but the liveView didn't show anything!
I spend many years finding errors. And I have try to create a new iOS Project using the same SceneViewController, and it work very well. I got no idea.
In the last, I try to change the lightingModel to .phong, and change the background to the white color.
Then the magic thing happened! The liveView now show a black sphere and the white background.
Why?
From the WWDC Session, I learn that I can use proxy to communicate with main process in Contents.swift and LiveView process.
So next, I make a extension.
extension SceneViewController: PlaygroundLiveViewMessageHandler {
public func receive(_ message: PlaygroundValue) {
if case let .string(_) = message {
changeColor()
}
}
}
And inside the Contents.swift, I create a new function
func addSky() {
if let proxy = page.liveView as? PlaygroundRemoteLiveViewProxy {
proxy.send(.string("addSky"))
}
}
And everyTime I call it, The liveView changed, see the image below. It's the same as The first time I try to use a 3D map as the background.
Does anyone know what does it happened ?

SKStoreProductViewController in iOS 8 Actions Extension

I'm working on an iOS Action Extension that's capable of showing an SKStoreProductViewController. Action Extensions are capable of showing in two sizes, either full screen (NSExtensionActionWantsFullScreenPresentation set to YES) or in a small centered window in the screen (set to NO). I'm opting for the minimal window in the middle, my extension doesn't warrant use of the full screen.
When I show an SKStoreProductViewController modally within my extension as prescribed it ends up large and clipped in the center of the action extension. Has anybody else encountered this or found a way around it?
With NSExtensionActionWantsFullScreenPresentation set to YES, the extension behaves like a UIModalPresentationStyleFormSheet thus:
override func viewDidLoad() {
super.viewDidLoad()
preferredContentSize = CGSize(width: 540, height: 620)
}
Note that the extension is displayed full screen on an iPhone (as expected).
References:
https://developer.apple.com/library/ios/documentation/General/Reference/InfoPlistKeyReference/Articles/SystemExtensionKeys.html#//apple_ref/doc/uid/TP40014212-SW35
https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIViewController_Class/index.html#//apple_ref/c/econst/UIModalPresentationFormSheet

Resources