Post

Replies

Boosts

Views

Activity

Can SwiftUI View Receive a Call When Window Will Close?
I have an NSStatusBar application. This is my first in SwiftUI. And I need to know when the window is closed so that I can disable some of menu commands. I can use NSWindowDelegate with AppDelegate as follows. import SwiftUI @main struct SomeApp: App { @NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate @StateObject private var menuViewModel = MenuViewModel() var body: some Scene { WindowGroup { ContentView() .environmentObject(menuViewModel) } } } class AppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate { private var menuViewModel = MenuViewModel() func applicationDidFinishLaunching(_ notification: Notification) { if let window = NSApplication.shared.windows.first { window.setIsVisible(false) window.delegate = self } } func windowWillClose(_ notification: Notification) { menuViewModel.windowClosed = true } } When the window will close, MenuViewModel (ObservableObject) will receive a call, which I want my ContentView to receive. But, so far, it won't. import SwiftUI struct ContentView: View { var body: some View { ZStack { ... ... } .onReceive(statusBarViewModel.$windowClosed) { result in // never called... } } } Can a SwiftUI View receive a call somehow when its window closes? Muchos thankos.
1
0
203
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")
1
0
322
Aug ’24
The Mystery of an Array of Strings and Escaped Characters
I have a very simple set of lines of code. It doesn't matter whether you run it under UIKit or SwiftUI. In SwiftUI, I have the following. import SwiftUI struct ContentView: View { var body: some View { VStack { Button("Click on me") { let tabLine = "1\tAnthony James\t139.9" var item = "" let tabs = tabLine.components(separatedBy: "\t") for tab in tabs { item += "'\(tab)'" } print("\(item)") } } } } So I have tab-separated values. And I want to separate them and quote each value either with an apostrophe or a double quotation mark. In the case above, I get the following print. '1''Anthony James''139.9' That's exactly what I want. Now, I have an array of three of those guys like the following. import SwiftUI struct ContentView: View { var body: some View { VStack { Button("Click on me") { let tabLine0 = "1\tAnthony James\t139.9" let tabLine1 = "2\tKim Harbaugh\t181.4" let tabLine2 = "3\tAnthony James\t212.4" let tabTextLines = [tabLine0, tabLine1, tabLine2] var strings = [String]() for tabLine in tabTextLines { var item = "" let tabs = tabLine.components(separatedBy: "\t") for tab in tabs { item += "'\(tab)'" } strings.append(item) } print("\(strings)") } } .frame(width: 360, height: 240) } } And I get the following print. This is a nightmare situation. Each value is quoted with an escaped apostrophe. I can't even remove the escapees with replacingOccurrences(of:with:). How does that happen when you have an array of strings? If I try quoting the values with a unicode character, things are the same. Is there a workaround? Muchos thankos.
4
0
285
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.
1
0
212
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.
1
0
273
Aug ’24
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
261
Aug ’24
Text .onTapGesture Never Called When Shown with .onLongPressGesture
I'm showing a Text View when a button with an image is long-pressed. import SwiftUI struct ContentView: View { @Environment(\.colorScheme) var colorScheme var isDark: Bool { return colorScheme == .dark } @State private var showLabel = false var body: some View { Button(action: { }) { VStack { ZStack { Image(systemName: "swift") .resizable() .aspectRatio(contentMode: .fit) .frame(width: 32) .padding(.horizontal, 40) .padding(.vertical, 6) .background(.gray.opacity(0.2), in: RoundedRectangle(cornerRadius: 10)) .onTapGesture { showLabel.toggle() } .onLongPressGesture(minimumDuration: 2) { print("Long pressed...") showLabel.toggle() } if showLabel { Text("Help Content") .font(.caption) .foregroundStyle(!isDark ? .white : .black) .padding(10) .background(!isDark ? .black : .white, in: Rectangle()) .onTapGesture { print("hey") showLabel.toggle() } .offset(x: 120) } } } } } } So a Text View will appear as shown in the image above. But its .onTapGesture is never called. I wonder why? Thanks.
1
0
364
Jul ’24
Unarchiving an object with custom classes
I have a custom class named CodeReadModel, which contains another custom class named CodeDataModel. The former contains the latter as an array like the following. class CodeReadModel: NSObject, NSSecureCoding { class var supportsSecureCoding: Bool { true } let identifier: String let codeDataModels: [CodeDataModel] init(identifier: String, codeDataModels: [CodeDataModel]) { self.identifier = identifier self.codeDataModels = codeDataModels } required init(coder decoder: NSCoder) { self.identifier = decoder.decodeObject(forKey: "identifier") as! String self.codeDataModels = decoder.decodeObject(forKey: "codeDataModels") as! [CodeDataModel] } func encode(with coder: NSCoder) { coder.encode(identifier, forKey: "identifier") coder.encode(codeDataModels, forKey: "codeDataModels") } } And I want to unarchive an object with the following. func importCodeReaderSnippetNext(fileURL: URL) { do { NSKeyedUnarchiver.setClass(CodeReadModel.self, forClassName: "CodeReadModel") NSKeyedUnarchiver.setClass(CodeDataModel.self, forClassName: "CodeDataModel") let data = try! Data(contentsOf: fileURL) if let codeReadModel = try NSKeyedUnarchiver.unarchivedObject(ofClass: CodeReadModel.self, from: data) { } } catch { print("Error: \(error.localizedDescription)") } } And I will get an error because codeReadModel contains another custom class, which cannot be decoded. How can I resolve this problem? Muchas thankos.
7
0
479
Jul ’24
CoreBluetooth Central, Observing Multiple Peripherals
A device running with the following lines of code can receive a message from a peripheral. In this manner, though, I can only receive messages from one peripheral since the service and characteristic IDs are hardcoded in CentralViewModel.swift. So my question is how I can observe messages from multiple peripherals. Thanks. import SwiftUI struct ContentView: View { var body: some View { NavigationStack { VStack { NavigationLink(destination: CentralView()) { Text("Central") } .buttonStyle(.borderedProminent) .padding() } } } } // CentralView.swift // import SwiftUI struct CentralView: View { @StateObject var central: CentralViewModel = CentralViewModel() var body: some View { Text(central.message) .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading) .padding(20) .onDisappear { central.stopAction() } } } // CentralViewModel.swift // import Foundation import CoreBluetooth class CentralViewModel: NSObject, ObservableObject { @Published var message: String = "" var serviceUUID: CBUUID! var characteristicUUID: CBUUID! var centralManager: CBCentralManager! var discoveredPeripheral: CBPeripheral? var transferCharacteristic: CBCharacteristic? var writeIterationsComplete = 0 //var connectionIterationsComplete = 0 let defaultIterations = 5 var data: Data = Data() override init() { super.init() self.serviceUUID = CBUUID(string: "994F8A12-FE8E-4CCB-BD7B-1AE989A32853") self.characteristicUUID = CBUUID(string: "F4BD0CA2-7581-40E2-A517-1CE275A3A749") centralManager = CBCentralManager(delegate: self, queue: nil, options: [CBCentralManagerOptionShowPowerAlertKey: true]) } func stopAction() { centralManager.stopScan() } private func cleanup() { guard let discoveredPeripheral = discoveredPeripheral, case .connected = discoveredPeripheral.state else { return } for service in (discoveredPeripheral.services ?? [] as [CBService]) { for characteristic in (service.characteristics ?? [] as [CBCharacteristic]) { if characteristic.uuid == characteristicUUID && characteristic.isNotifying { self.discoveredPeripheral?.setNotifyValue(false, for: characteristic) } } } centralManager.cancelPeripheralConnection(discoveredPeripheral) } private func writeData() { guard let discoveredPeripheral = discoveredPeripheral, let transferCharacteristic = transferCharacteristic else { return } while writeIterationsComplete < defaultIterations && discoveredPeripheral.canSendWriteWithoutResponse { writeIterationsComplete += 1 } if writeIterationsComplete == defaultIterations { discoveredPeripheral.setNotifyValue(false, for: transferCharacteristic) } } } extension CentralViewModel: CBCentralManagerDelegate { func centralManagerDidUpdateState(_ central: CBCentralManager) { switch central.state { case .poweredOn: print("Power on") startScanningForPeripherals() return case .poweredOff : print("Power off") return case .resetting: print("Resetting") return case .unauthorized: print("Unauthorized") return case .unknown: print("Unknown") return case .unsupported: print("Unsupported") return @unknown default: print("An unknown central manager state has occurred") return } } func startScanningForPeripherals() { self.centralManager.scanForPeripherals(withServices: [self.serviceUUID], options: nil) } func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) { guard RSSI.intValue >= -50 else { return } if discoveredPeripheral != peripheral { print("Peripheral discovered") discoveredPeripheral = peripheral centralManager.connect(peripheral, options: nil) } } func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) { peripheral.delegate = self peripheral.discoverServices([serviceUUID]) print("Service discovered") } } extension CentralViewModel: CBPeripheralDelegate { func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) { if error != nil { cleanup() return } guard let peripheralServices = peripheral.services else { return } for service in peripheralServices { peripheral.discoverCharacteristics([characteristicUUID], for: service) } } func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) { if let error = error { print("Error discovering characteristics: \(error.localizedDescription)") cleanup() return } guard let serviceCharacteristics = service.characteristics else { return } for characteristic in serviceCharacteristics where characteristic.uuid == characteristicUUID { transferCharacteristic = characteristic peripheral.setNotifyValue(true, for: characteristic) } } func peripheral(_ peripheral: CBPeripheral, didUpdateNotificationStateFor characteristic: CBCharacteristic, error: Error?) { if let error = error { print("Error changing notification state: \(error.localizedDescription)") return } guard characteristic.uuid == characteristicUUID else { return } if characteristic.isNotifying { print("Notification began on \(characteristic)") } else { print("Notification stopped on \(characteristic). Disconnecting") cleanup() } } func peripheralIsReady(toSendWriteWithoutResponse peripheral: CBPeripheral) { print("Peripheral is ready to send data to YOU!") } func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) { if let error = error { print("Error discovering characteristics: \(error.localizedDescription)") cleanup() return } guard let characteristicData = characteristic.value, let stringFromData = String(data: characteristicData, encoding: .utf8) else { return } print("Received \(characteristicData.count) bytes: \(stringFromData)") if stringFromData == "EOM" { message = String(data: data, encoding: .utf8) ?? "" writeData() } else { data.append(characteristicData) } } }
1
0
491
Mar ’24
Heavy Duty Work With Async Await
I am perplexed as to how to use async await. In the following example, I don't use GCD or performSelector(inBackground:with:). The view controller is NSViewController, but it doesn't make any difference if it's NSViewController or UIViewController. import Cocoa class ViewController: NSViewController { func startWriteImages() { Task{ let bool = await startWriteImagesNext() if bool { print("I'm done!") } } } func startWriteImagesNext() async -&gt; Bool { // pictures is a path to a folder in the sandbox folder // appDelegate.defaultFileManager is a variable pointing to FileManager.default in AppDelegate let pictURL = URL(fileURLWithPath: pictures) if let filePaths = try? self.appDelegate.defaultFileManager.contentsOfDirectory(atPath: pictURL.path) { for file in filePaths { let fileURL = pictURL.appending(component: file) if self.appDelegate.defaultFileManager.fileExists(atPath: fileURL.path) { let newURL = self.folderURL.appending(component: file) do { try self.appDelegate.defaultFileManager.copyItem(at: fileURL, to: newURL) } catch { print("Ugghhh...") } } } return true } return false } func startWriteImagesNext2() async -&gt; Bool { let pictURL = URL(fileURLWithPath: pictures) if let filePaths = try? self.appDelegate.defaultFileManager.contentsOfDirectory(atPath: pictURL.path) { DispatchQueue.global().async() { for file in filePaths { let fileURL = pictURL.appending(component: file) if self.appDelegate.defaultFileManager.fileExists(atPath: fileURL.path) { let newURL = self.folderURL.appending(component: file) do { try self.appDelegate.defaultFileManager.copyItem(at: fileURL, to: newURL) } catch { print("Ugghhh...") } } } } return true } return false } } In the code above, I'm saving each file in the folder to user-selected folder (self.folderURL). And the application will execute the print guy only when work is done. Since it's heavy-duty work, I want to use CCD or performSelector(inBackground:with:). If I use the former (startWriteImagesNext2), the application will execute the print guy right at the beginning. I suppose I cannot use GCD with async. So how can I perform heavy-duty work? Muchos thankos.
4
0
977
Feb ’24
TextField with Custom Binding
I'm just playing with Combine, and I want to limit the number of letters that the text field can show. So I have the following lines of code. import SwiftUI struct ContentView: View { @State var messageText: String = "" var body: some View { let bindingMessage = Binding { messageText } set: { messageText = String($0.prefix(10)) print(String($0.prefix(10))) } Form { TextField("Message", text: bindingMessage) } } } I'm expecting that the text field will show only the first 10 letters. But, as the screenshot below indicates, it shows the entire string. What am I doing wrong? Muchos Thankos. It sounds basic. Ugghhh...
2
0
656
Jan ’24
How Come My WeatherKit Sample App Doesn't Work?
I have gone through several tutorials for WeatherKit. But my sample app doesn't return weather data. The following is a list of what I have. I've registered a Bundle ID for my sample app with the WeatherKit capability on. I've created a developer profile for my sample app. I've opened my Xcode project to make sure that the WeatherKit capability is enabled. I have run my sample app with an actual device. I have waited for more than 30 minutes for the service to kick in. It's been several days. The following is my code. import SwiftUI import CoreLocation import WeatherKit struct ContentView: View { @State var currentWeather: CurrentWeather? var body: some View { NavigationStack { List { Group { SampleCell(title: "Temperature", value: String(currentWeather?.apparentTemperature.value ?? 0.0) + "℃") SampleCell(title: "Cloud coverage", value: String(currentWeather?.cloudCover ?? 0.0)) SampleCell(title: "Weather condition", value: String(currentWeather?.condition.description ?? "")) SampleCell(title: "Dew point", value: String(currentWeather?.dewPoint.value ?? 0.0) + "℃") SampleCell(title: "Humidity", value: String(currentWeather?.humidity ?? 0.0)) SampleCell(title: "Pressure", value: String(currentWeather?.pressure.value ?? 0.0) + "mbar") SampleCell(title: "Pressure trend", value: String(currentWeather?.pressureTrend.description ?? "")) SampleCell(title: "Temperature", value: String(currentWeather?.temperature.value ?? 0.0) + "℃") SampleCell(title: "UV index", value: String(currentWeather?.uvIndex.value ?? 0)) SampleCell(title: "Visibility", value: String(currentWeather?.visibility.value ?? 0.0) + "m") } SampleCell(title: "Window direction", value: String(currentWeather?.wind.direction.value ?? 0.0) + "°") SampleCell(title: "Window speed", value: String(currentWeather?.wind.speed.value ?? 0.0) + "km/h") SampleCell(title: "Gust", value: String(currentWeather?.wind.gust?.value ?? 0.0) + "km/h") } .navigationTitle(Text("CurrentWeather")) .task { let service = WeatherService() let location = CLLocation( latitude: 35.467081, longitude: 139.620798 ) do { let weather = try await service.weather(for: location) currentWeather = weather.currentWeather } catch let error { print(error.localizedDescription) } } } } } struct SampleCell: View { var title: String var value: String var body: some View { VStack { HStack { Text(title) Spacer() Text(value) } } } } Yet, I constantly get the following warnings. 2023-11-29 09:33:46.504737+0900 WeatherCrazyMama[15279:9734572] [WeatherDataService] Aborting silent interpolation: no interpolator object; location=CLLocationCoordinate2D(latitude: 35.467081, longitude: 139.620798) 2023-11-29 09:33:47.900605+0900 WeatherCrazyMama[15279:9734577] [AuthService] Failed to generate jwt token for: com.apple.weatherkit.authservice with error: Error Domain=WeatherDaemon.WDSJWTAuthenticatorServiceListener.Errors Code=2 "(null)" 2023-11-29 09:33:47.989603+0900 WeatherCrazyMama[15279:9734572] [WeatherService] Encountered an error when fetching weather data subset; location=<+35.46708100,+139.62079800> +/- 0.00m (speed -1.00 mps / course -1.00) @ 2023/11/29 9:33:46 AM Japan Standard Time, error=WeatherDaemon.WDSJWTAuthenticatorServiceListener.Errors 2 Error Domain=WeatherDaemon.WDSJWTAuthenticatorServiceListener.Errors Code=2 "(null)" The operation couldn’t be completed. (WeatherDaemon.WDSJWTAuthenticatorServiceListener.Errors error 2.) What am I doing wrong? Thanks.
2
1
1.2k
Nov ’23
SwiftUI #Preview with Callback Closure
I have created a simple calendar framework of my own. The screenshot below shows what it looks like. The following lines show a concise version of my calendar framework. The deal is such that the app will return a date when I tap a date button with the callBack closure. import SwiftUI struct ContentView: View { @State private var navigateToAddDate = false @State private var days: [Day] = [] @State var callBack: ((Date) -> Void) private let cols = [ GridItem(.flexible()), GridItem(.flexible()), GridItem(.flexible()), GridItem(.flexible()), GridItem(.flexible()), GridItem(.flexible()), GridItem(.flexible()) ] var body: some View { NavigationStack { VStack { LazyVGrid(columns: cols) { ForEach(days, id: \.self) { day in Button(action: { selectedDay = day navigateToAddDate.toggle() }, label: { Image(systemName: "\(day.num).circle.fill") .resizable() .aspectRatio(contentMode: .fit) .foregroundColor(day.show ? dateTextForecolor(day: day) : .clear) }) .disabled(day.isInvalid) } } } } } } struct ContentView_Previews: PreviewProvider { static var callBack: (Date) -> Void = { _ in } static var previews: some View { ContentView(callBack: callBack) } } struct Day: Hashable { let date: Date let text: String let num: Int let dayOfWeek: Int let show: Bool let isInvalid: Bool } Well, PreviewProvider works. Now, I want to use #Preview that comes with iPhone 15. #Preview { var callBack: (Date) -> Void = { _ in } ContentView(callBack: callBack) } And I get a warning and an error. The warning is the following Result of 'ContentView' initializer is unused , which seems to stem from the said warning. How can I make the Preview guy work? Thanks.
3
0
1.1k
Nov ’23
Callback from onTapGesture Through Framework
I've found a simple example at YouTube (https://www.youtube.com/watch?v=ddp1jwkDwr8) as to create a framework. The following example does NOT use a framework. import SwiftUI struct ContentView: View { @State private var selectedColor: Color = .clear var body: some View { VStack { ColorSelectorView(selectedColor: $selectedColor) { color in print("**** \(color)") } } } } import SwiftUI struct ColorSelectorView: View { @Binding var selectedColor: Color @State var callBack: ((Color) -> Void)? let colors: [Color] = [.blue, .green, .orange, .yellow, .red, .purple] var body: some View { HStack { ForEach(colors, id: \.self) { color in Image(systemName: selectedColor == color ? "record.circle.fill" : "circle.fill") .foregroundColor(color) .onTapGesture { selectedColor = color callBack?(color) } } } } } #Preview { ColorSelectorView(selectedColor: .constant(.red)) } If I select a color, ContentView will receive a call back as to which color has been selected. So far, so good... Now, I want to make the ColorSelectorView part a framework. ContentView doesn't change. The following is the framework part. import SwiftUI public struct ColorSelectorView: View { @Binding var selectedColor: Color @State var callBack: ((Color) -> Void)? let colors: [Color] = [.blue, .green, .orange, .yellow, .red, .purple] public init(selectedColor: Binding<Color>, callBack: ((Color) -> Void)? = nil) { self._selectedColor = selectedColor self.callBack = callBack } public var body: some View { HStack { ForEach(colors, id: \.self) { color in Image(systemName: selectedColor == color ? "record.circle.fill" : "circle.fill") .foregroundColor(color) .onTapGesture { selectedColor = color callBack?(color) } } } } } struct ColorSelectorView_Previews: PreviewProvider { static var previews: some View { ColorSelectorView(selectedColor: .constant(.red)) } } Running ContentView with a framework, it doesn't receive a call back. What am I doing wrong? Muchos thankos.
1
0
588
Nov ’23
URLSession ViewModel with Loading State
I have a test app that is supposed to list a bunch of iTunes music records. I have existing lines of code that successfully load data. Now, I'm adding a loading state to them. As a result, I have the following lines code. import SwiftUI struct iTunesView: View { @StateObject var viewModel = iTunesViewModel() var body: some View { switch viewModel.state { case .idle: EmptyView() case .loading: ProgressView() case .loaded(let results): List { ForEach(results, id: \.self) { result in Text("\(result.trackId)") Text(result.trackName) .lineLimit(0) } } case .failed(let error): Text(error.localizedDescription) } } } @MainActor class iTunesViewModel: ObservableObject { enum LoadingState { case idle case loading case loaded([iTunesResult]) case failed(Error) } @Published var state: LoadingState = .idle init() { state = .loading Task { await fetchMusic() } } func fetchMusic() async { guard let url = URL(string: "https://itunes.apple.com/search?term=classical+music&entity=song") else { state = .failed(URLError(.badURL)) return } do { let urlRequest = URLRequest(url: url, timeoutInterval: 1.0) let (data, _) = try await URLSession.shared.data(for: urlRequest) let music = try JSONDecoder().decode(iTunesResponse.self, from: data) self.state = .loaded(music.results) print("\(music.results)") } catch { state = .failed(error) } } } struct iTunesResponse: Codable { let resultCount: Int let results: [iTunesResult] } struct iTunesResult: Codable, Hashable { var trackId: Int var trackName: String var collectionName: String } enum iTunesError: Error { case badURL case decoding case invalidHTTPResponse case badData(statusCode: Int) case badRequest(statusCode: Int) case redirection(statusCode: Int) case server(statusCode: Int) case error(String) } For some reason, the app just shows a spinner although it reaches the print line in the fetchMusci function and print the string data. I wonder what I'm doing wrong? With the code lines above, an app is fully functional except it will show the progress guy, you know? I've made changes after reading this Stack overflow topic. Thanks.
1
0
438
Nov ’23