Swift Notes
| 45 minutes
Fresh Start!| Bottom| JSON Explained
🕶️Paul Hudson Hacking With Swift|Antoine Van Der Lee|Code with Chris| XCode Keyboard Shortcuts | Swift with Majid | swiftyplace.com
- ⌘ = Command
- ⌥ = Option/Alt
- ⇧ = Shift
- ⌃ = Control
- ←→ ↑↓ = Arrow keys
- ↩ = Enter
Ok, so per 🕶️Paul Hudson, Hacking With Swift, I’m going to document my path of learning to program in swift. I figured, after watching Apple blast through the universe for the past 20 years, maybe, now is the time to commit and learn how to write iPhone/ipad/iwatch apps.
I started in late spring of 22 reading the manual at swift.org I certainly didn’t understand it all but I got through the manual. Once that was done, I knew I needed to find an on-line course to really get the juices flowing and stumbled into code with chris. He offered a free 3 hour course on-line that provided a lot of “a ha” moments. He quickly was providing answers to questions I had from reading the manual. I was hooked so I bought his year long subscription for $200 and started my first official course, “swift in 90 days”. (He has many more courses and I plan to take all of them.) It was fantastic!
There were challenges after most lessons to re-enforce the information reviewed and it was the key to understanding the material. That being said, as I would complete challenges, I would look all over the internet for help and Paul Hudson, hackingwithswift was a recurring theme. There are several other sites I use as reference and will list those further down. I could play and replay the lessons until I understood them. This new way of learning is much more forgiving. I’m not rushing to scribble notes as the professor talks. Just play, pause, rewind, play again. It’s great.
I liked codewithchris because you could watch him write and debug the programs “live”. He would step through the program, building and testing as he went. The process was really great, even when he made mistakes. By the end of the 90 day course, he was flying through the coding and it was fun being able to keep up. We learned how to hook into other apps like yelp and apple maps. (as an example) It was extremely powerful. He also covered GitHub.com, a fantastic source control tool that is on-line. Everybody uses it and it’s a phenomenal source for other programmers open source swift projects. The resources available to learn swift are wide and deep.
Hackingwithswift is a different approach. He starts with the basics of every type of property, function, method, structure, class, protocol, inheritance, etc. He teaches and re-enforces the “why’s” of swift. Both resources have been great and they complement one another.
For now, I’m switching gears and pursuing hackingwithswift: 🕶️swiftUI in 100 days I’m on day 19. So far it has been mainly review but it has been deeper and more thorough. Paul also has an iPhone app called unwrapped that is free. He gives quick tutorials and then quizzes you. It’s been really good as well. He lists sources here
Ok, so that’s all I have for now. p.s. Ben is going to learn swift as well. He’s started!
Day 28: 13NOV22 Still trucking along, I really enjoy the pace and material! The challenges are small enough and concise enough to cover the new material yet don’t require a lot of setup to progress. 🕶️Day 28
Day 38: 26NOV22 The challenges are definitely getting more challenging and solutions are certainly pushing me into new territory. So the lightbulb went on….when going to another view, you are instantiating a new struct. So all values you pass are initialized. Not sure why I had to piece that together….
learned how to extend a FormatStyle:
extension FormatStyle where Self == FloatingPointFormatStyle<Double>.Currency {
static var localCurrency: Self {
.currency(code: Locale.current.currency?.identifier ?? "USD")
}
}
learned how to extend a view:
extension View {
func style(for item: ExpenseItem) -> some View {
switch item.valueCategory {
case .cheap:
return self
.foregroundColor(.green)
.font(.body)
case .normal:
return self
.foregroundColor(.yellow)
.font(.title3)
case .extravagent:
return self
.foregroundColor(.red)
.font(.title)
}
}
}
And forcing myself to use enums…I think this will pay off.
19DEC22 - Day 49 Complete! 100 days of swiftUI is simply fantastic! I subscribe to HWS+ as well, totally worth it. (Paul provides the solutions to the challenges and so much more) I love Paul’s saying: “Don’t spend all your time sharpening your pencil when you should be drawing” Loading data from a json file, saving to userdefaults, understanding how to pass data from one view to another, understanding structures, extensions…muy excelente!
The next part of the course is all about data. (which I’m pretty excited about) I haven’t read ahead, but I’m hoping to learn about databases. Ultimately, I want to write an app (still not decided) that will leverage a webserver to store data. The fewer changes I have to make at the app store, the better.
I have also used some of Paul Hudson’s books to clarify things. SwiftUI by example was very helpful. I bought all of them and will read all of them.
I kind of feel sorry for book stores. Any books they have are out-of-date as soon as they’re printed. Paul keeps his books current. A very nice feature.
So on to day 50, only 50 more to go!
13AUG23 - yes I have taken a long break. But I am back.
🕶️Day 58 things are getting more complicated
🕶️Dynamically filtering fetchrequests
🕶️Steps to create coredata model by hand
🕶️ one to many relationships coredata
Cored data failed to load model stackoverflow, saved my bacon
let container = NSPersistentContainer(name: “CandyDataModel”)
Examples of NSPredicate usage https://nspredicate.xyz
Look at this https://www.objc.io
19AUG23 - Finished wrap up for project 12, very powerful stuff - Generic Object, enum for filtering and sort order passed directly from form
🕶️core-data Review lots to absorb
27AUG23
🕶️Working on the Day 60 challenge, projects 10-12
I’m using the HWS+ to augment this: Both of these go into greater detail (although much quicker discussions) of core data and json.
5 Steps to setup CoreData:
- Data Model
- Setup Apple Permissions
- Create Class for CoreData
- Inject into swiftui
- Make less annoying by creating computed values that hide the optional values of coredata
Working on steps to quickly setup json structures
🕶️how to download json and decode
Restart top
24NOV23
Command Decision - Starting from scratch. I’m not consistent so every time I sit down, I have to review 3 or 4 lessons. So let’s just start again and let things sink in.
Apple has upgraded so many things AND, Paul Hudson has updated the material to reflect the changes. Why not just start from the beginning to learn/relearn the fundamentals?
Here we go: option + shift + 8 gives the ° symbol. Learn something everyday. Finished Checkpoint 1. OK, this is easy but still learning tidbits of info.
26NOV23
Finished days 3 - 6 This is a great review.
type inference:
var emptyArrayOfStrings = [String]()
var emptySet = Set<Int>()
var emptyDictionary = [String: String]()
27NOV23
Finished day 7, 8, and checkpoint 4
Closures tomorrow!
These is definitely easier the second time around. It’s even fun.
28NOV23
I finally understand closures! Use a closure for single usage cases. Otherwise create a function.
29NOV23
Finished Day 10
“Structs are one of the ways Swift lets us create our own data types out of several small types.” - Paul Hudson
30NOV23
Day 11
Key point that now makes sense the 2nd time around. Creating example data is an important part of creating sample views for you iphone app.
🕶️static properties and methods
01DEC23
Starting on objects.
This is the Key Difference between Structs and Objects from Paul:
“I’ve already said that SwiftUI uses structs extensively for its UI design. Well, it uses classes extensively for its data: when you show data from some object on the screen, or when you pass data between your layouts, you’ll usually be using classes.”
Straight from 🕶️Paul: classes and structs
Classes and structs give Swift developers the ability to create custom, complex types with properties and methods, but they have five important differences:
- Classes do not come with synthesized memberwise initializers. Authors of classes must write their own initializer by hand. This way, you can add properties as much as you want without affecting the initializer for your class, and affecting others who inherit from your class. And when you do decide to change your initializer, you’re doing so yourself, and are therefore fully aware of the implications for any inheriting classes.
- One class can be built upon (“inherit from”) another class, gaining its properties and methods.
- Copies of structs are always unique, whereas copies of classes actually point to the same shared data.
- Classes have deinitializers, which are methods that are called when an instance of the class is destroyed, but structs do not.
- Variable properties in constant classes can be modified freely, but variable properties in constant structs cannot.
04DEC23
I finished checkpoint 7 which is classes, inheritance, and initializers 🕶️This was particularly important - initializer
per Paul: Notice how the method has slightly different naming now: when we return a new value we used trimmed(), but when we modified the string directly we used trim(). This is intentional, and is part of Swift’s design guidelines: if you’re returning a new value rather than changing it in place, you should use word endings like ed or ing, like reversed().
var quote = " The truth is rarely pure and never simple "
extension String {
func trimmed() -> String {
self.trimmingCharacters(in: .whitespacesAndNewlines)
}
mutating func trim() {
self = self.trimmed()
}
}
quote.trim()
Learning how to use extensions. When you use an extension, you can access the other methods of the type. example:
extension String {
var isLong: Bool {
return count > 25
}
}
let john = "John Robert Deranian"
print(john.isLong)
print(john.count)
output:
false
20
From Example Above: count is another method of String so I can call it directly…very important point. And to reiterate all returned properties are computed properties
Up Next: 🕶️protocol extensions
7DEC23 Finished Day 13 checkpoint 8. I added an enum to the protocol type that worked perfectly!
protocol Building {
var rooms: Int {get set}
var cost: Int {get set}
var agent: String {get set}
var type: Building_Type {get}
}
extension Building {
func saleSummary() {
print("\(agent) sold this \(type) with \(rooms) rooms for the cost of \(cost)")
}
}
enum Building_Type {
case House, Office
}
struct House: Building {
var type = Building_Type.House
var rooms: Int
var cost: Int
var agent: String
}
struct Office: Building {
var type = Building_Type.Office
var rooms: Int
var cost: Int
var agent: String
}
var alaska = House(rooms: 6, cost: 640_000, agent: "Missy MoneyBags")
var corporate = Office(rooms: 90, cost: 2_000_000, agent: "Jerry Malone")
alaska.saleSummary()
corporate.saleSummary()
print(alaska.type)
print(corporate.type)
running everything through xcode has been the key to picking up syntax errors and hints to fix syntax.
9DEC23 Optionals are a key design feature of swift. Any data type can be optional.
From Paul Hudson:
In these chapters we’ve covered one of Swift’s most important features, and although most people find optionals hard to understand at first almost everyone agrees they are useful in practice.
Let’s recap what we learned:
- Optionals let us represent the absence of data, which means we’re able to say “this integer has no value” – that’s different from a fixed number such as 0.
- As a result, everything that isn’t optional definitely has a value inside, even if that’s just an empty string.
- Unwrapping an optional is the process of looking inside a box to see what it contains: if there’s a value inside it’s sent back for use, otherwise there will be nil inside.
- We can use if let to run some code if the optional has a value, or guard let to run some code if the optional doesn’t have a value – but with guard we must always exit the function afterwards.
- The nil coalescing operator, ??, unwraps and returns an optional’s value, or uses a default value instead.
- Optional chaining lets us read an optional inside another optional with a convenient syntax.
- If a function might throw errors, you can convert it into an optional using try? – you’ll either get back the function’s return value, or nil if an error is thrown.
- Optionals are second only to closures when it comes to language features folks struggle to learn, but I promise after a few months you’ll wonder how you could live without them!
10DEC23 Swfit Review. Don’t blink or you’ll miss something!
Paul’s Glossary of Terms reference page
26DEC23
Finished Day 19, first challenge complete. And now I’m finally running into a version issue. My old mac pro won’t take the latest os so it won’t take the latest version of xcode.
I have been waiting to buy a new computer as long as possible. Oh well.
28DEC23
🕶️This is an excellent review of stacks, colors, frames gradients, buttons and alerts
Very handy background notes
struct ContentView: View {
var body: some View {
VStack {
RadialGradient(colors: [.blue, .black], center: .center,
startRadius: 20, endRadius: 200)
AngularGradient(colors: [.red, .yellow, .green, .blue,
.purple, .red], center: .center)
Text("Your content")
.frame(maxWidth: .infinity, maxHeight: .infinity)
.foregroundStyle(.white)
.background(.indigo.gradient)
}
}
}
// https://www.hackingwithswift.com/books/ios-swiftui/buttons-and-images
// https://www.hackingwithswift.com/books/ios-swiftui/showing-alert-messages
Button("Show Alert") {
showingAlert = true
}
.alert("Important message", isPresented: $showingAlert) {
Button("OK", role: .cancel) { }
} message: {
Text("Please read this.")
}
29DEC23
nice addition: static let myArray = [ 1,2,3,etc] this is constant that is not part of structure instance.
🕶️Static properties and methods
you assign this with myTempArray = Self.myArray
and how to remove a value from and array: countries.remove(at: correctAnswer) where correctAnswer was an index
Today is our first technique project, and we’re focusing on two fundamental components of SwiftUI: views and modifiers. We’ve been using these already, but we’ve kind of glossed over exactly how they work. Well, that ends today: we’ll be going through lots of details about what they are, how they work, and why they work as they do.
30DEC23 This is very very powerful!
To create a custom modifier, create a new struct that conforms to the ViewModifier protocol. This has only one requirement, which is a method called body that accepts whatever content it’s being given to work with, and must return some View.
Key Point: Tip: Often folks wonder when it’s better to add a custom view modifier versus just adding a new method to View, and really it comes down to one main reason: custom view modifiers can have their own stored properties, whereas extensions to View cannot.
struct Watermark: ViewModifier {
var text: String
func body(content: Content) -> some View {
ZStack(alignment: .bottomTrailing) {
content
Text(text)
.font(.caption)
.foregroundStyle(.white)
.padding(5)
.background(.black)
}
}
}
extension View {
func watermarked(with text: String) -> some View {
modifier(Watermark(text: text))
}
}
Add a watermark to any view:
Color.blue
.frame(width: 300, height: 200)
.watermarked(with: "Hacking with Swift")
It is definitely much easier the second time around. It’s making a lot more sense. Views within views, very cool
🕶️Consolidation Day Projects 1-3
31DEC23
and This: Key points
01JAN24 Happy New Year Everyone! Ok, let’s get to work
learned a new shortcut: ctrl + cmd + space gives emoji menu in xcode
Paul used emoji instead of system images for this challenge. There has to be a way to increase the size of the system images besides .large which isn’t.
I also tried to use enums with raw values but that didn’t work. I wanted to pick a random value from the enum that would pull the name and the image to use. The case logic wasn’t any better.
The simple array of emoji’s worked perfectly, one array with the symbols built in.
I also tried to use a dictionay, but the randomElement was not helping without more code.
04JAN24
A Date
is independent of a particular calendar or time zone. To represent a Date
to a user, you must interpret it in the context of a Calendar
.
Review this:
static var defaultWakeTime: Date {
var components = DateComponents()
components.hour = 7
components.minute = 0
return Calendar.current.date(from: components) ?? .now
}
Note the use of static
var. Another var was using this as the struct was initializing. By making defaultWakeTime a static variable, it belongs to the ContentView struct itself rather than a single instance of that struct. This in turn means defaultWakeTime can be read whenever we want, because it doesn’t rely on the existence of any other properties. Great Review of Static Properties and Methods
Very Handy way to pluralize a prompt based on value
Stepper("^[\(coffeeAmount) cup](inflect: true)", value: $coffeeAmount, in: 1...20)
From Paul:
This is odd syntax, I know, but it’s actually a specialized form of Markdown, which is a common text-format. This syntax tells SwiftUI that the word “cup” needs to be inflected to match whatever is in the coffeeAmount variable, which in this case means it will automatically be converted from “cup” to “cups” as appropriate.
For the challenge, 🕶️Day 28 BetterRest Paul used a computed property instead of 3 onChanges. Much cleaner. I still like my layout though.
06JAN24
Per Paul
The job of List is to provide a scrolling table of data. In fact, it’s pretty much identical to Form, except it’s used for presentation of data rather than requesting user input. Don’t get me wrong: you’ll use Form quite a lot too, but really it’s just a specialized type of List.
🕶️List
Adding to bottom of screen like this: (score, format: .number) is mine, I used a double for scoring :)
.safeAreaInset(edge: .bottom) {
Text("Score \(score, format: .number)")
.frame(maxWidth: .infinity)
.padding()
.background(.blue)
.foregroundStyle(.white)
.font(.title)
}
Paul kept the guard theme going:
guard answer != rootWord else {
wordError(title: "That's the word already", message: "Try Again")
return
}
I was just going to use:
if answer == rootWord { wordError…. }
My own thoughts of the day: vim, organizing your thoughts, json files, README.md
XCode Keyboard Shortcuts - Local Copy
Organizing your thoughts
// MARK: -
// TODO:
// FIXME:
By pressing Control + 6, you can browse the contents of the file using the jump bar at the top of the editor. The dash or hyphen in the MARK comment adds a horizontal line or separator line above the title. By using this technique, you split the contents of the source file up into sections that are easier to browse and manage.
You could take it one step further by adding a build phase that generates a warning for every TODO and FIXME that hasn’t been addressed. This is very useful since you probably don’t want to ship an application that has one or more TODO’s or FIXME’s.
You can also use SwiftLint for this. SwiftLint is easy to add to a project using CocoaPods. After installing SwiftLint, create a build phase for SwiftLint and add the todo rule to the SwiftLint configuration file at the root of the project. When you build the project, SwiftLint automatically generates warnings for any TODO’s and FIXME’s.
README.md
Professional Readme Guide
07JAN24
Comments on Animation Challenge
.rotation3DEffect(.degrees(picked == number ? 360 : 0), axis: (x: 0 , y: 1, z: 0))
.animation(.default, value: picked)
.animation
is looking for a change in the value picked
in example above. It fires as soon as it does.
To make objects behave differently, e.g. a vstack of buttons, you can use a ternary operator (wtf) to control how the animation behaves for each button. The animation is still firing on the change of picked
in this case, but the behaviour has changed for each button. Very clever and very important to understand.
review swiftdata preview appcoda tips on optionals
navigationstack: https://sarunw.com/posts/how-to-pop-view-from-navigation-stack-in-swiftui/ This Was good! : https://stackoverflow.com/questions/57130866/how-to-show-navigationlink-as-a-button-in-swiftui Changed to this: https://stackoverflow.com/questions/75146794/how-to-replace-initdestinationtagselectionlabel-with-navigationlinkvalue
Animating views and transitions 🍏
https://sarunw.com/posts/swiftui-form-styling/
https://github.com/amosgyamfi/open-swiftui-animations
13JAN24 I have finished the challenge: https://www.hackingwithswift.com/guide/ios-swiftui/3/3/challenge
Since this was my second time, I was able to use tools that you don’t learn until later, namely: navigationDestination to another view. The logic of passing all your data to the new view/struct was finally ingrained in my head. Even setting up the previews with dummy data makes sense now.
Paul’s technique of using static values didn’t work for me on the preview. I need to find out why.
Animations? What is there to say except there is a lot a material out there. There are 3rd party libraries for this. We’ll see when/if Paul has a day on linking to external libraries.
14JAN24
🕶️[Day 36](Starting https://www.hackingwithswift.com/100/swiftui/36)
Learning how to use JSONEncoder to store data in Userdefaults with Codable, which allows you to archive more than simple strings i.e. structs into Userdefaults.
🕶️Next up: building a list we can delete from
I had to upgrade my old macpro (black trashcan) to a mac studio, because it would take the latest version of xcode so I couldn’t work in ios17. It’s pretty hard to build apps for the iphone if you can’t build to the latest version.
In apple’s defense, I have been using my old macpro for 7 years. It’s been a great box and will now have more mundane uses like a timemachine server.
15JAN24
https://www.hackingwithswift.com/100/swiftui/37
We’re going to write an app with @Observable, sheet(), Codable, Userdefaults, onDelete()
Everything is intuitive here except for IndexSet and the ability to remove with offsets.
I’m going to find a better explanation of IndexSet
and offsets
struct ContentView: View {
@State private var expenses = Expenses()
var body: some View {
NavigationStack {
List {
ForEach(expenses.items, id: \.name) { item in
Text(item.name)
}
.onDelete(perform: removeItems)
}
.navigationTitle("iExpense")
.toolbar {
Button("Add Expense", systemImage: "plus") {
let expense = ExpenseItem(name: "Test", type: "Personal", amount: 5)
expenses.items.append(expense)
}
EditButton()
}
}
}
func removeItems(at offsets: IndexSet) {
expenses.items.remove(atOffsets: offsets)
}
}
use uuidgen
from the command line to see one in action
Made the expenses unique by adding id = UUID()
, and adding the Identifiable
protocol to the struct like this:
struct ExpenseItem: Identifiable {
let id = UUID()
let name: String
let type: String
let amount: Double
}
Now in a ForEach loop, no need to specify the id. Swift knows
//ForEach(expenses.items, id: \.id) { item in // REMOVED replaced with line below
ForEach(expenses.items) { item in
dimisss takes two pieces:
@Environment(\.dismiss) private var dismiss
and in a button: dismiss()
🕶️Making Changes Permanent with userdefaults
Below is a very important piece of code that I will probably see time and time again: didSet
sees a change in items
, re-encodes, and updates UserDefaults
, On startup of app, init looks for UserDefaults and decodes, otherwise returns an empty array
items updated
.toolbar {
Button("Save") {
let item = ExpenseItem(name: name, type: type, amount: amount)
expenses.items.append(item)
}
}
Userdefaults updated
@Observable
class Expenses{
var items = [ExpenseItem]() {
didSet {
if let encoded = try? JSONEncoder().encode(items){
UserDefaults.standard.setValue(encoded, forKey: "Items")
}
}
}
init() {
if let savedItems = UserDefaults.standard.data(forKey: "Items") {
if let decodedItems = try? JSONDecoder().decode([ExpenseItem].self, from: savedItems) {
items = decodedItems
return
}
}
items = []
}
}
Computed properties in swift swiftbysundell
7 ways to organize SwiftUI Code
getting enums to work in a picker sarunw
my solution works, now to understand 🕶️Paul’s iexpense
I used enums to separate the lists. I did not rebuild the IndexSet() and push the itemsToBeDeleted onto a stack. It worked without this step. I’m working on the subview with my code. One more day and I’ll move on. Subviews will explained more without a doubt.
How to filter an array….much better than a foreach and pushing onto another array. Filter just does it! I did use my enum
var personalItems: [ExpenseItem] {
items.filter { $0.type == .Personal }
}
var businessItems: [ExpenseItem] {
items.filter { $0.type == .Business }
}
In the end, I used Paul solution mainly to understand how he did it. As always, very clever
18JAN24
🕶️For a change today what’s new in ios -17
This is part of the Hacking with Swift+ Membership that I highly highly recommend.
Big item I learned, you can add an if statement
to a section on a list to change views…
I thought I could only do this with bool state properties which are the chicken and egg problem.
I try to initialize those from other values that are initializing and it fails…..
Trick to initialize a var after view is loaded
swiftui handbook conditional modifer
conditional views in swiftui medium
Very Good Conversation of view modifiers
🕶️ternary conditional operator
struct ContentView: View {
@State private var viewIsBold = false
var body: some View {
Section{
Text("Hello, World!")
Button("toggle", action: {viewIsBold.toggle()})
}
//.modifier(ConditionalModifier(isBold : viewIsBold))
//or
.conditionalView(viewIsBold)
// or ternary conditional operator works too:
// .font(viewIsBold ? .custom("HelveticaNeue-bold", size: 14) :
.custom("HelveticaNeue", size: 14))
}
}
extension View {
func conditionalView(_ value: Bool) -> some View {
self.modifier(ConditionalModifier(isBold: value))
}
}
struct ConditionalModifier: ViewModifier {
var isBold: Bool
func body(content: Content) -> some View {
Group {
if self.isBold {
content.font(.custom("HelveticaNeue-Bold", size: 14))
}else{
content.font(.custom("HelveticaNeue", size: 14))
}
}
}
}
All about adding comments in swiftui, particularly swift Markup This is the bomb!
https://www.advancedswift.com/comments-documentation-swift/
local copy of swift commenting
🕶️access and change data from another view
🕶️make a scrollview snap with paging
19JAN24
🕶️Finished the what’s new in ios17
20JAN24
Initializing a var and then populating
I learned how to use the new scroll features. More importantly, however, was the application’s use of JSON and a datacontroller. In one view, a struct was created with no values, and then populated with .onAppear. I was banging my head on this one.
@State private var unusedActivities = [Activity]() // empty array of structs created here
// Populated here!
.onAppear {
unusedActivities = dataController.unusedActivities
}
// Here's the magic!
var unusedActivities: [Activity] {
allActivities.filter { item in
activityProgress.contains { other in
item.id == other.id
} == false
}
}
21JAN24
🍏 Apple does a pretty good job of explaining it
mastering navigationstack majid
This one is particularly good navigationstack
A great simple explanation of JSON for swift:
🕶️Working with hierarchical codable data
Now we’re down to formatting the views. You can specify the date formats from JSON, very powerful. Pay close attention to where you modify views e.g. scrollview, lazyvstack, image, etc. Paul used computed properities to format strings for the views, very clever.
Added an extension to Color. Color is part of Shapestyle, this extension was specific to Color
extension ShapeStyle where Self == Color {
static var darkBackground: Color {
Color(red: 0.1, green: 0.1, blue: 0.2)
}
static var lightBackground: Color {
Color(red: 0.2, green: 0.2, blue: 0.3)
}
}
🕶️jumping ahead how to make navigationstack return to root
READ THIS: https://dev.to/kuncans/the-new-navigationstack-navigationpath-for-swiftui-5cpa
22JAN24
Here’s some of Paul’s magic. Looking for items in another array: Using the crewname in mission.crew and looking for it in astronauts[crewname]
listed the crewmembers of a mission with mission.crew.map then searched the Dictionary: 🍏 astronauts, using the crewmember as the key
init(mission: Mission, astronauts: [String: Astronaut]) {
self.mission = mission
self.crew = mission.crew.map { member in
if let astronaut = astronauts[member.name] {
return CrewMember(role: member.role, astronaut: astronaut)
} else {
fatalError("Missing \(member.name)")
}
}
}
🕶️There was a lot to this challenge: Moonshot
- breaking up views,
- I moved the initializers (Paul didn’t)
@AppStorage("showingGrid") private var viewGrid = true
keeps state between sessions- formatting dates
- Grouping views with state vars
- initing the previews correctly: note the preferredColorScheme being readded
#Preview {
let missions: [Mission] = Bundle.main.decode("missions.json")
let astronauts: [String: Astronaut] = Bundle.main.decode("astronauts.json")
return ListLayoutView(astronauts: astronauts, missions: missions)
.preferredColorScheme(.dark)
}
My initial problem of the toolbar not working was simply because I had it in the wrong place. toolbar goes inside the navigationStack, not on it! (I knew this.)
Things are making sense. swiftui is really cool.
And now onto a subject that completely changed since I first started last year: Navigation
The Key difference is the use of NavgationPath
https://www.hackingwithswift.com/100/swiftui/43
How to track your navigationpath with an external class. I assume this is how you will always do it. One way uses a simple Int array, the other uses a navigationPath. The navigationPath requires extra care and feeding to store and retrieve the path. Pay attention.
🕶️This is money! How to save navigationstack paths using codable
Tomorrow: https://www.hackingwithswift.com/100/swiftui/45
23JAN24 Day 46
Part of the 🕶️navigation challenge is to replace a sheet with and navigationlink.
This helped greatly: https://stackoverflow.com/questions/74409227/toolbar-navigation-link
You can put a navigationlink in a toolbar. Path was not needed because this was the end of the line.
24JAN24 Finished the 🕶️navigation challenge
This helped swiftyplace navigationstack
Paul did not use the navigationPath at all for this challenge. ContentView had a NavigationStack. The subviews with the lists, only had the navigationlinks. This makes sense they’re really part of the overall ContentView. The navigationDestination does not have to be on the same view as the navigationStack, rather closer to the navigationLinks. I need to look more into this. But the big unexpected part was no mention of the navigationPath. I had too many navigationStacks but they worked.
🍏 From Apple - This Explains why:
Use a NavigationStack to present a stack of views over a root view. People can add views to the top of the stack by clicking or tapping a NavigationLink, and remove views using built-in, platform-appropriate controls, like a Back button or a swipe gesture. The stack always displays the most recently added view that hasn’t been removed, and doesn’t allow the root view to be removed.
So once you have created the navigationStack, you just add to the stack via the path directly or via navigationDestination.
https://ionic.io/blog/deploying-to-a-device-without-an-apple-developer-account
25JAN24 🕶️What we’ve learned up to this point
Links below are to the review material for each topic.
🕶️Reviewed What’s the difference between @ObservedObject, @State, and @EnvironmentObject?
🕶️How to enable editing on a list using EditButton
🕶️Reading and writing basics: UserDefaults
🕶️How ScrollView lets us work with scrolling data
🕶️Using Generics to load any kind of Codable data
NavigationLink and NavigationPath - review notes on 24JAN24
🕶️Customizing the navigationbar apperance
🕶️Placing toolbar buttons in exact locations based on function or personal choice
Github Notes:
- Relearn how to put these projects on github. Here’s one way: https://www.youtube.com/watch?v=pPhILJTFH_o
- Here’s a better one: https://youtu.be/v25er68nnk8?si=v55f2J_-eVz87NnC
- Best one current for xcode15: https://www.youtube.com/watch?v=HDm6kcYL90U
- One change I had to make: Because I create the projects with git locally, the integrate->Create new github repository complains that I already have a repository. Instead from the repository view, I right click on “Remotes” and select “New project Remote”. After that, all the commentary in this link is spot on!
🕶️[Challenge](And now the challenge: https://www.hackingwithswift.com/guide/ios-swiftui/4/3/challenge)
26JAN
🕶️Complete Guide to sf symbols
effective debugging medium.com
27JAN24
BEST YET, navigationlink, path and enums betterprogramming
I have used the enum to control the path. The extra trick of adding values detailActivityView(Exercise)
to an Enum (I’ll look up the technical jargon later) is key to making this work. Otherwise you’re defining the path and losing user values
Passing here: case .detailActivityView(let Exercise)
: DetailActivityView(exercise: Exercise, savedExercises: $savedExercises, homeNavigtionStack: $homeNavigtionStack)
The whole point of the DetailActivityView was to show the selected Exercise. The enum was able to handle it. Awesome!
enum EnumNavigation: Hashable {
case addActivityView, detailActivityView(Exercise), sfsymbolView, loggingView
}
.navigationDestination(for: EnumNavigation.self) { screen in
switch screen {
case .sfsymbolView: SfSymbolView()
case .addActivityView: AddActivityView(exercises: exercises,
savedExercises: $savedExercises, homeNavigtionStack: $homeNavigtionStack)
case .detailActivityView(let Exercise): DetailActivityView(exercise: Exercise,
savedExercises: $savedExercises, homeNavigtionStack: $homeNavigtionStack)
case .loggingView: LoggingView(savedExercises: $savedExercises,
homeNavigtionStack: $homeNavigtionStack, activityLogs: $activityLogs,
checkedExercises: $checkedExercies)
}
}
struct Exercise: Codable, Hashable {
var name: String
let force: String?
let level: String
let mechanic: String?
let equipment: String?
let primaryMuscles: [String]
let secondaryMuscles: [String]
let instructions: [String]
let category: String
var displayedForce: String {
force ?? "n/a"
}
var displayedMechanic: String {
mechanic ?? "none"
}
var displayedEquipment: String {
equipment ?? "none"
}
}
Huge Thank You to Akshay Mahajan
https://www.appcoda.com/swiftui-togglestyle/
https://www.appcoda.com/swiftui-checkbox/
I could not for the life of me, init my dictionary in the view. I finally found this BUT IT DIDN’T WORK. I have yet to be successful doing anything with a dictionary. I’m ready to give up on those.
So building a class with two values: Exercise:Exercise and a checked:Bool.
bindable swiftui list elements swiftbysundell Highlights from Article:
struct NoteListView: View {
@ObservedObject var list: NoteList
var body: some View {
List {
ForEach($list.notes) { $note in
NavigationLink(note.title,
destination: NoteEditView(
note: $note
)
)
}
}
}
}
Note that we can reference our closure’s $note input either with or without its dollar prefix, depending on whether we want to access the underlying value directly, or the binding that encapsulates it. Something that’s really nice about the above new syntax is that it’s actually fully backward compatible with all previous operating systems on which SwiftUI is supported — so on iOS, that means as far back as iOS 13. The only requirement is that we have to build our app using the compiler and SDK that’s included in Xcode 13.
Next UP: Write the log to disk. Sort/Filter exercises on difficulty, muscleGroup. Create Search Engine as well. Then, I think I’ll be done. You can add eyecandy all day, but that can come later.
31JAN24
https://www.appcoda.com/swiftui-confetti-animation/
https://blog.stackademic.com/swiftui-textfield-clear-button-a3070c5b587e
adding to json file with jq: https://gist.github.com/joar/776b7d176196592ed5d8
https://stackoverflow.com/questions/34543829/jq-cannot-index-array-with-string
Today, I spent way too much time trying to add a field to my exercises.json file so that the exercises would be unique and the same everytime I loaded the app.
These 2 lines did it:
cat exercises.json | jq ‘.[] += {“uuid” : “Replace” }’
.,$ s/replace/=trim(system(‘uuidgen’))/g # need the trim to get rid of the /n
GOOD ONE how to print debugging swift
migrating to observable protocol 🍏
02FEB24
ForEach requires an ordered data source, a Set is unordered by definition.
A simple solution is to sort the Set for example by exercisename
ForEach(selectedItems.sorted{$0.exercisename < $1.exercisename})
The result of sorted is an array which is 'RandomAccessCollection compliant.
Search bar best practices swiftyplace
05FEB24 Maybe this will finally sink in.
How to get another view with button click
I’m trying to make a view searchable that is not at the root of the navigation stack. Adding the .searchable to stack displays the search window where I don’t want it and I’m trying not to change the way the app is setup. So I’m going to display sheeet with a new naviationstack and see where that takes me.
I imported json file with a uuid: instead of id:. swift is complaining about not “Identifiable”, so now I need to figure out how to make make my uuid: field identifiable.
I have been banging on this for several days: How to add searchable to a view that does not “have” a navigtionStack. Well, mine was embedded, so it turns out I could just add .searchable to the Vstack along with my search filter and Voila!, it worked.
Also, I happened to discover that you must have an id: field in your struct for Identifiable to work. In my case, my uuid field was uuid: so by putting in a computed property, swift stopped complaining
var id:UUID {
return uuid.self
}
🕶️Or, I could have remapped uuid: into id: with this
To make that happen we need to declare a CodingKeys enum: a mapping that Codable can use to convert JSON names into properties for our struct. This is a regular enum that uses strings for its raw values so that we can specify both our property name (the enum case) and the JSON name (the enum value) at the same time. It also needs to conform to the CodingKey protocol, which is what makes this work with the Codable protocol.
enum CodingKeys: String, CodingKey {
case firstName = "user_first_name"
case lastName = "user_last_name"
case age
}
🕶️Common swiftui erros and to fix them
06FEB24
How to cite paper github markdown
🕶️Looking at the solution to the challenge habit tracking
Recurring theme: putting a systemImage in a Button. Here’s the syntax:
.toolbar {
Button("Add", systemImage: "plus") {
addingNewActivity.toggle() // changing state fires a sheet view
}
}
Finally on to Day 50. I pretty much melted the last challenge and learned a lot of new stuff.
-
Observable objects have changed:
-
Searching a view has completely changed
- 🕶️Making a Swiftui view searchable
- Karin Prater at swiftyplace.com just rocked it!
- If the view is part of a stack, you can add .searchable to it…..very very cool (no new call to navigationStack, swift complains loudly and you lose your navigation as well)
-
Did you know that you must have a var
id:
in your structure in order for it to beIdentifiable
period dot end of story -
json manipulation is making way more sense
-
NavigationStack
- Best Description by Akshay Mahajan betterprogramming.pub The use of enums to control the stack makes it very logical and controllable.
AsyncImage(url: URL(string: "https://drano.net/images/woodyharoldson_cartel.jpg")) { phase in
if let image = phase.image {
image
.resizable()
.scaledToFit()
} else if phase.error != nil {
Text("There was an error loading the image.")
} else {
ProgressView()
}
}
07FEB24
Finished Project 10 minus the challenge. The extra work I did on observable really paid off. The extra bits in this project:
- enum CodingKeys: String, CodingKey { case _name =“name ….} to make the struct match with the json data
- haptic, interesting, but not needed yet
- one class was used in project to track all data throughout the views, very very handy
- validating fields with one computed state property
- 🕶️sending json data to a server, very big
🕶️Challenge tomorrow: Cupcake Corner
08FEB24
Running your app in simulator or device 🍏
clever extension:
extension String {
var isReallyEmpty: Bool {
self.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
}
}
var hasValidAddress: Bool {
if name.isReallyEmpty || streetAddress.isReallyEmpty ||
city.isReallyEmpty || zip.isReallyEmpty { // Paul's
return false
}
return true
}
VERY IMPORTANT!🕶️Swiftdata
09FEB24 Steps to use swiftData
- Create Class that will be the database:
import Foundation
import SwiftData
@Model // NOT @Observable
class Student {
var id: UUID
var name: String
init(id: UUID, name: String) {
self.id = id
self.name = name
}
}
- Appstruct add .modelContainer(for: Student.self)
import SwiftUI
import SwiftData
@main
struct Sandbox2App: App {
var body: some Scene {
WindowGroup {
ContentView()
}
.modelContainer(for: Student.self)
}
}
- Add @Query and @Environmnet(.modelContext) var modelContext to main View (ContentView) This loads the database into RAM with @Query used
struct ContentView: View {
@Query var students: [Student] // This queries all entries, you can filter as you like here
@Environment(\.modelContext) var modelContext
To setup a datamodel in preview: many steps
#Preview {
do {
let config = ModelConfiguration(isStoredInMemoryOnly: true)
let container = try ModelContainer(for: Book.self, configurations: config)
let example = Book(title: "Test Book", author: "Test Author", genre: "Fantasy",
review: "This was a great book; I really enjoyed it.", rating: 4)
return DetailView(book: example)
.modelContainer(container)
} catch {
return Text("Failed to create preview: \(error.localizedDescription)")
}
}
Handy Trick: note the use of .constant…prevents user editing eventhough original view allows it
RatingView(rating: .constant(book.rating))
.font(.largeTitle)
https://peterfriese.dev/blog/2019/xcode-shortcuts/
10FEB24
How to use macOS’s Character Viewer to type emoji and other symbols ⌘ Command ⌃ Control Space to pull emjoi viewer (once set in keyboard settings)
Part of the Challenge is formatting a date:
More clever: Swift date formatting swiftyplace
Mine was more direct:
Text(formatDate(book.date))
.font(.footnote)
func formatDate(_ date: Date) -> String {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "ddMMMyyyy hh:mm"
let dateString = dateFormatter.string(from: date)
return dateString.uppercased()
}
Key point of working with a database, if you change the structure, you need to delete the app in your simulator and start fresh.
🕶️Onto SwiftData in more detail
Oh, And my comment was published!
Another keyboard shortcut tip: Going Back and Forth (⌃ + ⌘ + ← and ⌃ + ⌘ + →)
Xcode tracks your entire movement history as soon as you open or create a project. Any file you go to, any symbol you look up - it all is appended to the IDE’s navigation stack. Use ⌃ + ⌘ + ← and ⌃ + ⌘ + → to go back and forth between the source code locations you visited.
IMPORTANT QUERY NOTES
🕶️dynamically sorting and filtering query
To make queries dynamic, Paul used a separate view to display the query results and passed the query parameters in as well.
This is best done by passing a value into the view using an initializer, then using that to create the query. The UsersView initializer also accepts some kind of sort descriptor for our User class. This uses Swift’s generics again: the SortDescriptor type needs to know what it’s sorting, so we need to specify User inside angle brackets.
init(minimumJoinDate: Date, sortOrder: [SortDescriptor<User>]) {
_users = Query(filter: #Predicate<User> { user in
user.joinDate >= minimumJoinDate
}, sort: sortOrder)
}
From the ContentView: Building a pickable sort order which is then passed to the queryView
Menu("Sort", systemImage: "arrow.up.arrow.down"){
Picker("Sort", selection: $sortOrder) {
Text("Sort by Name")
.tag([
SortDescriptor(\User.name),
SortDescriptor(\User.joinDate),
])
Text("Sort by Join Date")
.tag([
SortDescriptor(\User.joinDate),
SortDescriptor(\User.name)
])
}
}
11FEB24
Bought my developer license today!
Distributing your app to registered devices 🍏
To Sync with iCloud: https://www.hackingwithswift.com/books/ios-swiftui/syncing-swiftdata-with-cloudkit
SwiftData with iCloud has a requirement that local SwiftData does not: all properties must be optional or have default values, and all relationship must be optional. The first of those is a small annoyance, but the second is a much bigger annoyance – it can be quite disruptive for your code.
What are app ids and bundle identifiers
I’m not a particularly big fan of scattering that kind of code everywhere around a project, so if I’m using jobs regularly I’d much rather create a read-only computed property called unwrappedJobs or similar – something that either returns jobs if it has a value, or an empty array otherwise, like this:
var unwrappedJobs: [Job] { jobs ?? [] }
🕶️5 Steps to better swiftui views
swiftui protocols fivestars.blog
If your toolbar is getting messy, you can separate if off
12FEB24
Very good example of building predicates and sortorders dynamically
Filtering and sorting persistent data 🍏
🕶️swiftdata trouble with predicates
Separating the predicate:
@Query private var homes: [Home]
init(session: Session) {
let id = session.persistentModelID
let predicate = #Predicate<Home> { home in
home.session?.persistentModelID == id
}
_homes = Query(filter: predicate, sort: [SortDescriptor(\.timestamp)] )
}
So I have discovered what others have discovered. The queries with enums don’t work.
I need to change my database and remove the enums. Not a big deal except enums are great for forcing logic and I plan on using them extensively. Let’s hope Apple fixes this soon.
defining data relationships with enums 🍏
13FEB24
change a property in Expense and now it won’t build, trying to just delete it, but it won’t start to let me delete it…pretty silly
swiftui sf symbols swiftyplace
14FEB24
changed out the enum to a string and now the query works. I do still use the enum to build the views and flip to string as I add to the swiftdata.
Another Big shoutout the swiftyplace. I used the same structure as: https://www.swiftyplace.com/blog/modeling-data-in-swiftdata
enum TagSorting: String, Identifiable, CaseIterable {
case byName
case byAmount
var title: String {
switch self {
case .byName:
return "Sort by Name"
case .byAmount:
return "Sort by Amount"
}
}
var sortDescriptor: [SortDescriptor<Expense1>] {
switch self {
case .byName:
[
SortDescriptor(\Expense1.name),
SortDescriptor(\Expense1.amount)
]
case .byAmount:
[
SortDescriptor(\Expense1.amount),
SortDescriptor(\Expense1.name)
]
}
}
var id: Self { return self }
}
which turned my menu into:
Menu {
Picker(selection: $tagSorting.animation()) {
ForEach(TagSorting.allCases) { tag in
Text(tag.title)
}
} label: {
Text("Sort Tags by")
}
} label: {
Label("Sorting", systemImage: "slider.horizontal.3")
}
The animation is an added bonus. Notice how new vars are defined in the enum using switch self. VERY COOL!
my first attempt to use icloud: “Failed to setup cloudKit integration for store”
Pushing background updates to your app 🍏
15FEB24
🕶️Very good notes on reading JSON files that have different keys or property types
adding the download of the json file to the app struct
🕶️how to preload an app with json data
type does not conform to protocol decodable
🕶️How to make swiftdata models conform to codable
16FEB24
ignoring invalid json elements swiftbysundell
this might be worth a read swiftui model review update
5 things you should know optionals avanderlee
When you accidently corrupt the data container because you’re playing with the structure. Hint, delete before changing. possible way to get rid of “error Source URL” has 🕶️disappeared
Just give it a new name and restart I have had to do this several times, but at least this is quick and easy to do. Found better answer on 23FEB - How to delete the files so the app regens on recompile
@main
struct MyFaceBookApp: App {
var container: ModelContainer
init() {
do {
-----> let storeURL = URL.documentsDirectory
.appending(path: "myfacbook_v4.sqlite")
let config = ModelConfiguration(url: storeURL)
container = try ModelContainer(for: User.self,
configurations: config)
} catch {
fatalError("Could not initialize ModelContainer")
}
}
var body: some Scene {
WindowGroup {
ContentView()
}
.modelContainer(container)
}
}
17FEB24
READ THIS guide to building swiftdata applications
how to parse iso8601 date sarunw
I have been banging on this for several days looking for the silver bullet but never found one:
I finally figured out how to parse a date from json that the decoder wouldn’t read by default. I had to save as a string and once the init was complete, changed my date field from .now to the parsed string….
reading the date field as .iso8601 did not work eventhough Paul said it would.
You need a real date field if you wish to have real queries, computed properties are never part of a query.
func updateRegisteredDate(for stringDate: String) -> Date {
let RFC3339DateFormatter = DateFormatter()
RFC3339DateFormatter.locale = Locale(identifier: "en_US_POSIX")
RFC3339DateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZZZZZ"
RFC3339DateFormatter.timeZone = TimeZone(secondsFromGMT: 0)
if let date = RFC3339DateFormatter.date(from: stringDate) {
return date
}
return .now
}
}
The key piece:
func loadMessages() async {
do {
try? modelContext.delete(model: User.self)
let url = URL(string: "https://www.hackingwithswift.com/samples/friendface.json")!
let (data, _) = try await URLSession.shared.data(from: url)
var users = [User]()
users = try JSONDecoder().decode([User].self, from: data)
for user in users {
-----> user.registeredDate = updateRegisteredDate(for: user.registered)
modelContext.insert(user)
}
} catch {
//let _ = print("Failed to load url.")
let _ = print("Download error: \(error)")
}
}
18FEB
maybe use this for the contentview: filtering and sorting 🍏
Finally found the right answer to formatting dates that apple doesn’t handle by default
extension JSONDecoder.DateDecodingStrategy {
static func anyFormatter(in formatters: [DateFormatter]) -> Self {
return .custom { decoder in
guard formatters.count > 0 else {
throw DecodingError.dataCorrupted
(DecodingError.Context(codingPath: decoder.codingPath,
debugDescription: "No date formatter provided"))
}
guard let dateString = try? decoder.singleValueContainer().
decode(String.self) else { throw DecodingError.dataCorrupted(
DecodingError.Context(codingPath: decoder.codingPath,
debugDescription: "Could not decode date string"))
}
let successfullyFormattedDates = formatters.lazy.compactMap {
$0.date(from: dateString) }
guard let date = successfullyFormattedDates.first else {
throw DecodingError.dataCorrupted(DecodingError.Context(
codingPath: decoder.codingPath, debugDescription:
"Date string \"\(dateString)\" does not match any of the expected
formats (\(formatters.compactMap(\.dateFormat).joined(separator: " or ")))"))
}
return date
}
}
}
extension KeyedDecodingContainer {
func decodeDate(forKey key: KeyedDecodingContainer<K>.Key,
withPossible formats:
[DateFormatter]) throws -> Date? {
for format in formats {
if let date = format.date(from: try
self.decode(String.self, forKey: key)) {
return date
}
}
throw DecodingError.dataCorruptedError(
forKey: key, in: self, debugDescription:
"Date string does not match format expected by formatter.")
}
}
extension DateFormatter {
static let iso8601Full: DateFormatter = {
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZZZZZ"
formatter.calendar = Calendar(identifier: .iso8601)
formatter.timeZone = TimeZone(secondsFromGMT: 0)
formatter.locale = Locale(identifier: "en_US_POSIX")
return formatter
}()
static let yyyyMMdd: DateFormatter = {
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd"
formatter.calendar = Calendar(identifier: .iso8601)
formatter.timeZone = TimeZone(secondsFromGMT: 0)
formatter.locale = Locale(identifier: "en_US_POSIX")
return formatter
}()
}
From my class:
var registered: Date? example: 2014-07-05T04:25:04-01:00 Apple does not read this out of the box…crazy
how I used the above to bypass the defaults:
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
address = try container.decode(String.self, forKey: .address)
about = try container.decode(String.self, forKey: .about)
// MARK: note the call to decodeDate not decode
registered = try container.decodeDate(forKey: .registered,
withPossible: [.iso8601Full, .yyyyMMdd])
}
iso8601 fails miserably
extension URLSession {
func decode<T: Decodable>(
_ type: T.Type = T.self,
from url: URL,
keyDecodingStrategy: JSONDecoder.KeyDecodingStrategy = .useDefaultKeys,
dataDecodingStrategy: JSONDecoder.DataDecodingStrategy = .deferredToData,
dateDecodingStrategy: JSONDecoder.DateDecodingStrategy = .iso8601
//https://stackoverflow.com/questions/44682626/
swifts-jsondecoder-with-multiple-date-formats-in-a-json-string
) async throws -> T {
let (data, _) = try await data(from: url)
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = keyDecodingStrategy
decoder.dataDecodingStrategy = dataDecodingStrategy
decoder.dateDecodingStrategy = dateDecodingStrategy
let decoded = try decoder.decode(T.self, from: data)
return decoded
}
}
19FEB24
Trying to have contentView had a dynamic query refresh:
swiftdata query with dynamic properties
23FEB24
There is probably a better way to do this, and actually migrate the old data. But what I’ve been doing since I have just been working with new apps in simulators so far is this.
In your main app file, add an initializer like this…
import SwiftData
import SwiftUI
@main
struct MyApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
.modelContainer(for: MyDataType.self)
}
init() {
print(URL.applicationSupportDirectory.path(percentEncoded: false))
}
}
Then, when you run your app, it will print a line that tells you the location where your database files are stored. You can go to that location in finder, and you should find 3 files there. (default.store, default.store-shm, and default.store-wal)
If you delete all 3 of those files, they will automatically regenerate when you run your app again. However, your data will be completely deleted, and you will start with a fresh empty database. But it has allowed me to work around errors like this while I’m still in the beginning stages of making my app.
swift shortcut unwrapping an array of optionals
26FEB Well it turns out that the date extension was mangling the date. iso8601 worked after all. I learned alot about making extensions and customizing date formats so all was not lost.
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
users = try decoder.decode([User].self, from: data)
Finished with Day 61
Onto Days 62-76: Filters, maps, and more
Big shift today… 🕶️watch this again integrating core image with swiftui
02MAR24
How to call a function from a subview
Just finished Day 67
I’ve slowed down and having much more fun. swift is a great language.
I did try to use subviews but it got messsy calling methods not in the child view. The calls were still in the parent view so it complained. This project did not use a class to maintain a centralized place to keep functions.
This project highlighted the reason to do so. (at least for me)
notes from apple 🍏 on processing an image using buil-in filters
03MAR24
How to sort multiple properties sarunw
Working with files and folders swiftbysundell
Just look at how SwiftUI’s built-in API was designed — containers (such as HStack and VStack) are views, while styling APIs (such as padding and foregroundColor) are implemented as modifiers. So, if we follow that same approach as much as possible within our own projects, then we’ll likely end up with UI code that feels consistent and inline with SwiftUI itself.
I have seen the same with Paul Hudson’s approach.
filemanager reading,writing, deleting swiftyplace
URL ResourceKeys - part of the Filemanager contentsofDirectory call explained
05MAR24
Day 69, Touch ID, Face ID, yesterday was integrating Mapkit into swift. Pretty Cool. Not sure if I’ll ever use it.
https://www.hackingwithswift.com/books/ios-swiftui/using-touch-id-and-face-id-with-swiftui
Tomorrow, I think I’ll get my old iphone 12 mini working with all of this…
07MAR Day 72
Reading json data from wikipedia, storing map locations, and MVVM. All very good!
extension ContentView {
@Observable
class ViewModel {
}
}
Tip: I get lots of questions about why I place my view models into view extensions, so I’d like to take a moment to explain why. This is a small app, but think about how this would look when you have 10 views, or 50 views, or even 500 views. If you use extensions like this, the view model for your current view is always just called ViewModel, and not EditMapLocationViewModel or similar – it’s much shorter, and avoids cluttering up your code with lots of different class names!
one rule of customizing comparable, must provide a < func
static func < (lhs: Page, rhs: Page) -> Bool {
lhs.title < rhs.title
}
From Paul:
That took quite a bit of code in total, but the end result is that we have loading and saving done really well:
- All the logic is handled outside the view, so later on when you learn to write tests you’ll find the view model is much easier to work with.
- When we write data we’re making iOS encrypt it so the file can’t be read or written until the user unlocks their device.
- The load and save process is almost transparent – our view has no idea it’s even happening.
Sometimes people ask me why I didn’t introduce MVVM earlier in the course, and there are two primary answers:
- It works really badly with SwiftData, at least right now. This might improve in the future, but right now using SwiftData is basically impossible with MVVM.
- There are lots of ways of structuring projects, with MVVM being just one of many. Spend some time experimenting rather than locking yourself into the first idea that comes along.
Tomorrow, Locking our UI behind FaceID
11MAR24
🕶️How to handle authentication errors/NSErrors
🕶️Confirmation Dialog vs alert
Visually alerts and confirmation dialogs are very different: on iPhones, alerts appear in the center of the screen and must actively be dismissed by choosing a button, whereas confirmation dialogs slide up from the bottom, can contain lots of buttons, and can be dismissed by tapping on Cancel or by tapping outside of the options.
Opening the Settings app from another app
UIApplication.open(_:options:completionHandler:) must be used from main thread only
18MAR24
https://www.hackingwithswift.com/books/ios-swiftui/identifying-views-with-useful-labels
onTapGesture()
.accessibilityLabel()
.accessibilityHint()
.accessibilityAddTraits(.isButton)
.accessibilityRemoveTraits(.isImage)
Image(decorative: "character")
.accessibilityHidden(true)
VStack {
Text("Your score is")
Text("1000")
.font(.title)
}
.accessibilityElement(children: .combine)
OR
VStack {
Text("Your score is")
Text("1000")
.font(.title)
}
.accessibilityElement(children: .ignore) ### .ignore is the default so you can simply write .accessibiltiyElement()
.accessibilityLabel("Your score is 1000")
https://www.hackingwithswift.com/books/ios-swiftui/reading-the-value-of-controls
To fix this we can give iOS specific instructions for how to handle adjustment, by grouping our VStack together using accessibilityElement() and accessibilityLabel(), then by adding the accessibilityValue() and accessibilityAdjustableAction() modifiers to respond to swipes with custom code.
Adjustable actions hand us the direction the user swiped, and we can respond however we want. There is one proviso: yes, we can choose between increment and decrement swipes, but we also need a special default case to handle unknown future values – Apple has reserved the right to add other kinds of adjustments in the future.
VStack {
Text("Value: \(value)")
Button("Increment") {
value += 1
}
Button("Decrement") {
value -= 1
}
}
.accessibilityElement()
.accessibilityLabel("Value")
.accessibilityValue(String(value))
.accessibilityAdjustableAction { direction in
switch direction {
case .increment:
value += 1
case .decrement:
value -= 1
default:
print("Not handled.")
}
}
https://www.hackingwithswift.com/books/ios-swiftui/handling-voice-input-in-swiftui
Button("John Fitzgerald Kennedy") {
print("Button tapped")
}
.accessibilityInputLabels(["John Fitzgerald Kennedy", "Kennedy", "JFK"])
https://mfaani.com/posts/why-cant-xcode-show-caller/
21MAR24 Milestone Projects 13-15
All swiftui property wrappers explained
Operation Overloading & Custom Property Wrappers
25MAR
https://www.hackingwithswift.com/quick-start/swiftui/how-to-show-a-menu-when-a-button-is-pressed
https://rectangleapp.com - handy app to allow mac to align windows
https://stackoverflow.com/questions/29462953/how-to-add-an-optional-string-extension
https://www.hackingwithswift.com/books/ios-swiftui/using-alert-and-sheet-with-optionals
https://www.hackingwithswift.com/books/ios-swiftui/customizing-our-filter-using-confirmationdialog
28MAR
I need to init the database when the contentview opens…. Once I know if the db is empty, I’ll show a different list view with a prompt to create an entry
https://www.swiftbysundell.com/articles/handling-loading-states-in-swiftui/
29MAR https://www.hackingwithswift.com/articles/226/5-steps-to-better-swiftui-views
@19:20 explains why you call somefunction for a button action instead of somefunction()
31MAR24
Setting up swiftdata to use mvvm - Key Post
review:
https://www.hackingwithswift.com/quick-start/swiftdata
I need to convert my sample pic into a data file for inserting into swiftdata.
I just discovered this: https://www.youtube.com/watch?v=y3LofRLPUM8
Just convert image into data by loadTransferable and define image as data in your class
04APR straightforward way to use MVVM with swiftdata….not using Paul’s view extension for this(yet)
https://blog.devgenius.io/swiftdata-with-mvvm-and-swiftui-fdf0fc1b2c4f
06APR Splitting SwiftData and SwiftUI via MVVM
https://www.avanderlee.com/swift/mainactor-dispatch-main-thread/
11APR24
https://developer.apple.com/tutorials/develop-in-swift/save-data
https://www.donnywals.com/an-introduction-to-synchronizing-access-with-swifts-actors/
24APR24
Looks like this project is tying everything together…tabs, navigationstack swiftdata, enums
https://www.hackingwithswift.com/books/ios-swiftui/building-our-tab-bar
27APR
https://www.hackingwithswift.com/quick-start/swiftui/displaying-a-detail-screen-with-navigationlink