Posts

Post not yet marked as solved
0 Replies
182 Views
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) } } }
Posted
by Tomato.
Last updated
.
Post not yet marked as solved
4 Replies
531 Views
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.
Posted
by Tomato.
Last updated
.
Post marked as solved
2 Replies
663 Views
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.
Posted
by Tomato.
Last updated
.
Post not yet marked as solved
1 Replies
309 Views
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...
Posted
by Tomato.
Last updated
.
Post marked as solved
1 Replies
564 Views
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.
Posted
by Tomato.
Last updated
.
Post marked as solved
1 Replies
343 Views
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.
Posted
by Tomato.
Last updated
.
Post not yet marked as solved
0 Replies
246 Views
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.
Posted
by Tomato.
Last updated
.
Post not yet marked as solved
1 Replies
478 Views
In Cocoa, you can find out whether or not you have a Retina screen with the backingScaleFactor property like the following. func getWinFactor() -> CGFloat? { if let view = self.view.window { let factor = view.backingScaleFactor return factor } else { return nil } } How could we detect whether or not the application is dealing with a Retina screen in SwiftUI? I thought the displayScale Environment property is the chosen one. But my 27-inch iMac with a Retina display will return the scale as 1.0. import SwiftUI struct ContentView: View { @Environment(\.displayScale) var displayScale var body: some View { VStack { ... } .onAppear { print("display scale: \(displayScale)") // Returning 1.0 } } } Do I miss something with this environment guy? Muchos thankos.
Posted
by Tomato.
Last updated
.
Post not yet marked as solved
1 Replies
378 Views
I'm trying to change the locale of an app with Picker as follows. import SwiftUI @main struct LocaleSwitchCrazyMamaApp: App { var body: some Scene { WindowGroup { let lanSetting = LanguageSetting() ContentView() .environmentObject(lanSetting) .environment(\.locale, lanSetting.locale) } } } class LanguageSetting: ObservableObject { @Published var locale = Locale(identifier: "en") } import SwiftUI struct ContentView: View { @State private var segmentSelection = 0 @EnvironmentObject var languageSetting: LanguageSetting var body: some View { VStack { Text(NSLocalizedString("Hello", comment: "")) .padding(.vertical, 20) Picker("Language", selection: $segmentSelection) { Text("English").tag(0) Text("Japanese").tag(1) Text("French").tag(2) } .frame(width: 200) .pickerStyle(.segmented) .onChange(of: segmentSelection) {newValue in if newValue == 0 { languageSetting.locale = Locale(identifier: "en") } else if newValue == 1 { languageSetting.locale = Locale(identifier: "ja") } else { languageSetting.locale = Locale(identifier: "fr") } } } .padding() } } In addition, I have three locale versions like the following "Hello" = "Hello"; // en.lproj "Hello" = "Bonjour"; //fr.lproj "Hello" = "こんにちは"; // ja.lproj As long as I run the app on a simulator, the language of the Hello text won't change when tap any of the segments. What am I doing wrong? Muchos thankos
Posted
by Tomato.
Last updated
.
Post marked as solved
1 Replies
352 Views
When I enumerate an array of objects with ForEach, I often wonder how I use the array. For example, I have the following lines of code. import SwiftUI struct ContentView: View { @State var checkItems: [CheckItem] = [ .init("Susan"), .init("Meagan"), .init("Daniel") ] var body: some View { List() { ForEach(0..<checkItems.count, id: \.self) { index in HStack { Image(systemName: !checkItems[index].selected ? "circle" : "checkmark.circle.fill") .resizable() .scaledToFit() .frame(height: 24) .foregroundColor(!checkItems[index].selected ? .gray : .blue) .onTapGesture { checkItems[index].selected.toggle() } Text(checkItems[index].name) } } } } } struct CheckItem: Identifiable, Hashable { var id = UUID() var selected: Bool var name: String init(_ name: String) { self.selected = false self.name = name } } The code works as shown in the following image. In the following lines of code, I'm enumerating the same array in a slightly different fashion. struct ContentView: View { @State var checkItems: [CheckItem] = [ .init("Susan"), .init("Meagan"), .init("Daniel") ] var body: some View { List() { ForEach(checkItems, id: \.id) { item in HStack { Image(systemName: !item.selected ? "circle" : "checkmark.circle.fill") .resizable() .scaledToFit() .frame(height: 24) .foregroundColor(!item.selected ? .gray : .blue) .onTapGesture { //item.selected.toggle() // Cannot use mutating member on immutable value: 'item' is a 'let' constant } Text(item.name) } } } } } And I get an error in the line inside the onTapGesture guy. I wonder why the first section of code works and why second section doesn't? Muchos thankos.
Posted
by Tomato.
Last updated
.
Post marked as solved
2 Replies
759 Views
I have a macOS application with SwiftUI. I am saving a dictionary containing two custom classes with NSSavePanel. That's not a problem. import SwiftUI struct ContentView: View { var body: some View { ... } private func savePanel() -> URL? { let savePanel = NSSavePanel() savePanel.allowedContentTypes = [.myCustomeFileType] savePanel.canCreateDirectories = true savePanel.isExtensionHidden = false savePanel.title = "Saving..." savePanel.message = "Please select a path where to save a file." savePanel.nameFieldStringValue = "Untitled" return savePanel.runModal() == .OK ? savePanel.url : nil } private func fileSaveAs() { if let url = savePanel() { let models = colorViewModel.frameModels let borderModel = BorderModel(showBorder: true, colorIndex: 6, borderWeightIndex: 8) let dict = ["FrameModelArray": models, "BorderModel": borderModel] as [String : Any] NSKeyedArchiver.setClassName("FrameModel", for: FrameModel.self) NSKeyedArchiver.setClassName("BorderModel", for: BorderModel.self) do { let data = try NSKeyedArchiver.archivedData(withRootObject: dict, requiringSecureCoding: false) try data.write(to: url, options: .atomic) } catch { print("Errrrrr \(error.localizedDescription)") } } } } So my custom classes are FrameModel, BorderModel. I can unarchive a saved file with a deprecated type method as follows. private func fileOpen() { if let url = openPanel() { do { NSKeyedUnarchiver.setClass(FrameModel.self, forClassName: "FrameModel") NSKeyedUnarchiver.setClass(BorderModel.self, forClassName: "BorderModel") let data = try Data(contentsOf: url) if let someData = try NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(data) { if let dict = someData as? [String : Any] { if let frameModels = dict["FrameModelArray"] as? [FrameModel] { print("[FrameModel] read...") } if let borderModel = dict["BorderModel"] as? BorderModel { print("BorderModel read...") } } } } catch { print("Errrrrr \(error.localizedDescription)") } } } If I use unarchivedObject(ofClasses:from:), I can't unarchive my file. What am I doing wrong? Thanks. private func fileOpen() { if let url = openPanel() { do { NSKeyedUnarchiver.setClass(FrameModel.self, forClassName: "FrameModel") NSKeyedUnarchiver.setClass(BorderModel.self, forClassName: "BorderModel") let data = try Data(contentsOf: url) if let dictionary = try? NSKeyedUnarchiver.unarchivedObject(ofClasses: [FrameModel.self, BorderModel.self], from: data) as? NSDictionary { print("Being read...") } else { print("Not read...") } } catch { print("Errrrrr \(error.localizedDescription)") } } }
Posted
by Tomato.
Last updated
.
Post not yet marked as solved
0 Replies
338 Views
I have a sample macOS app that I'm working on. I can run the exactly same lines of code below for iOS. For now, I'm running code for macOS since I can just press Command + z to undo the last action. Anyway, I have two Text View objects. Since TextView has the DragGesture gesture, I am able to freely move either of them. And I want to undo and redo their positions. So the following is what I have. import SwiftUI struct ContentView: View { @State var textViews: [TextView] = [TextView(text: "George"), TextView(text: "Susan")] var body: some View { VStack { ForEach(textViews, id: \.id) { textView in textView } } } } struct ContentView_Previews: PreviewProvider { static var previews: some View { ContentView() } } struct TextView: View { @Environment(\.undoManager) var undoManager @StateObject private var undoModel = UndoViewModel() @State private var dragOffset: CGSize = .zero @State private var position: CGSize = .zero let id = UUID() let text: String init(text: String) { self.text = text } var body: some View { ZStack { Text(text) .fixedSize() .padding(.vertical, 10) .offset(x: dragOffset.width + position.width, y: dragOffset.height + position.height) .gesture( DragGesture() .onChanged { self.dragOffset = $0.translation } .onEnded( { (value) in self.position.width += value.translation.width self.position.height += value.translation.height self.dragOffset = .zero undoModel.registerUndo(CGSize(width: position.width, height: position.height), in: undoManager) }) ) } } } class UndoViewModel: ObservableObject { @Published var point = CGSize.zero func registerUndo(_ newValue: CGSize, in undoManager: UndoManager?) { let oldValue = point undoManager?.registerUndo(withTarget: self) { [weak undoManager] target in target.point = oldValue // registers an undo operation to revert to old text target.registerUndo(oldValue, in: undoManager) // this makes redo possible } undoManager?.setActionName("Move") point = newValue // update the actual value } } Well, if I press Command + z after moving one of them, it won't return to the last position. What am I doing wrong? Muchos thankos.
Posted
by Tomato.
Last updated
.
Post not yet marked as solved
1 Replies
421 Views
Let me say that I have three structs that are sequentially connected. ContentView -> FirstView -> SecondView And I want to make a call from SecondView to ContentView with a button tap. So I have the following lines of code. import SwiftUI struct ContentView: View { @State var goToFirst = false var body: some View { NavigationStack { VStack { NavigationLink { FirstView(callBack: { sayHello() }, goToSecond: $goToFirst) } label: { Text("Go to First") } } } .navigationDestination(isPresented: $goToFirst) { } } func sayHello() { print("Hello!") } } struct FirstView: View { @State var callBack: (() -> Void)? @Binding var goToSecond: Bool var body: some View { VStack { Button("Go to Second") { goToSecond.toggle() } } .navigationDestination(isPresented: $goToSecond) { SecondView(callBack: callBack) } } } struct SecondView: View { @State var callBack: (() -> Void)? var body: some View { VStack { Button("Tap me to make a call to ContentView") { callBack?() } } } } If I tap the button in SecondView, my ContentView will receive a call and call the sayHello function. Since ContentView and SecondView are not directly connected with each other, they have to through FirstView in this case. I wonder if there's a better or easier approach in having SecondView make a call to ContentView? In UIKit and Cocoa, you can make a delegate call to a distant class even when two classes are not directly connected with other. Using the notification is another option. In SwiftUI, I suppose you don't use either of them. Muchos thankos.
Posted
by Tomato.
Last updated
.
Post marked as solved
2 Replies
666 Views
I have a ForEach loop with Range that I use with Picker. I'm using Range because I want to set startYear and endYear when View appears. The following is my code. import SwiftUI struct ProviderCalendarView: View { @State private var startYear: Int = 2023 @State private var endYear: Int = 2034 @State private var selectedYear = 3 var body: some View { VStack { HStack { Picker(selection: $selectedYear) { ForEach((startYear...endYear), id: \.self) { year in Text("\(year)") } } label: { } } } } } And the compiler says the following. Picker: the selection "3" is invalid and does not have an associated tag, this will give undefined results. It's not a critical error. But how can I stop it? Thanks.
Posted
by Tomato.
Last updated
.
Post marked as solved
1 Replies
534 Views
I have created a simple case to make my point as follows. import SwiftUI struct ContentView: View { var body: some View { ZStack { Color.yellow.ignoresSafeArea() VStack(alignment: .leading) { ForEach(Fruit.allCases, id: \.self) { fruit in DisclosureGroup(fruit.rawValue) { VStack { Text("1") Text("2") Text("3") } } .contextMenu { Button("Hello", action: { }) } } }.padding(.horizontal, 20) } } } enum Fruit: String, CaseIterable { case apple = "Apple" case grape = "Grape" case lemon = "Lemon" case orange = "Orange" case peach = "Peach" case pineapple = "Pineapple" case watermelon = "Watermelon" } struct ContentView_Previews: PreviewProvider { static var previews: some View { ContentView() } } What I want to do is show the contextual menu when the user long-presses a fruit name, which works. Yet, if I long-press a child inside the disclosure view, I also get the contextual menu, which is unintentional. Is there a simple way by which I can stop the contextual menu to appear if long-press a child inside the disclosure view? Muchos thankos
Posted
by Tomato.
Last updated
.