Trying to implement swiftui preview on uikit project - xcode

Why my preview crashes when I apply this code at the bottom of my viewController?
I am on iMac M1. Is it something related to M1? Because I copied the exact code from the tutorial on YouTube.
import UIKit
final class ViewController: UIViewController {
let button = UIButton()
override func viewDidLoad() {
super.viewDidLoad()
button.translatesAutoresizingMaskIntoConstraints = false
button.backgroundColor = .systemBlue
button.setTitle("Button", for: .normal)
view.addSubview(button)
NSLayoutConstraint.activate([
button.centerXAnchor.constraint(equalTo: view.centerXAnchor),
button.centerYAnchor.constraint(equalTo: view.centerYAnchor),
])
}
}
#if canImport(SwiftUI) && DEBUG
import SwiftUI
struct UIViewControllerPreview<ViewController: UIViewController>: UIViewControllerRepresentable {
let viewController: ViewController
init(_ builder: #escaping () -> ViewController) {
viewController = builder()
}
// MARK: - UIViewControllerRepresentable
func makeUIViewController(context: Context) -> ViewController {
viewController
}
func updateUIViewController(_ uiViewController: ViewController, context: UIViewControllerRepresentableContext<UIViewControllerPreview<ViewController>>) {
return
}
}
#endif
#if canImport(SwiftUI) && DEBUG
import SwiftUI
let deviceNames: [String] = [
"iPhone SE",
"iPhone 11 Pro Max",
"iPad Pro (11-inch)"
]
#available(iOS 13.0, *)
struct ViewController_Preview: PreviewProvider {
static var previews: some View {
ForEach(deviceNames, id: \.self) { deviceName in
UIViewControllerPreview {
ViewController()
}.previewDevice(PreviewDevice(rawValue: deviceName))
.previewDisplayName(deviceName)
}
}
}
#endif

Related

TextField in popover. "This would eventually crash when the view is freed"

I have written an app to display timezones in a NSStatusBar popover. All good so far but when I add a second popover from a button inside the original popover view and include a TextField I start getting problems.
The TextField shows as having focus but it refuses input. If I toggle in and out of the second popover then I get a crash.
...as the first responder for window <_NSPopoverWindow: 0x7fb9c1807bb0>, but it is in a different window ((null))! This would eventually crash when the view is freed. The first responder will be set to nil.
I'm assuming these are related.
I have extracted just the NSStatus bar setup and the two popover views and it is fully repeatable in this toy instance. I use an EventMonitor to catch a click outside of the popover to close it. I don't think that is relevant but I have included it in the toy app for completeness
AppDelegate.swift
import Cocoa
import SwiftUI
#NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
var popover: NSPopover!
var statusBarItem: NSStatusItem!
var eventMonitor: EventMonitor?
var contentView = ContentView()
func applicationDidFinishLaunching(_ aNotification: Notification) {
// Create the popover
let popover = NSPopover()
popover.behavior = .transient
popover.contentViewController = NSHostingController(rootView: contentView)
self.popover = popover
// Create the status item
self.statusBarItem = NSStatusBar.system.statusItem(withLength: CGFloat(NSStatusItem.variableLength))
if let button = self.statusBarItem.button {
button.image = NSImage(named: "Icon")
button.action = #selector(togglePopover(_:))
}
eventMonitor = EventMonitor(mask: [.leftMouseDown, .rightMouseDown]) { [unowned self] event in
if self.popover.isShown {
closePopover(event)
}
}
eventMonitor?.start()
NSApp.activate(ignoringOtherApps: true)
}
#objc func togglePopover(_ sender: AnyObject?) {
if popover.isShown {
closePopover(sender)
} else {
showPopover(sender)
}
}
func showPopover(_ sender: AnyObject?) {
if let button = statusBarItem.button {
popover.show(relativeTo: button.bounds, of: button, preferredEdge: NSRectEdge.minY)
self.popover.contentViewController?.view.window?.becomeKey()
}
eventMonitor?.start()
}
func closePopover(_ sender: AnyObject?) {
popover.performClose(sender)
eventMonitor?.stop()
}
}
open class EventMonitor {
fileprivate var monitor: AnyObject?
fileprivate let mask: NSEvent.EventTypeMask
fileprivate let handler: (NSEvent?) -> ()
public init(mask: NSEvent.EventTypeMask, handler: #escaping (NSEvent?) -> ()) {
self.mask = mask
self.handler = handler
}
deinit {
stop()
}
open func start() {
monitor = NSEvent.addGlobalMonitorForEvents(matching: mask, handler: handler) as AnyObject?
}
open func stop() {
if monitor != nil {
NSEvent.removeMonitor(monitor!)
monitor = nil
}
}
}
ContentView.swift
import SwiftUI
struct ContentView: View {
#State private var showingPopover = false
#State var value: String = "Initial Value"
var body: some View {
VStack {
Button {
showingPopover = true
} label: {
Image(systemName: "globe")
.imageScale(.large)
.foregroundColor(.accentColor)
}
.popover(isPresented: $showingPopover) {
EditView(value: $value)
}
Text("Hello, world!")
}
.padding()
}
}
struct EditView: View {
#Binding var value: String
var body: some View {
VStack {
TextField("Location ", text: $value) // the location as a string
.multilineTextAlignment(.center)
.lineLimit(1)
.onSubmit {
print("value submit \(value)")
}
}
.padding()
.frame(width:200, height: 50)
}
}
I've searched for information and found references to similar problems with windowed applications and needing to click in the 'window' before clicking in the TextField but that doesn't seem to do anything in this context
I'm building on MacOS 12.6.2 with Target set for 12.0

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

How can I get the color of the pixels from the live camera feed in SwiftUI?

I am trying to implement a feature in my app to get the color of the pixel or pixels in the center of the camera view without taking a photo, just based off of what the in-app camera view is being pointed at. My camera view is simple and posted below, but I cannot figure out how to access the data from the live preview or if it is even possible with this implementation.
import Foundation
import UIKit
import SwiftUI
struct CameraView: UIViewControllerRepresentable {
typealias UIViewControllerType = UIImagePickerController
func makeUIViewController(context: Context) -> UIViewControllerType {
let viewController = UIViewControllerType()
viewController.delegate = context.coordinator
viewController.sourceType = .camera
viewController.showsCameraControls = false
return viewController
}
func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) {
}
func makeCoordinator() -> CameraView.Coordinator {
return Coordinator(self)
}
}
extension CameraView {
class Coordinator : NSObject, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
var parent: CameraView
init(_ parent: CameraView) {
self.parent = parent
}
func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
print("Cancel pressed")
}
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
if let image = info[UIImagePickerController.InfoKey.originalImage] as? UIImage {
UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil)
}
}
}
}

SwiftUI Invoke NSPopover with Keyboard Shortcut

I'm building a menu bar application with SwiftUI for macOS Big Sur and can't figure out how to open the popover (the app's main window, since it's a menu bar app) with a keyboard shortcut. I want users to be able to view the window by pressing Command + [a letter] regardless of what else they're doing on their computer (as long as the application is open of course). Here are the main functions and code that control the popover:
#main
struct MenuBarPopoverApp: App {
#NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
var body: some Scene {
Settings{
EmptyView()
}
.commands {
MenuBarPopoverCommands(appDelegate: appDelegate)
}
}
}
class AppDelegate: NSObject, NSApplicationDelegate, ObservableObject {
var popover = NSPopover.init()
var statusBarItem: NSStatusItem?
var contentView: ContentView!
override class func awakeFromNib() {}
func applicationDidFinishLaunching(_ notification: Notification) {
print("Application launched")
NSApplication.shared.activate(ignoringOtherApps: true)
contentView = ContentView()
popover.animates = false
popover.behavior = .transient
let contentVc = NSViewController()
contentVc.view = NSHostingView(rootView: contentView.environmentObject(self))
popover.contentViewController = contentVc
statusBarItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
let itemImage = NSImage(named: "statusBarIcon")
itemImage?.isTemplate = true
statusBarItem?.button?.image = itemImage
statusBarItem?.button?.action = #selector(AppDelegate.togglePopover(_:))
}
#objc func showPopover(_ sender: AnyObject?) {
if let button = statusBarItem?.button {
NSApplication.shared.activate(ignoringOtherApps: true)
popover.show(relativeTo: button.bounds, of: button, preferredEdge: NSRectEdge.minY)
}
}
#objc func closePopover(_ sender: AnyObject?) {
popover.performClose(sender)
}
#objc func togglePopover(_ sender: AnyObject?) {
if popover.isShown {
closePopover(sender)
} else {
showPopover(sender)
}
}
}
And the MenuBarPopoverCommands (the main part of the app is a text editor, so I have a bunch of keyboard shortcuts relating to that):
struct MenuBarPopoverCommands: Commands {
let appDelegate: AppDelegate
init(appDelegate: AppDelegate) {
self.appDelegate = appDelegate
}
var body: some Commands {
CommandMenu("Edit") {
Section {
Button("Cut") {
appDelegate.contentView.editCut()
}.keyboardShortcut(KeyEquivalent("x"), modifiers: .command)
Button("Copy") {
appDelegate.contentView.editCopy()
}.keyboardShortcut(KeyEquivalent("c"), modifiers: .command)
Button("Paste") {
appDelegate.contentView.editPaste()
}.keyboardShortcut(KeyEquivalent("v"), modifiers: .command)
Button("Undo") {
appDelegate.contentView.undo()
}.keyboardShortcut(KeyEquivalent("z"), modifiers: .command)
Button("Redo") {
appDelegate.contentView.redo()
}.keyboardShortcut(KeyEquivalent("z"), modifiers: [.command, .shift])
Button("Bold") {
appDelegate.contentView.bold()
}.keyboardShortcut(KeyEquivalent("b"), modifiers: .command)
Button("Italic") {
appDelegate.contentView.italic()
}.keyboardShortcut(KeyEquivalent("i"), modifiers: .command)
Button("Select All") {
appDelegate.contentView.editSelectAll()
}.keyboardShortcut(KeyEquivalent("a"), modifiers: .command)
}
}
}
}
Swift 5 solution was presented in https://stackoverflow.com/a/58225397/3984522. However, there's a nice package, which does the job https://github.com/soffes/HotKey in a couple of lines of code:
import SwiftUI
import HotKey
#main
struct MenuBarPopoverApp: App {
#NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
var body: some Scene {
Settings{
EmptyView()
}
.commands {
MenuBarPopoverCommands(appDelegate: appDelegate)
}
}
}
class AppDelegate: NSObject, NSApplicationDelegate, ObservableObject {
var popover = NSPopover.init()
var statusBarItem: NSStatusItem?
var contentView: ContentView!
let hotKey = HotKey(key: .x, modifiers: [.control, .shift]) // Global hotkey
override class func awakeFromNib() {}
func applicationDidFinishLaunching(_ notification: Notification) {
print("Application launched")
NSApplication.shared.activate(ignoringOtherApps: true)
contentView = ContentView()
popover.animates = false
popover.behavior = .transient
let contentVc = NSViewController()
contentVc.view = NSHostingView(rootView: contentView.environmentObject(self))
popover.contentViewController = contentVc
statusBarItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
let itemImage = NSImage(systemSymbolName: "eye", accessibilityDescription: "eye")
itemImage?.isTemplate = true
statusBarItem?.button?.image = itemImage
statusBarItem?.button?.action = #selector(AppDelegate.togglePopover(_:))
hotKey.keyUpHandler = { // Global hotkey handler
self.togglePopover()
}
}
#objc func showPopover(_ sender: AnyObject? = nil) {
if let button = statusBarItem?.button {
NSApplication.shared.activate(ignoringOtherApps: true)
popover.show(relativeTo: button.bounds, of: button, preferredEdge: NSRectEdge.minY)
}
}
#objc func closePopover(_ sender: AnyObject? = nil) {
popover.performClose(sender)
}
#objc func togglePopover(_ sender: AnyObject? = nil) {
if popover.isShown {
closePopover(sender)
} else {
showPopover(sender)
}
}
}

SwiftUI Wrapper for UITextView not updating ObservedObject

I hope I'm wrong, but I have not been able to find a SwiftUI equivalent to an editable
UITextView. So, I built one using UIViewRepresentable. Populating both a SwiftUI Text
and my own view with the ObservableObject works - but updates made in my view are
not propagated to the ObservableObject. I must be missing something important with
the Binding concept. Any guidance would be appreciated.
import SwiftUI
import Combine
struct ContentView: View {
#ObservedObject var myOText: MyOText
var body: some View {
ScrollView {
VStack {
Text("This is a bound Text View")
.padding(.top, 10)
.font(.headline)
Text(myOText.inTheCourse)
.lineLimit(3)
.padding()
Text("This is a multi-line UITextView wrapper:")
.font(.headline)
MultilineTextView(myOText: myOText)
.frame(height: 100)
.padding()
Spacer()
}
}
}
}
struct MultilineTextView: UIViewRepresentable {
#ObservedObject var myOText: MyOText
func makeUIView(context: Context) -> UITextView {
let view = UITextView()
view.isScrollEnabled = true
view.isEditable = true
view.isUserInteractionEnabled = true
view.textAlignment = .center
view.font = UIFont(name: "Times New Roman", size: 20)
return view
}
func updateUIView(_ uiView: UITextView, context: Context) {
uiView.text = myOText.inTheCourse
}
}
class MyOText: ObservableObject {
#Published var inTheCourse: String = "When in the Course of human events, it becomes necessary for one people to dissolve the political bands which have connected them ..."
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView(myOText: MyOText())
}
}
Xcode Version 11.2 beta 2 (11B44), iOS 13.
Your multiline text view needs a coordinator to observe the text updates from UITextView
struct MultilineTextView: UIViewRepresentable {
#ObservedObject var myOText: MyOText
func makeUIView(context: Context) -> UITextView {
let view = UITextView()
view.isScrollEnabled = true
view.isEditable = true
view.isUserInteractionEnabled = true
view.textAlignment = .center
view.font = UIFont(name: "Times New Roman", size: 20)
view.delegate = context.coordinator
return view
}
func updateUIView(_ uiView: UITextView, context: Context) {
uiView.text = myOText.inTheCourse
}
func makeCoordinator() -> MultilineTextView.Coordinator {
Coordinator(self)
}
class Coordinator: NSObject, UITextViewDelegate {
var control: MultilineTextView
init(_ control: MultilineTextView) {
self.control = control
}
func textViewDidChange(_ textView: UITextView) {
control.myOText.inTheCourse = textView.text
}
}
}

Resources