SwiftUI alert and confirmation dialog preferredColorScheme - swift

I've noticed when setting the preferredColorScheme in my SwiftUI app the .alert and .confirmationDialog modifiers don't use the colorScheme. Also the launch screen doesn't use the colorScheme either. It seems to be using system defaults, is this normal behaviour?
struct MyApp: App {
var body: some Scene {
WindowGroup {
ContentView()
.preferredColorScheme(.light)
}
}
}
I was also unable to get better results using UIKit's overrideUserInterfaceStyle. The launch screen is still using system defaults, unsure how to solve that in SwiftUI without a launch screen. Also the code becomes pretty ugly and much more extensive.
guard let scene = scenes.first as? UIWindowScene else { return }
scene.keyWindow?.overrideUserInterfaceStyle = .light // .light .dark .unspecified

I couldn't figure out a pure SwiftUI way, but I found a simple solution. Use UIKit, then handle the changes in SwiftUI. I just needed to use .overrideUserInterfaceStyle. As for the Launch Screen in SwiftUI, add a Background color key to the dictionary. Then add a Color setfrom your assets and set the Background color key value to the name of the asset created as a String.
let scenes = UIApplication.shared.connectedScenes
guard let scene = scenes.first as? UIWindowScene else { return nil }
scene.keyWindow?.overrideUserInterfaceStyle = .light //.dark, .light, .unspecified

Related

How to implement SwiftUI’s .onDrag modifier with NSImage (macOS)

I am building a sandboxed app for macOS with SwiftUI. I have a NSImage that is displayed as Image(nsImage: myNSImage). I want that View to support drag and drop, meaning that it can be dragged to any location that can receive image files.
Here’s my approach:
Image(nsImage: myNSImage)
.onDrag({
let itemProvider = NSItemProvider()
itemProvider.suggestedName = "image.png"
itemProvider.registerDataRepresentation(for: UTType.png) {
loadHandler in
loadHandler(nsImage.pngRepresentation, nil)
print("loadHandler completed") // Never prints !!
return nil
}
return itemProvider
})
This way I can drag the View. But it seems like it isn’t able to provide the image.
The “drop”, i.e. on the Desktop, is simply not working. I would expect that it saves an "image.png" in the destination URL, but it doesn’t.
How can I implement .onDrag so that it provides a image file based on NSImage?
Edit: I already have tried different UTTypes, for example .tiff in combination with nsImage.tiffRepresentation without luck.

How can I change Mapbox style URL when device switches light/dark mode?

In my SwiftUI app I am using Mapbox and have two style URL's: One for light-mode and one for dark-mode.
Right now I am using the code below to switch style URL's in my constants file which only works if I perform a fresh build of the app...
let MAPBOX_STYLE_URL : String = {
if UITraitCollection.current.userInterfaceStyle == .dark {
return "mapbox://styles/customDarkModeUrl"
}
else {
return "mapbox://styles/customLightModeUrl"
}
}()
Where can I make the app adapt the style URL every time the device switches to dark-mode?
Could I put this code in SceneDeletegate's willEnterForeGround or didBecomeActive?? I am not sure how to perform this style url update?
Thank you!
You just need to monitor your environment color scheme on your view:
struct ContentView: View {
#Environment(\.colorScheme) var colorScheme
var mapboxStyleURL: String {
"mapbox://styles/custom" + (colorScheme == .dark ? "Dark" : "Light") + "ModeUrl"
}
var body: some View {
Text(mapboxStyleURL)
}
}

Sharing Screenshot of SwiftUI view causes crash

I am grabbing a screenshot of a sub-view in my SwiftUI View to immediately pass to a share sheet in order to share the image.
The view is of a set of questions from a text array rendered as a stack of cards. I am trying to get a screenshot of the question and make it share-able along with a link to the app (testing with a link to angry birds).
I have been able to capture the screenshot using basically Asperi's answer to the below question:
How do I render a SwiftUI View that is not at the root hierarchy as a UIImage?
My share sheet launches, and I've been able to use the "Copy" feature to copy the image, so I know it's actually getting a screenshot, but whenever I click "Message" to send it to someone, or if I just leave the share sheet open, the app crashes.
The message says it's a memory issue, but doesn't give much description of the problem. Is there a good way to troubleshoot this sort of thing? I assume it must be something with how the screenshot is being saved in this case.
Here are my extensions of View and UIView to render the image:
extension UIView {
func asImage() -> UIImage {
let renderer = UIGraphicsImageRenderer(bounds: bounds)
return renderer.image { rendererContext in
layer.render(in: rendererContext.cgContext)
}
}
}
extension View {
func asImage() -> UIImage {
let controller = UIHostingController(rootView: self)
// locate far out of screen
controller.view.frame = CGRect(x: 0, y: CGFloat(Int.max), width: 1, height: 1)
UIApplication.shared.windows.first!.rootViewController?.view.addSubview(controller.view)
let size = controller.sizeThatFits(in: UIScreen.main.bounds.size)
controller.view.bounds = CGRect(origin: .zero, size: size)
controller.view.sizeToFit()
controller.view.backgroundColor = .clear
let image = controller.view.asImage()
controller.view.removeFromSuperview()
return image
}
}
Here's an abbreviated version of my view - the button is about halfway down, and should call the private function at the bottom that renders the image from the View/UIView extensions, and sets the "questionScreenShot" variable to the rendered image, which is then presented in the share sheet.
struct TopicPage: View {
var currentTopic: Topic
#State private var currentQuestions: [String]
#State private var showShareSheet = false
#State var questionScreenShot: UIImage? = nil
var body: some View {
GeometryReader { geometry in
Button(action: {
self.questionScreenShot = render()
if self.questionScreenShot != nil {
self.showShareSheet = true
} else {
print("Did not set screenshot")
}
}) {
Text("Share Question").bold()
}
.sheet(isPresented: $showShareSheet) {
ShareSheet(activityItems: [questionScreenShot!])
}
}
}
private func render() -> UIImage {
QuestionBox(currentQuestion: self.currentQuestions[0]).asImage()
}
}
I've found a solution that seems to be working here. I start the variable where the questionScreenShot gets stored as nil to start:
#State var questionScreenShot: UIImage? = nil
Then I just make sure to set it to 'render' when the view appears, which means it loads the UIImage so if the user clicks "Share Question" it will be ready to be loaded (I think there was an issue earlier where the UIImage wasn't getting loaded in time once the sharing was done).
It also sets that variable back to nil on disappear.
.onAppear {
self.currentQuestions = currentTopic.questions.shuffled()
self.featuredQuestion = currentQuestions.last!
self.questionScreenShot = render()
}
.onDisappear {
self.questionScreenShot = nil
self.featuredQuestion = nil
}

How to get size and position of a sceneWindow under mac Catalyst

I have been able to obtain the size of a sceneWindow when I resize it using
func windowScene(_ windowScene: UIWindowScene, didUpdate previousCoordinateSpace: UICoordinateSpace, interfaceOrientation previousInterfaceOrientation: UIInterfaceOrientation, traitCollection previousTraitCollection: UITraitCollection) {
print("movement trapped \(windowScene.coordinateSpace.bounds)"
}
within the sceneDelegate. But the x,y coordinates are always 0,0 regardless of where I drag the window to. Looking to be able to dictate where the new sceneWindow is located on the mac's screen relative to the "default" sceneWindow.
you can try to convert window frame from windowScene coordinateSpace to UIScreen coordinateSpace
windowFrame = [window convertRect:window.frame toCoordinateSpace:window.windowScene.screen.coordinateSpace];
Well
I didn't find a way to get this done in UIWindow but remember, Catalyst does support AppKit and you can call it anytime use objc runtime.
So here comes up the idea:
Get NSWindows from NSApplication
Get UIWindow from view.window
Compare some magic and lookup our target NSWindow
Get the frame of that NSWindow
var targetNSWindow: AnyObject? = nil
let nsWindows = (NSClassFromString("NSApplication")?.value(forKeyPath: "sharedApplication.windows") as? [AnyObject])!
for nsWindow in nsWindows {
let uiWindows = nsWindow.value(forKeyPath: "uiWindows") as? [UIWindow] ?? []
if uiWindows.contains(view.window!) {
targetNSWindow = nsWindow
}
}
if let found = targetNSWindow {
print(found.value(forKeyPath: "_frame")!)
}
And here is a sample output.
NSRect: {{818, 296}, {964, 614}}
A little bit more, you can have your window information from sceneDelegate and compare the magic with it in a similar way. But be careful, sometimes you don't have any window when the app just loads. Do the jobs in DispatchQueue.main.async block if that happens.

Can't preview SwiftUI Canvas with RealityKit scene

I only create a new property of RealityKit.
Xcode won't be able to preview the SwiftUI canvas.
But it can build successfully.
I create the App by Xcode.
Choose Augmented Reality App and set User Interface to SwiftUI.
The project can work normally.
import SwiftUI
import RealityKit
struct ContentView : View {
var body: some View {
return VStack {
Text("123")
ARViewContainer().edgesIgnoringSafeArea(.all)
}
}
}
struct ARViewContainer: UIViewRepresentable {
func makeUIView(context: Context) -> ARView {
let arView = ARView(frame: .zero)
// Load the "Box" scene from the "Experience" Reality File
let boxAnchor = try! Experience.loadBox()
// Add the box anchor to the scene
arView.scene.anchors.append(boxAnchor)
return arView
}
func updateUIView(_ uiView: ARView, context: Context) {}
}
#if DEBUG
struct ContentView_Previews : PreviewProvider {
static var previews: some View {
ContentView()
}
}
#endif
I only create a new property of RealityKit in makeUIView function.
let test: AnchoringComponent.Target.Alignment = .horizontal
The project cannot preview canvas and appear the error
'Alignment' is not a member type of 'AnchoringComponent.Target'
The project still can compile successful.
I am so confused what I met.
Has anyone meet the same issue?
You have to fix several issues:
You can't use anchoring component in iOS Simulator or in SwiftUI Canvas Preview, because it can only be used for pinning a virtual content to the real world surfaces. So no simulator for AR apps.
RealityKit Anchors are useless in iOS Simulator mode and SwiftUI Canvas Preview mode.
// Use it only for compiled AR app, not simulated...
let _: AnchoringComponent.Target.Alignment = .horizontal
Not only anchors are useless in iOS Simulator Mode and SwiftUI Preview Mode but also other session-oriented properties (including ARView.session) like ones you can see on a picture:
Change .backgroundColor in ARView to any other desirable one. Default color sometimes doesn't allow you to see a RealityKit scene. Looks like a bug.
func makeUIView(context: Context) -> ARView {
let arView = ARView(frame: .zero)
let boxAnchor = try! Experience.loadBox()
boxAnchor.steelBox?.scale = SIMD3(5, 5, 5)
boxAnchor.steelBox?.orientation = simd_quatf(angle: Float.pi/4, axis: SIMD3(1,1,0))
arView.backgroundColor = .orange
arView.scene.anchors.append(boxAnchor)
return arView
}
And here's what you can see in SwiftUI Preview Area now:
And, of course, you have to give a Camera Permission before using AR app. And it doesn't matter what you're using: Storyboard or SwiftUI.
You need to add Camera Usage Description property and arkit string in info.plist file:
XML version looks like this:
/* info.plist
<key>NSCameraUsageDescription</key>
<string>Camera access for AR experience</string>
<key>UIRequiredDeviceCapabilities</key>
<array>
<string>armv7</string>
<string>arkit</string>
</array>
*/
After fixing these issues app compiles and works as expected (without any errors):