Using SwiftUI in Xcode 11 playground - swift-playground

I know how to use SwiftUI in Xcode 11 in a regular app project, but I was wondering if there was a way to use it in playgrounds as well, even if I couldn't use the live editor?

Sure, it's pretty easy:
import PlaygroundSupport
PlaygroundPage.current.liveView = UIHostingController(rootView: PlaygroundRootView())
public struct PlaygroundRootView: View {
public init() {}
public var body: some View {
Text("Hello World!")
}
}
see more here:
https://github.com/attoPascal/SwiftUI-Tutorial-Playground

On macOS Mojave besides using PlaygroundPage.current.liveView you can draw your SwiftUI view into an image. That way you can have multiple "live views" in your playground.
Check out this article: https://ericasadun.com/2019/06/20/swiftui-render-your-mojave-swiftui-views-on-the-fly/
The sample code is hosted here
https://gist.github.com/erica/fb5005f625207e108836175ff201d8f2
The renderedImage utility code (copyright by Erica Sadun!)
import PlaygroundSupport
import SwiftUI
extension UIView {
var renderedImage: UIImage {
let image = UIGraphicsImageRenderer(size: self.bounds.size).image { context in
UIColor.lightGray.set(); UIRectFill(bounds)
context.cgContext.setAlpha(0.75)
self.layer.render(in: context.cgContext)
}
return image
}
}
extension View {
var renderedImage: UIImage {
let window = UIWindow(frame: CGRect(origin: .zero, size: CGSize(width: 320, height: 160)))
let hosting = UIHostingController(rootView: self)
hosting.view.frame = window.frame
window.rootViewController = hosting
window.makeKey()
return hosting.view.renderedImage
}
}

Related

Listening for click/touch events in SKScene on MacOS in a SwiftUI SpriteView

I'm trying to intercept mouse click events inside SpriteKit SKScene inside a a SwiftUI SpriteView in Mac OS. Overriding touchesBegun() that accepts an NSEvent object is never called when I click mouse inside the view. isUserInteractionEnabled is set to true. What am I missing? Running in Xcode 13.2 with deployment target 12.1 for Mac OS.
import SwiftUI
import SpriteKit
class MyScene : SKScene {
override func touchesBegan(with event: NSEvent) {
print(event)
}
}
var scene : MyScene {
let r = MyScene()
r.isUserInteractionEnabled = true
r.size = .init(width: 500, height: 500)
r.scaleMode = .aspectFill
r.backgroundColor = .orange
return r
}
struct ContentView: View {
var body: some View {
SpriteView.init(scene: scene)
.frame(width: 500, height: 500)
.padding()
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
On macOS SKScene inherits from NSResponder, so we override mouse handlers, like
class MyScene : SKScene {
override func mouseDown(with event: NSEvent) { // << here !!
print(event)
}
}
Tested with Xcode 14 / macOS 12.5

Swift animation freezes when page loads, swiftui

I use a Lottie animation in SwiftUI. Sometimes, when pages loads, animation freezes for a couple of seconds. I would like to know if it is possible to dispatch task or do anything in order to have the animation never freeze. File is stores locally in the app, Lottie is displayed in SwiftUI:
if condition { LottieLoading() }
import SwiftUI import Lottie
struct LottieLoading: UIViewRepresentable {
func makeUIView(context: UIViewRepresentableContext<LottieLoading>) -> UIView {
let view = UIView(frame: .zero)
let animationView = AnimationView()
let animationLottie = Animation.named("progress_relat")
animationView.animation = animationLottie
animationView.contentMode = .scaleAspectFit
animationView.loopMode = .loop
animationView.animationSpeed = 2
animationView.play()
animationView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(animationView)
NSLayoutConstraint.activate([
animationView.heightAnchor.constraint(equalTo: view.heightAnchor),
animationView.widthAnchor.constraint(equalTo: view.widthAnchor)
])
return view
}
func updateUIView(_ uiView: UIViewType, context: Context) {
}
}

How to position NSPopover in status bar application (macOS)

I have created a macOS status bar application using SwiftUI and i finally have everything working the way i want it. The only problem is that when i use it on full screen the status bar hides and the popover menu gets chopped off. Any ideas?
MyApp.swift:
import SwiftUI
#main
struct MyApp: App {
#NSApplicationDelegateAdaptor(AppDelegate.self) var delegate;
var body: some Scene {
Settings {
ContentView()
}
}
}
class AppDelegate: NSObject,NSApplicationDelegate {
var statusItem: NSStatusItem!
var popOver: NSPopover!
func applicationDidFinishLaunching(_ notification: Notification){
let contentView = ContentView()
let popOver = NSPopover();
popOver.behavior = .transient
popOver.animates = true
popOver.contentViewController = NSHostingController(rootView: contentView)
popOver.setValue(true, forKeyPath: "shouldHideAnchor")
self.popOver = popOver
self.statusItem = NSStatusBar.system.statusItem(withLength: CGFloat(NSStatusItem.variableLength))
if let MenuButton = self.statusItem.button {
MenuButton.image = NSImage(systemSymbolName: "display.2", accessibilityDescription: nil)
MenuButton.action = #selector(MenuButtonToggle)
}
}
#objc func MenuButtonToggle(_ sender: AnyObject){
if let button = self.statusItem.button {
if self.popOver.isShown{
self.popOver.performClose(sender)
}else {
self.popOver.show(relativeTo: button.bounds, of: button, preferredEdge: NSRectEdge.minY)
self.popOver.contentViewController?.view.window?.makeKey()
}
}
}
}
In your code just add a "random" larger size than the popover itself.
I think this happens because the size of the popover is not calculated right away so there is a race condition in there, but this seems to work pretty well for me 👌
popOver.contentSize = NSSize(width: 600, height: 1)

WKWebView shows white bar until window moved/resized

I'm trying to show a WKWebView in SwiftUI on MacOS. When the app initially loads, the WKWebView has a large, white bar at the top. Moving or resizing the window causes this to immediately disappear and display correctly. Interestingly, the blue border around the view does not exhibit the bad behavior.
My guess is that I'm missing some action in updateNSView.
I'm on MacOS 11.1 Big Sur, Xcode 12.2, Intel. Another thing to note is that I need to enable the "Outgoing Connections" entitlement in the App Sandbox to get WKWebView to render anything at all, even tho the content is provided locally from a string.
import SwiftUI
import WebKit
#main
struct ProblemWKWebViewApp: App {
var body: some Scene {
WindowGroup {
SwiftUIWebView()
.border(Color.blue, width: 2)
}
}
}
struct SwiftUIWebView: NSViewRepresentable {
public typealias NSViewType = WKWebView
func makeNSView(context: Context) -> WKWebView {
let webView = WKWebView()
webView.loadHTMLString("<body style=\"background-color: red;\"><h1>Hello World!</h1></body>", baseURL: nil)
return webView
}
func updateNSView(_ nsView: WKWebView, context: Context) {
}
}
I had the same problem. I suppose that it is a bug. However I managed to fix it with adding ignoresSafeArea():
#main
struct ProblemWKWebViewApp: App {
var body: some Scene {
WindowGroup {
SwiftUIWebView()
.border(Color.blue, width: 2)
// Shows weird black bar on top without this on macOS
.ignoresSafeArea()
}
}
}

Xcode 11.1 Only Takes Square Photos - Need 4:3

I'm using the camera to capture images for an app. Once I touch the photo button, I am
only able to save the photo in a square format. I want to preserve the 4:3 format for
older cameras and eventually allow the user to choose 16:9 if desired. I have not been
able to find any code to programmatically chose aspect ratio. I only get square. I even
tried locking the Camera Mode in Camera Settings on the device. Thankfully, that
didn't work.
It is a SwiftUI app. In the CameraViewController below, if I remove the allowsEditing
option, the picker freezes - neither the Use Photo, nor Retake buttons function. The
app does not crash, just the view is frozen. I am able to swipe down the view to
dismiss and continue using the app.
I call the PhotoCaptureView below from a button in what would be the detail view of
a master/detail equivalent.
PhotoCaptureView:
import SwiftUI
struct PhotoCaptureView: View {
#EnvironmentObject var cameraSettings: CameraSettings
#Binding var photos: [UIImage]
var body: some View {
return VStack {
CameraViewController(photos: $photos)
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
}.edgesIgnoringSafeArea(.all)
}
}
And the model view CameraViewController:
import UIKit
import SwiftUI
import MobileCoreServices
import AVFoundation
struct CameraViewController: UIViewControllerRepresentable {
#EnvironmentObject var cameraSettings: CameraSettings
#Binding var photos: [UIImage]
func updateUIViewController(_ uiViewContyroller: UIImagePickerController, context: Context) {
//no code here
}
func makeUIViewController(context: UIViewControllerRepresentableContext<CameraViewController>) -> UIImagePickerController {
let vc = UIImagePickerController()
if UIImagePickerController.isSourceTypeAvailable(UIImagePickerController.SourceType.camera) {
vc.sourceType = .camera
vc.allowsEditing = true
vc.delegate = context.coordinator
return vc
}
return UIImagePickerController()
}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
class Coordinator: NSObject, UINavigationControllerDelegate, UIImagePickerControllerDelegate {
var parent: CameraViewController
init(_ imagePickerController: CameraViewController) {
self.parent = imagePickerController
}
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
guard let image = info[.editedImage] as? UIImage else {
print("no image found")
return
}
print("Image taken successfully, with size \(image.size)")
parent.photos.append(image)
picker.dismiss(animated: true) {
print("in dismiss after successful picture")
}
}
func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
picker.dismiss(animated: true) {
print("in did cancel")
}
}
private func pickerController(_ controller: UIImagePickerController, didSelect image: UIImage?) {
controller.dismiss(animated: true, completion: nil)
}
}
}
The CameraSettings are just Bools to determine whether to present the camera option.
class CameraSettings: ObservableObject {
#Published var isAuthorized: Bool = false
#Published var isCameraAvailable: Bool = false
}
Xcode 11.1 (11A1027) I am using an iPhone X iOS 13.1.2
Any guidance would be appreciated.
The info setting of didFinishPickingMediaWithInfo needs to specify "originalImage" to avoid the square only picture. Even with the edited version, however, it seems you can only move the image around - not change the aspect.
change the info line above to:
guard let image = info[.originalImage] as? UIImage else {
Then set:
vc.allowsEditing = false
Importantly the documentation tells me that: "The UIImagePickerController class supports portrait mode only."

Resources