Post

Replies

Boosts

Views

Activity

Send a Closure from NSViewRepresentable (or UIViewRepresentable) to ContentView?
I'm using NSTableView with NSViewRepresentable in my SwiftUI ContentView. I'm letting the user right-click on a table row such that the application will recognize the row number, which is achieved. The following is what I have so far. struct ContentView: View { @State private var rowSelection = -1 VStack { TableView(tableData: someData, selectedRow: $rowSelection) } } struct TableView: NSViewRepresentable { @Binding var tableData: [Dictionary<String, String>] @Binding var selectedRow: Int func makeNSView(context: Context) -> NSScrollView { let scrollView = NSScrollView(frame: .zero) let tableView = NSTableView() tableView.delegate = context.coordinator tableView.dataSource = context.coordinator let contextMenu = NSMenu() let copyRowMenuItem = NSMenuItem(title: "Copy Row", action: #selector(Coordinator.tableRowAction(_:)), keyEquivalent: "") contextMenu.addItem(copyRowMenuItem) copyRowMenuItem.target = context.coordinator tableView.menu = contextMenu scrollView.documentView = tableView scrollView.hasVerticalScroller = true scrollView.hasHorizontalScroller = true scrollView.autohidesScrollers = true return scrollView } func updateNSView(_ nsView: NSScrollView, context: Context) { } func makeCoordinator() -> Coordinator { return Coordinator(tableData: $tableData, tableInfo: $tableInfo, selectedRow: $selectedRow, rowSelected: $rowSelected) } class Coordinator: NSObject, NSTableViewDelegate, NSTableViewDataSource { @Binding var tableData: [Dictionary<String, String>] @Binding var selectedRow: Int init(tableData: Binding<[Dictionary<String, String>]>, tableInfo: Binding<[PragmaModel]>, selectedRow: Binding<Int>, rowSelected: Binding<Bool>) { self._tableData = tableData self._rowSelected = rowSelected } func numberOfRows(in tableView: NSTableView) -> Int { return tableData.count } func tableView(_ tableView: NSTableView, objectValueFor tableColumn: NSTableColumn?, row: Int) -> Any? { for i in 0..<tableInfo.count { let col = NSString(format: "%i", i) as String let identifier = NSString(format: "Column%i", i) as String if ((tableColumn?.identifier)!.rawValue == identifier) { let data = tableData[row][col] return data } } return nil } func tableViewSelectionDidChange(_ notification: Notification) { let tv = notification.object as! NSTableView if tv.selectedRow >= 0 { selectedRow = tv.selectedRow } } @objc func tableRowAction(_ sender: Any) { // closure // } } } The contextual menu works. Yet, the application needs to know when a row is clicked on. So I want to send a closure back to ContentView. How can I do that, por favor? Muchos thankos.
1
0
505
Aug ’24
Button.background(Color) under the Light Appearance
I'm trying to set the background color of a button with label like the following for a macOS application. I haven't run it for iOS. VStack(spacing: 20) { HStack(spacing: 32) { Button { showGuide.toggle() } label: { Text("Hello") .font(.title3) .frame(width: 190, height: 36) } .foregroundStyle(.primary) .background(.yellow) .clipShape(.capsule) .shadow(color: .red, radius: 8) Button { } label: { Text("Good morning") .font(.title3) .frame(width: 190, height: 36) } .foregroundStyle(.primary) .background(.pink) .clipShape(.capsule) .shadow(color: .red, radius: 8) } } .frame(maxWidth: .infinity, alignment: .center) Interestingly, those two buttons have a white background color under the light appearance as shown below. And it will get the designated background color under the dark appearance as shown below. So why don't I get the buttons colored under the light appearance? I can't figure out why it happens. Does anybody know why? Thanks.
Topic: UI Frameworks SubTopic: SwiftUI Tags:
1
0
548
Aug ’24
All Item Row Positions After List onMove(perform:)
Using List to list an array of an object isn't a problem. For example, I have simple lines of code below to list an array of some guy. struct ContentView: View { @State private var selectedFieldItem: FieldItem? private var fieldListView: some View { List(selection: $selectedFieldItem) { ForEach(fieldItems.indices, id: \.self) { index in Button { ... } label: { let fieldItem = fieldItems[index] HStack(spacing: 10) { Text("\(fieldItem.name)") } .frame(maxWidth: .infinity, alignment: .leading) } .buttonStyle(.borderless) } .onMove(perform: fieldlocate) } .listStyle(.plain) } private func fieldlocate(from source: IndexSet, to destination: Int) { fieldItems.move(fromOffsets: source, toOffset: destination) } } As you see in the picture below, I can move a row up and down. A problem that I now have with List is that I cannot know how those rows are rearranged after one of them is moved up or down. In Cocoa, you can tell the positions of all rows after one moves with tableView(_:acceptDrop:row:dropOperation:) I think I have done the same in UIKit. Can we tell the current row numbers of List items in SwiftUI? Like Item 0 moves from Row 0 to Row 2 item 1 moves from Row 1 to Row 0 item 2 moves from Row 2 to Row 1 after one drags Item 0 from the top to the bottom? Muchos thankos.
Topic: UI Frameworks SubTopic: SwiftUI
1
0
399
Aug ’24
Showing SwiftUI Below NSStatusItem When Button is Clicked on Over SwiftUI View
I see a lot of tutorials that show how to open a SwiftUI View when a NSStatusItem is clicked on. That's not what I want. I need to show a SwiftUI View when I click on a button over SwiftUI View. So far the following is what I have. import SwiftUI @main struct MyStatusApp_App: App { @NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate var body: some Scene { WindowGroup { ContentView() } } } #if os(macOS) class AppDelegate: NSObject, NSApplicationDelegate { var statusItem: NSStatusItem! private var popover: NSPopover? func applicationDidFinishLaunching(_ notification: Notification) { hideTitleBar() NSApp.setActivationPolicy(.accessory) statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength) if let button = statusItem.button { if let image = NSImage(named: "statusImage") { button.image = image } } } #endif // ContentView // import SwiftUI struct ContentView: View { @NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate var body: some View { VStack { Button("Click me!") { let popOver = NSPopover() popOver.contentViewController = NSHostingController(rootView: NotificationView()) appDelegate.statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength) if let statusBarbutton = appDelegate.statusItem.button { popOver.show(relativeTo: statusBarbutton.bounds, of: statusBarbutton, preferredEdge: .minY) } } } .frame(width: 200, height: 100) } } If I run the application and click on the button (orange arrow) over ContentView, a guy from NotificationView will appear (green rectangle). That's good. But it appears not below the status item (red arrow). It's positioned at an odd location. It's way below the status item guy. What am I doing wrong? Muchos thankos. I guess site's add image function is broken. It doesn't show my screenshot. ![]("https://developer.apple.com/forums/content/attachment/7e19bf2e-439d-4ed0-a03c-740b77e94e24" "title=Screenshot.jpg;width=364;height=400")
Topic: UI Frameworks SubTopic: SwiftUI Tags:
1
0
719
Aug ’24
Writing to Printer with Core Bluetooth
I have a very cheap Bluetooth-connected printer. And I want to print out a word or two via Core Bluetooth. It's an iOS app with the SwiftUI framework. The following is what I have for an ObservableObject class. import Foundation import CoreBluetooth class BluetoothManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeripheralDelegate { @Published var connectedDevices: [CBPeripheral] = [] @Published var powerOn = false @Published var peripheralConnected = false private var centralManager: CBCentralManager! private var peripheralName = "LX-D02" private var connectedPeripheral: CBPeripheral? private var writeCharacteristic: CBCharacteristic? private let serviceUUID = CBUUID(string:"5833FF01-9B8B-5191-6142-22A4536EF123") private let characteristicUUID = CBUUID(string: "FFE1") override init() { super.init() self.centralManager = CBCentralManager(delegate: self, queue: nil) } func startScanning() { if centralManager.state == .poweredOn { centralManager.scanForPeripherals(withServices: nil, options: nil) } } func centralManagerDidUpdateState(_ central: CBCentralManager) { if central.state == .poweredOn { powerOn = true print("Bluetooth is powered on") } else { print("Bluetooth is not available") } } func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) { if !connectedDevices.contains(peripheral) { if let localName = advertisementData["kCBAdvDataLocalName"] as? String { if localName == peripheralName { connectedDevices.append(peripheral) centralManager.connect(peripheral, options: nil) centralManager.stopScan() peripheralConnected = true print("Connected: \(peripheral.identifier.uuidString)") } } } } func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) { connectedPeripheral = peripheral peripheral.delegate = self let services = [serviceUUID] peripheral.discoverServices(services) //discoverServices(peripheral: peripheral) } func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: (any Error)?) { guard let error = error else { print("Failed connection unobserved") return } print("Error: \(error.localizedDescription)") } func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) { if let error = error { print("Failing to discover servies: \(error.localizedDescription)") return } discoverCharacteristics(peripheral: peripheral) } /* Return all available services */ private func discoverServices(peripheral: CBPeripheral) { peripheral.discoverServices(nil) } private func discoverCharacteristics(peripheral: CBPeripheral) { guard let services = peripheral.services else { return } for service in services { peripheral.discoverCharacteristics(nil, for: service) } } func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) { guard let characteristics = service.characteristics else { return } for characteristic in characteristics { let characteristicUUID = characteristic.uuid print("Discovered characteristic: \(characteristicUUID)") peripheral.setNotifyValue(true, for: characteristic) if characteristic.properties.contains(.writeWithoutResponse) { writeCharacteristic = characteristic print("You can write!!!") // Never read... } if characteristic.properties.contains(.write) { print("You can write?") writeCharacteristic = characteristic // Being read... } } func writeToPrinter() { guard let peripheral = connectedPeripheral else { print("Ughhh...") return } if let characteristic = writeCharacteristic { if let data = "Hello".data(using: .utf8, allowLossyConversion: true) { peripheral.writeValue(data, for: characteristic, type: .withoutResponse) peripheral.writeValue(data, for: characteristic, type: .withResponse) // -> Message sent successfully } } } func peripheral(_ peripheral: CBPeripheral, didWriteValueFor characteristic: CBCharacteristic, error: Error?) { if let error = error { print("Writing error: \(error.localizedDescription)") return } print("Message sent successfully") } } My app has no trouble connecting to the bluetooth-connected printer. Initially, I called discoverServices(peripheral:) to get all services And I get a service identifier (5833FF01-9B8B-5191-6142-22A4536EF123) for my printer. peripheral(_:didDiscoverCharacteristicsFor:error:) doesn't return a thing for .writeWithoutResponse but does return a characteristic for .write. Eventually, if I call writeToPrinter(), peripheral.writeValue(data, for: characteristic, type: .withoutResponse) returns WARNING: Characteristic <CBCharacteristic: 0x3019040c0, UUID = 5833FF02-9B8B-5191-6142-22A4536EF123, properties = 0x8, value = (null), notifying = NO> does not specify the "Write Without Response" property - ignoring response-less write If I call peripheral.writeValue(data, for: characteristic, type: .withResponse) , there is no error. But I get no output from the printer. What am I doing wrong? Thanks.
1
0
469
Jan ’25
Release Build Configuration as Release Fails Preview
I have a simple SwiftUI project with two basic build configurations (Debug, Release) as shown below. I now choose Build > Scheme > Edit Scheme under Product and select Release as the current build configuration as shown below. And the Preview canvas exhibit errors. If I click on the Diagnostics button, it says under PREVIEW UPDATE ERROR OptimizationLevelError: not building -Onone ”BuildSchemeCrazyDaughter.app” needs -Onone Swift optimization level to use previews (current setting is -O) What does that mean and why don't I get the preview for the Release build configuration? Thanks.
1
0
180
Mar ’25
Opening a New Document from File URL?
I have a sample document-based macOS app. I understand that you can open a new window or a new tab with some text. import SwiftUI struct ContentView: View { @Binding var document: TexDocument @Environment(\.newDocument) var newDocument var body: some View { VStack(spacing: 0) { topView } } private var topView: some View { Button { newDocument(TexDocument(text: "A whole new world!")) } label: { Text("Open new window") .frame(width: 200) } } } Suppose that I have a path to a text file whose security-scoped bookmark can be resolved with a click of a button. I wonder if you can open a new window or a new tab with the corresponding content?. I have done that in Cocoa. I hope I can do it in SwiftUI as well. Thanks.
1
0
94
May ’25
CoreData Data Sharing with AppGroup
I have the following lines of code to access data through CoreData. import Foundation import CoreData import CloudKit class CoreDataManager { static let instance = CoreDataManager() let container: NSPersistentCloudKitContainer let context: NSManagedObjectContext init() { container = NSPersistentCloudKitContainer(name: "ABC") container.loadPersistentStores(completionHandler: { (storeDescription, error) in if let error = error as NSError? { print(error.userInfo) } }) context = container.viewContext context.automaticallyMergesChangesFromParent = true context.mergePolicy = NSMergePolicy(merge: .mergeByPropertyObjectTrumpMergePolicyType) } func save() { do { try container.viewContext.save() print("Saved successfully") } catch { print("Error in saving data: \(error.localizedDescription)") } } } I have confirmed that I can share data between iPhone and iPad. Now, I need to use AppGroup as well. I have changed my code as follows. import Foundation import CoreData import CloudKit class CoreDataManager { static let shared = CoreDataManager() let container: NSPersistentContainer let context: NSManagedObjectContext init() { container = NSPersistentCloudKitContainer(name: "ABC") container.persistentStoreDescriptions = [NSPersistentStoreDescription(url: FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "some group name")!.appendingPathComponent("CoreDataMama.sqlite"))] container.loadPersistentStores(completionHandler: { (description, error) in if let error = error as NSError? { print("Unresolved error \(error), \(error.userInfo)") } }) context = container.viewContext context.automaticallyMergesChangesFromParent = true context.mergePolicy = NSMergePolicy(merge: .mergeByPropertyObjectTrumpMergePolicyType) } func save() { do { try container.viewContext.save() print("Saved successfully") } catch { print("Error in saving data: \(error.localizedDescription)") } } } Other files being unaltered, my sample apps aren't sharing data. What am I doing wrong? Just FYI, I'm using actual devices. Thank you for your reading this topic.
1
0
107
May ’25
iBeacon with CLMonitor Slow
The other day I was playing with iBeacon and found out that CLBeaconIdentityConstraint will be deprecated after iOS 18.5. So I've written code with BeaconIdentityCondition in reference to this Apple's sample project. import Foundation import CoreLocation let monitorName = "BeaconMonitor" @MainActor public class BeaconViewModel: ObservableObject { private let manager: CLLocationManager static let shared = BeaconViewModel() public var monitor: CLMonitor? @Published var UIRows: [String: [CLMonitor.Event]] = [:] init() { self.manager = CLLocationManager() self.manager.requestWhenInUseAuthorization() } func startMonitoringConditions() { Task { print("Set up monitor") monitor = await CLMonitor(monitorName) await monitor!.add(getBeaconIdentityCondition(), identifier: "TestBeacon") for identifier in await monitor!.identifiers { guard let lastEvent = await monitor!.record(for: identifier)?.lastEvent else { continue } UIRows[identifier] = [lastEvent] } for try await event in await monitor!.events { guard let lastEvent = await monitor!.record(for: event.identifier)?.lastEvent else { continue } if event.state == lastEvent.state { continue } UIRows[event.identifier] = [event] UIRows[event.identifier]?.append(lastEvent) } } } func updateRecords() async { UIRows = [:] for identifier in await monitor?.identifiers ?? [] { guard let lastEvent = await monitor!.record(for: identifier)?.lastEvent else { continue } UIRows[identifier] = [lastEvent] } } func getBeaconIdentityCondition() -> CLMonitor.BeaconIdentityCondition { CLMonitor.BeaconIdentityCondition(uuid: UUID(uuidString: "abc")!, major: 123, minor: 789) } } It works except that my sample app can take as long as 90 seconds to see event changes. You would get an instant update with an fashion (CLBeacon and CLBeaconIdentityConstraint). Is there anything that I can do to see changes faster? Thanks.
1
0
77
Jun ’25
Practical Use of Combine's Subject
I'm trying to understand how Combine works. The following is my sample code. import UIKit import Combine class ViewController: UIViewController { // MARK: - Variables var cancellable: AnyCancellable? // MARK: - IBAction @IBAction func buttonTapped(_ sender: UIButton) { currentValueSubject.send(20) } // MARK: - Life cycle var currentValueSubject = CurrentValueSubject<Int, Never>(1) override func viewDidLoad() { super.viewDidLoad() let cancellable = currentValueSubject .sink { value in print("New value: \(value)") } currentValueSubject.send(5) currentValueSubject.send(10) //currentValueSubject.send(completion: .finished) currentValueSubject.send(15) //cancellable.cancel() } } If I run it with the iPhone simulator, I get New value: 1 New value: 5 New value: 10 New value: 15 If I tap the button, the app won't get a new value. I suppose that's because the subscription is cancelled at the end of viewDidLoad? If so, why does it get cancelled? I don't quite see a practical side of Combine's Subject. When is it useful? Thanks.
2
0
1.2k
Aug ’21
Picker with Unexpected Results
I'm playing with Picker and have come up with some unexpected results. As shown below, I have three pickers in a form. struct PickColorView: View { @State var numberIndex: Int = 4 @State var textSizeIndex: Int = 0 var body: some View { NavigationView { Form { Picker(selection: $numberIndex, label: Text("Numbers")) { ForEach((0...20), id: \.self) { Text("\($0)") } }.onChange(of: numberIndex) { newIndex in print("Index: \(newIndex)") } Picker(selection: $textSizeIndex, label: Text("textSizeTitle")) { ForEach([14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60], id: \.self) { textSize in Text("\(textSize)") } }.onChange(of: textSizeIndex) { newIndex in print("Index: \(newIndex)") } } .navigationBarTitle("Settings") .navigationBarHidden(false) } .navigationViewStyle(StackNavigationViewStyle()) } } Well, if I run it, the top picker shows its initial selection (4) while the other doesn't. I wonder why? The following is a screenshot. (Please ignore the top picker appearing in the screenshot). If I go ahead and select one with the bottom picker, I end up with an actual value instead of the index. So if I select 18 (Please see the screenshot below.), I expect to get 2 with the onChange thing. But I get 18, instead. So how can I receive the index? Muchos thankos.
2
0
464
Jan ’22
Where and How to Create FileManager as a Singleton?
When I write code with UIKIt or Cocoa, I usually create and use FileManager.default in AppDelegate or a base view controller. In Cocoa, for example, I would write something like the following. import Cocoa @NSApplicationMain class AppDelegate: NSObject, NSApplicationDelegate { let defaultFileManager = FileManager.default func applicationDidFinishLaunching(_ aNotification: Notification) { } func applicationWillTerminate(_ aNotification: Notification) { NSApp.terminate(nil) } } import Cocoa class HomeViewController: NSViewController { let appDelegate = (NSApp.delegate as! AppDelegate) override func viewDidLoad() { super.viewDidLoad() if appDelegate.defaultFileManager.fileExists(atPath: some file path) { } } } So my question is where I can create FileManager.default so that I use it under different Views in SwiftUI? Muchos thankos.
2
0
615
Feb ’22
Getting an Updated Value Through @EnvironmentObject
I have a simple project as follows. import SwiftUI class GameSettings: ObservableObject { @Published var score: Int = 100 } struct ContentView: View { @StateObject var settings = GameSettings() var body: some View { GeometryReader { geo in ZStack { HStack(spacing: 0.0) { RightView().environmentObject(GameSettings()) .frame(width: geo.size.width / 2.0, height: geo.size.height) Spacer() } VStack { HStack { Spacer() Button { print("\(settings.score)") } label: { Text("Print") .font(.largeTitle) }.padding(.trailing, 40.0) } Spacer() } } } } } struct RightView: View { @EnvironmentObject var settings: GameSettings var body: some View { ZStack { Color.red Button { settings.score += 100 } label: { Text("Change") .font(.largeTitle) } }.environmentObject(settings) } } So the score is supposed to increase by 100 if I tap the button over the red area. And I want to print the latest value by tapping the Print button at the top-right corner. But it will remain at 100. What am I doing wrong? And can I achieve my goal without using an @ObservedObject variable? Thanks.
2
0
508
Mar ’22
Horizontally-Aligned TextField Wrap?
I have a @State variable with an array of strings with which to create instances of TextField. So far, I have the following lines of code. import SwiftUI struct ContentView: View { @State private var names: [String] = ["Jim Thorton", "Susan Murphy", "Tom O'Donnell", "Nancy Smith"] var body: some View { HStack { ForEach($names, id: \.self) { $name in TextField("", text: $name) .fixedSize() .padding(.horizontal, 20.0) .background(Color.orange.opacity(0.2)) } } } } I wonder if there is a simple way of aligning instances of TextField horizontally such that one that exceeds the screen width will go to the next line like the following picture? Thanks.
2
0
1.7k
Apr ’22
NSItemProvider & DropDelegate - Shrinking Preview Picture
I have downloaded a sample project at raywenderlich.com (https://www.raywenderlich.com/22408716-drag-and-drop-editable-lists-tutorial-for-swiftui). I am working on a project involving DropDelegate. And I have a question with this project to make my point. In reference to the picture shown below, if I grab, drag and move Count Sheep, its preview picture will shrink. How could I prevent the preview picture from shrinking its size? struct ContentView: View { @EnvironmentObject private var todoList: TodoList @State private var isShowingAddTodoView = false @State private var editMode: EditMode = .inactive @State private var focusId: Int? func addTodo() { isShowingAddTodoView = true } var body: some View { NavigationView { VStack { FocusTodoView(focusId: focusId) .padding() .onDrop( of: [TodoItem.typeIdentifier], delegate: TodoDropDelegate(focusId: $focusId)) ScrollView { ActiveTodoView() CompletedTodoView() .disabled(editMode.isEditing) .onDrop(of: [TodoItem.typeIdentifier], isTargeted: nil) { itemProviders in for itemProvider in itemProviders { itemProvider.loadObject(ofClass: TodoItem.self) { todoItem, _ in guard let todoItem = todoItem as? TodoItem else { return } DispatchQueue.main.async { todoList.updateTodo(withId: todoItem.id, isCompleted: true) } } } return true } } .applyPlainListAppearance() .navigationBarTitle("Drag Todo") .toolbar { ToolbarItemGroup(placement: .navigationBarTrailing) { EditButton() Button(action: addTodo) { Image(systemName: "plus") } .disabled(editMode.isEditing) } } .environment(\.editMode, $editMode) .sheet(isPresented: $isShowingAddTodoView) { AddTodoView() } } } .navigationViewStyle(StackNavigationViewStyle()) } } I wish I had a simpler sample. That's the only sample I have been able to find. Anyway, I've been asking Google all day about "SwiftUI DropDelegate preview" with no luck. Thanks.
2
0
726
Aug ’22