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.
Selecting any option will automatically load the page
Post
Replies
Boosts
Views
Activity
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.
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
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.

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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.