translation
a free Hacking with Swift tutorial
More content welcome to follow the public account “Swift Garden”
Struct vs class
There are two things in Swift that will be familiar to you: structures and classes. They are both methods that allow us to build complex data types with properties and methods, but the way they work, and especially the difference between the two, is a matter.
If you recall, there are five key differences between structures and classes:
- Class does not have a member-by-member constructor; Structs are constructed as functions one by one by default.
- Classes can use inheritance to build functionality; The structure doesn’t.
- If you copy a class, both copies point to the same data; But the copy of the structure, its data is independent.
- Classes can have destructors; The structure doesn’t have one.
- You can change the value of a variable attribute in a constant class instance; But a property in an instance of a constant structure is fixed, whether it is a constant or a variable.
In Objective-C, Apple’s original programming language, we used classes for almost everything — because there was no choice, almost everything worked based on classes.
In Swift, we have options, which should be based on those differences mentioned above. I say “should” because it’s not uncommon to see people who don’t care about the difference, just use class or struct.
Choosing a structure or class is up to you and the problem you’re trying to solve. But I want you to think about how they communicate your intentions. Donald Knuth said, “Programs are for people to read, and occasionally for computers to run,” and that was right up my street: Is your intent clearly communicated to others when they read your code?
If you use structures most of the time and then switch to classes in one particular place, that conveys the intent that this thing is different and needs to be used differently. But if you use classes all the time, the distinction disappears — after all, it’s unlikely that you’ll have to use classes most of the time.
Tip: One of the fascinating details of SwiftUI is that it reverses the way we use structures and classes. In UIKit we use structures for data and classes for UI, but in SwiftUI it’s completely the opposite — a reminder that learning is important, even if you don’t think new knowledge will come in handy right away.
ForEach
The second thing to discuss is ForEach, which we might use like this:
ForEach(0 ..< 100) { number in
Text("Row \(number)")}Copy the code
ForEach is a view, like most views in SwiftUI, but it allows you to create other views within a loop. By doing this, we can plan the SwiftUI 10 subview limit — ForEach itself is the target of the 10 subview limit, not the contents of it.
Now, look at an array of strings like this:
let agents = ["Cyril"."Lana"."Pam"."Sterling"]Copy the code
How do we iterate over these strings to create a text view?
One option is to build it the way we already do:
VStack {
ForEach(0 ..< agents.count) {
Text(self.agents[$0])}}Copy the code
SwiftUI, however, offers a second option: we can simply iterate through groups of numbers. This takes a bit of thinking, because SwiftUI needs to know how to identify each item in the array.
Think about it: if we iterate over an array of 4 elements, we create 4 views, but if the body is called again, our array now contains 5 elements, SwiftUI needs to know which view is new in order to show it. The last thing SwiftUI wants to do: scrap the entire layout and start from scratch every time a small change happens. Instead, it wants to do as little work as possible — it wants to keep the four views that already exist and only add a fifth.
So let’s go back to where Swift recognizes the elements in the array. When we use things like 0.. < 5 or 0.. A range like < agents. Count, Swift is already convinced that each element is unique and must be unique because each element is used only once in the loop.
In our array of strings, this becomes impossible. We cannot be clearly convinced that each value is unique: it requires that [“Cyril”, “Lana”, “Pam”, “Sterling”] not be repeated. So what we can do is tell SwiftUI the strings themselves — “Cyril”, “Lana”, etc. — that they are used to uniquely identify each view in the loop.
The code looks like this:
VStack {
ForEach(agents, id: \.self) {
Text($0)}}Copy the code
Instead of iterating through the number and then accessing the array as a subscript, we now read the array directly, just like we do for loops.
As you get closer to SwiftUI, we’ll see a third way of identifying views, which is using the Identifiable agreement, but we’ll talk about that later.
The binding
When we use controls like Picker and TextField, we add bidirectional binding to them via the @state attribute preannotation. This works well for simple attributes. But sometimes — hopefully only occasionally — you might need something a little more advanced: What if your current value needs to be computed by running code logic? Or are you thinking about things other than storage when values are written?
If we need to react to changes in the binding, we may need to resort to Swift’s didSet attribute observer. But you may be disappointed. Here’s how the custom binding works: The custom binding is exactly the same as the @state binding, except that we have full control.
Binding isn’t magic, @state just takes some boring sample code out of the way, and it’s perfectly fine to create and manage your own binding code if you want. Again, it’s not common to do this, it’s actually not common, and I hope you don’t either. Instead, I demonstrate the following to dispel the notion that SwiftUI is doing some magic on your code.
SwiftUI does things for us that we can do manually. Although it’s always better to rely on automated schemes. This is just to help us understand what’s going on underneath the behavior.
Let’s look at the simplest form of the custom binding, which holds the value of another @state property:
struct ContentView: View {@State var selection = 0
var body: some View {
let binding = Binding(
get: { self.selection },
set: { self.selection = $0})return VStack {
Picker("Select a number", selection: binding) {
ForEach(0 ..< 3) {
Text("Item \ [$0)")
}
}.pickerStyle(SegmentedPickerStyle()}}}Copy the code
So, the binding here plays the role of pass-through — it doesn’t actually store or calculate any data on its own, just acts as a “chip” between our UI and the underlying state values.
Note, however, that the picker is now created with selection: Binding and the $sign is no longer required. We don’t need to explicitly require bidirectional binding because it already exists.
If we wish, we can create more advanced bindings than just passing through a number. As an example, imagine that we have a form with three switches: the user agrees to terms, the user agrees to implicit policies, and the user agrees to receive mail.
We might represent them with three Boolean @state attributes:
@State var agreedToTerms = false
@State var agreedToPrivacyPolicy = false
@State var agreedToEmails = falseCopy the code
Although the user triggers them individually, we can implement them with a custom binding. This binding is true only if all three booleans are true, like this:
let agreedToAll = Binding(
get: {
self.agreedToTerms && self.agreedToPrivacyPolicy && self.agreedToEmails
},
set: {
self.agreedToTerms = $0
self.agreedToPrivacyPolicy = $0
self.agreedToEmails = $0})Copy the code
Now we can also implement four switches: one for each independent Boolean, and a master switch for agreeing or disagreeing:
struct ContentView: View {@State var agreedToTerms = false
@State var agreedToPrivacyPolicy = false
@State var agreedToEmails = false
var body: some View {
let agreedToAll = Binding<Bool> (get: {
self.agreedToTerms && self.agreedToPrivacyPolicy && self.agreedToEmails
},
set: {
self.agreedToTerms = $0
self.agreedToPrivacyPolicy = $0
self.agreedToEmails = $0})return VStack {
Toggle(isOn: $agreedToTerms) {
Text("Agree to terms")}Toggle(isOn: $agreedToPrivacyPolicy) {
Text("Agree to privacy policy")}Toggle(isOn: $agreedToEmails) {
Text("Agree to receive shipping emails")}Toggle(isOn: agreedToAll) {
Text("Agree to all")}}}}Copy the code
Again, custom bindings are not something you want to use very often. Despite Swift’s amazing cleverness, it’s not magic.
My official account here Swift and computer programming related articles, as well as excellent translation of foreign articles, welcome to pay attention to ~