Post

Replies

Boosts

Views

Activity

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
450
Nov ’23
Switching Locale with Picker
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
3
0
756
Oct ’23
How to Detect the Retina Display for macOS in SwiftUI
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.
1
1
816
Sep ’23
List with ForEach Enumeration
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.
1
0
598
Sep ’23
'unarchiveTopLevelObjectWithData' was deprecated Use unarchivedObject(ofClass:from:) instead
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)") } } }
2
0
1.4k
Sep ’23
Undo/Redo with DragGesture
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.
0
0
503
Sep ’23
Making a Call to a Distant Struct
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.
1
0
582
Sep ’23
Picker with ForEach
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.
2
0
1k
Aug ’23
DisclosureGroup with Swipe Actions and Contextual Menu
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
1
0
851
Aug ’23
Exporting a Document with FileDocument, Not Packaged
I'm trying to export a document file. It contains a codable struct named NoteGroup. struct NoteGroup: Codable { let id: UUID let name: String let createAt: Date let children: [NoteChild] init(id: UUID = .init(), name: String = "", createAt: Date = .init(), children: [NoteChild]) { self.id = id self.name = name self.createAt = createAt self.children = children } } , which contains another object named NoteChild. I have a FileDocument struct as follows. import SwiftUI import UniformTypeIdentifiers struct Document: FileDocument { var document: NoteGroup static var readableContentTypes = [UTType.frogType] init(document: NoteGroup = NoteGroup(children: [NoteChild(id: UUID(), name: "", createAt: Date())])) { self.document = document } init(configuration: ReadConfiguration) throws { self.init() } func fileWrapper(configuration: WriteConfiguration) throws -> FileWrapper { do { let data = try getDocumentData() let jsonFileWrapper = FileWrapper(regularFileWithContents: data) let filename = "Note.frog" jsonFileWrapper.filename = filename let fileWrapper = FileWrapper(directoryWithFileWrappers: [filename: jsonFileWrapper]) return fileWrapper } catch { throw error } } private func getDocumentData() throws -> Data { let encoder = JSONEncoder() do { let data = try encoder.encode(document) return data } catch { throw error } } } extension UTType { public static let frogType = UTType(exportedAs: "com.example.frog") } And I export a file like the following. import SwiftUI import UniformTypeIdentifiers struct ContentView: View { @State private var showingExporter = false @State var doc = Document() var body: some View { VStack { Button("Tap to export") { showingExporter.toggle() } .fileExporter( isPresented: $showingExporter, document: doc, contentType: .frogType ) { result in switch result { case .success(let file): print(file) case .failure(let error): print(error) } } }.onAppear { doc = Document(document: NoteGroup(id: UUID(), name: "Kyle", createAt: Date(), children: [NoteChild(id: UUID(), name: "Nancy", createAt: Date())])) } } } Well, I have read this topic. And I've watched this video about Uniform Type Identifiers. Thanks to the video, I am able to export a file. Yet, I end up with a folder (Frog.frog), not a packaged file. There is a JSON file in it, though. What am I doing wrong? It's for iOS. La vida no es facil. Muchos thankos.
2
0
705
Aug ’23
Removing More?
I use the ForEach enumeration to list a View horizontally. And I get the following picture. So far, so good... If I select the 5th object or 6th one, something odd (< More) appears. I don't know where it comes from. I have never seen it before. How does it happen? I wonder how I can remove it? I have searched the net for a clue to no avail. I don't even know how to describe it. The following is my code. import SwiftUI struct ContentView: View { @State var selectedTab = 0 @State var addTapped = false @State var refresh = false @State var people = [ Person(name: "Alice", systemImage: "person.circle.fill"), Person(name: "Jane", systemImage: "person.circle.fill"), Person(name: "Dave", systemImage: "person.circle.fill"), Person(name: "Susan", systemImage: "person.circle.fill"), Person(name: "Robert", systemImage: "person.circle.fill"), Person(name: "Daniel", systemImage: "person.circle.fill") ] var body: some View { VStack(alignment: .leading, spacing: 0) { ScrollView(.horizontal) { HStack(spacing: 20) { ForEach(0..<people.count, id: \.self) { num in VStack { let person = people[num] Image(systemName: person.systemImage) .resizable() .aspectRatio(contentMode: .fit) .frame(height: 32) Text(person.name) .fixedSize() } .foregroundColor(selectedTab == num ? Color.blue : Color.gray) .onTapGesture { self.selectedTab = num } } } }.padding(.horizontal, 10) Spacer() .frame(height: 2) Rectangle().fill(.gray) .frame(height: 1) TabView(selection: $selectedTab) { ForEach(0..<people.count, id: \.self) { num in let person = people[num] Text(person.name) .tag(person.id) } } } } } struct Person: Identifiable { let id = UUID() let name: String let systemImage: String } Muchos thankos.
2
0
537
Aug ’23
Sorting CoreData Records by Creation Date
I have followed a tutorial written by Hacking with Swift ( https://www.hackingwithswift.com/books/ios-swiftui/how-to-combine-core-data-and-swiftui) about Core Data in SwiftUI. The Entity name is Student. And it has two properties: name (String), id (UUID). And the following is my code. import SwiftUI struct CoreView: View { @Environment(\.managedObjectContext) var managedObject @FetchRequest(sortDescriptors: []) var students: FetchedResults<Student> var body: some View { VStack { List(students) { student in Text(student.name ?? "Unknown") } Button { let firstNames = ["Gary", "Harry", "Elane", "Ray", "Nancy", "Jim", "Susan"] let lastNames = ["Johns", "McNamara", "Potter", "Thompson", "Hampton"] if let selectedFirstName = firstNames.randomElement(), let selectedLastName = lastNames.randomElement() { let newStudent = Student(context: managedObject) newStudent.id = UUID() newStudent.name = "\(selectedFirstName) \(selectedLastName)" try? managedObject.save() } } label: { Text("Add") } } } } struct CoreView_Previews: PreviewProvider { static var previews: some View { CoreView() .environmentObject(DataController()) } } If I list all records and then add a new student to the list, the app will insert the last addition at a random row. I wonder if I can order these records by the creation date? Muchos thankos
2
0
988
Jul ’23
URLSession with URLRequest Timeout
I have the following lines of code to list some music titles from iTunes music. The code is 100% reproducible. import SwiftUI struct MusicView: View { @StateObject var viewModel = ViewModel() var body: some View { MusicListView(viewModel: viewModel) } } struct MusicListView: View { @ObservedObject var viewModel: ViewModel var body: some View { NavigationView { List(viewModel.results, id: \.self) { result in VStack(alignment: .leading) { Text("Track ID: \(result.trackId)") Text("Track name: \(result.trackName)") } } .task { do { try await viewModel.fetchMusic() } catch SessionError.badURL { print("Bad URL") } catch SessionError.invalidHTTPResponse { print("Invalid HTTP response") } catch SessionError.error(let err) { print("Error: \(err)") } catch { print("\(error.localizedDescription)") } } .navigationTitle("Music") } } } class ViewModel: ObservableObject { @Published var results: [Result] = [] func fetchMusic() async throws { guard let url = URL(string: "https://itunes.apple.com/search?term=classical+music&entity=song") else { throw SessionError.badURL } let urlRequest = URLRequest(url: url, timeoutInterval: 0.00) // <<<<<<<<<<<<< URLSession.shared.dataTask(with: urlRequest) { data, response, error in do { guard let data = data, error == nil else { throw SessionError.noData } guard let httpResponse = response as? HTTPURLResponse else { throw SessionError.invalidHTTPResponse } switch httpResponse.statusCode { case 200: let res = try JSONDecoder().decode(Response.self, from: data) DispatchQueue.main.async { self.results = res.results } case 400...499: throw SessionError.badURL default: fatalError() break } } catch { print(error.localizedDescription) } } .resume() } } struct Response: Codable { let resultCount: Int let results: [Result] } struct Result: Codable, Hashable { var trackId: Int var trackName: String var collectionName: String } enum SessionError: Error { case badURL case noData case decoding case invalidHTTPResponse case badRequest(statusCode: Int) case redirection(statusCode: Int) case server(statusCode: Int) case error(String) } As you see in the screenshot, I get some music titles listed. My question is why I get a list when in fact I have the URLRequest's timeout value set to 0.00? I haven't run it with an actual device. As far as I use an iPhone simulator, regardless of the timeout value that I set, I get data downloaded. I wonder why? Muchos thankos for reading
0
0
1.3k
Jul ’23
Version 15.0 beta 4 Horribly Slow when Debugged with Simulator
Oh, boy... Xcode has become more and more difficult to deal with. Today, I've dowloaded Version 15.0 beta 4. It took my 2019 iMac with 64 GB of RAM some 20 minutes just to launch an iPhone 14 Simulator and to let me see the home screen. Xcode takes 3 or 4 minutes to run code after I change just one line. I only have some 30 lines of code in total. It's a truly disappointing update. I wish they stop adding unnecessary features like code-folding animation to slow things down. import UIKit class ViewController: UIViewController { private let photoView: UIImageView = { let imageView = UIImageView() imageView.image = UIImage(systemName: "airplane") //imageView.clipsToBounds = true imageView.contentMode = .scaleAspectFit imageView.translatesAutoresizingMaskIntoConstraints = false return imageView }() override func viewDidLoad() { super.viewDidLoad() view.backgroundColor = .systemYellow view.addSubview(photoView) NSLayoutConstraint.activate([ photoView.centerXAnchor.constraint(equalTo: view.centerXAnchor), photoView.centerYAnchor.constraint(equalTo: view.centerYAnchor), photoView.widthAnchor.constraint(equalToConstant: 200), photoView.heightAnchor.constraint(equalToConstant: 200) ]) DispatchQueue.main.asyncAfter(deadline: .now() + 3) { self.runAirplaneAnimation() } } func runAirplaneAnimation() { photoView.addSymbolEffect(.pulse, animated: true) } }
0
0
409
Jul ’23
List Single Selection
I have some lines of code below where I can make multiple selections. import SwiftUI struct ContentView: View { @State private var selectedUsers: Set<String> = [] @State var users = ["Susan", "Kate", "Natalie", "Kimberly", "Taylor", "Sarah", "Nancy", "Katherine", "Nicole", "Linda", "Jane", "Mary", "Olivia", "Barbara"] var body: some View { VStack { List(selection: $selectedUsers) { ForEach(users, id: \.self) { user in Text(user) } } .environment(\.editMode, .constant(.active)) } } } So the blue selection symbol appears as shown in the screenshot above. That's good. But that's not what I'm after. I just want to select one row at a time. import SwiftUI struct ContentView: View { @State var selectedUser: String? @State var users = ["Susan", "Kate", "Natalie", "Kimberly", "Taylor", "Sarah", "Nancy", "Katherine", "Nicole", "Linda", "Jane", "Mary", "Olivia", "Barbara"] var body: some View { VStack { List(selection: $selectedUser) { ForEach(users, id: \.self) { user in Text(user) } } .environment(\.editMode, .constant(.active)) } } } In the lines of code above, I only let myself select one row at a time. And I don't get the blue selection symbol. I wonder why? I find two or three websites where they have similar lines of code and where they select one row at a time. And they have the blue selection symbol. Why don't I get it? Mucho thankos for reading.
4
0
941
Jul ’23