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.
Post
Replies
Boosts
Views
Activity
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
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.
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.
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)")
}
}
}
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.
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.
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.
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
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.
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.
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
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
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)
}
}
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.