You’re about to define a MuseumObject
type for your MetMuseum app. In this demo, I’m using an Xcode playground and writing in Swift. If you’re following along, start up Xcode and open the module1-lesson2 playground in the starter folder.
It has a MuseumObject
struct that you’ll fill in and a showImage()
method stub.
There’s a commented-out initializer that you’ll need later and a MuseumObjectView
that showImage()
will use to display the art object’s image.
First, declare the properties your app will use:
let objectID: Int
let title: String
let objectURL: String
let primaryImageSmall: String
let creditLine: String
let isPublicDomain: Bool
Each instance of MuseumObject
will have values for these properties. As you develop your app, you might need to add more properties, but these are enough for now.
MuseumObject
now has all the properties that MuseumObjectView
uses, so go ahead and uncomment it.
A struct definition is just a template for objects. It doesn’t do anything on its own. You have to instantiate an object — initialize it with parameter values
or pass it in as a parameter —
and then your app can use the properties of each object,
and each object can call showImage()
.
So, instantiate two objects — one in the public domain and the other not in the public domain. Copy this code from the transcript below this video:
let object_pd =
MuseumObject(objectID: 436535,
title: "Wheat Field with Cypresses",
objectURL: "https://www.metmuseum.org/art/collection/search/436535",
primaryImageSmall: "https://images.metmuseum.org/CRDImages/ep/original/DT1567.jpg",
creditLine: "Purchase, The Annenberg Foundation Gift, 1993",
isPublicDomain: true)
let object =
MuseumObject(objectID: 13061,
title: "Cypress and Poppies",
objectURL: "https://www.metmuseum.org/art/collection/search/13061",
primaryImageSmall: "",
creditLine: "Gift of Iola Stetson Haverstick, 1982",
isPublicDomain: false)
You’ve created two instances of the MuseumObject
type, each representing different art objects. These instances are initialized with specific property values, which will be used when working with the data of these art objects.
With this set of properties, struct is a good choice for MuseumObject
: All the properties are constant, and you’re not currently planning to implement any method that changes any values. Swift even defines the init
method for structs, so you don’t have to. If you typed all the previous code instead of copy-pasting, you’ve seen this in practice. If not, try it now — start typing a new object creation:
let obj = MuseumObject(
You get a suggested auto-completion with an argument for each property, in the same order that you declared them.
Delete this line.
Now, suppose you decide to change MuseumObject
to a class. Do this now:
class MuseumObject {
...
}
The playground flags an error. If yours is thinking about it for too long, click run.
The ‘MuseumObject’ class has no initializers. For a class, you need to define the init()
method, so uncomment this code and add all your parameters:
init(objectID: Int,
title: String,
objectURL: String,
primaryImageSmall: String,
creditLine: String,
isPublicDomain: Bool) {
self.objectID = objectID
self.title = title
self.objectURL = objectURL
self.primaryImageSmall = primaryImageSmall
self.creditLine = creditLine
self.isPublicDomain = isPublicDomain
}
You use self
to differentiate the object’s properties from the parameter values. The arguments are in the same order as the property declarations, but that’s only so you don’t have to change the instantiations you already wrote.
Now, implement showImage()
:
if isPublicDomain {
PlaygroundPage.current.setLiveView(MuseumObjectView(object: self))
} else {
guard let url = URL(string: objectURL) else { return }
PlaygroundPage.current.liveView = SFSafariViewController(url: url)
}
In a playground, you can either set the live view to a view, or to a view controller.
MuseumObjectView
uses AsyncImage(url:)
to download and display primaryImagesmall
. SFSafariViewController
, which you get by importing SafariServices
, loads objectURL
into an embedded Safari browser.
Now finally, each object can call showImage()
. First, show the public domain image:
object_pd.showImage()
Run the playground. If nothing appears in your playground, stop it and run it again.
object_pd
is in the public domain, so showImage()
sets the playground’s live view to MuseumObjectView
.
Now, stop the playground, comment out the line object_pd.showImage()
and add this one:
object.showImage()
Run the playground again:
This time, object
is not in the public domain, so showImage()
loads its web page in a Safari browser.
Structs and classes have other differences:
- You can create subclasses of a class. This is inheritance, and it’s a topic in the next lesson.
- Structs are value types; classes are reference types.
-
If a struct method modifies a struct object, you must mark it as
mutating
.
To demonstrate the second and third differences, change title
to be variable:
var title: String
Also, change MuseumObject
back to a struct:
struct MuseumObject {
Then, scroll down and add these lines below the object_pd
instantiation:
var object2 = object_pd
object2.title = "Sunflowers"
Comment out object.showImage()
and uncomment object_pd.showImage()
, then run the playground.
Because MuseumObject
is a struct, MuseumObjectView
still displays the title Wheat Field with Cypresses because object2
is a copy of object_pd
: Changing object2.title
doesn’t affect object_pd.title
.
What happens when MuseumObject
is a class?
class MuseumObject {
Run the playground again.
Now that MuseumObject
is a class, MuseumObjectView
displays the title Sunflowers because object2
is the same object as object_pd
: Changing object2.title
changes object_pd.title
.
When MuseumObject
is a class, changing object2
’s title
works even if you declare it as a constant class object:
let object2 = object_pd
because the constant value is its location in memory, not its contents.
Run the playground again to confirm this.
Now, add this method to MuseumObject
:
func changeTitle(to newTitle: String) {
title = newTitle
}
And change object2.title = "Sunflowers"
to this:
object2.changeTitle(to: "Sunflowers")
When MuseumObject
is a class, this works the same as before.
Change MuseumObject
back to a struct.
you get this error on title = newTitle
:
Cannot assign to property: ‘self’ is immutable
Fix this by marking changeTitle
as mutating
:
mutating func changeTitle(to newTitle: String) {
The error goes away. Why do you need to tell the compiler this method mutates the struct object?
Well, when you declare object2
as a constant struct object — let object2 = object_pd
— then every property in that struct object is constant. You mark changeTitle(to:)
as mutating
so Swift knows that a constant struct object isn’t allowed to call it.
When you change let
to var
, the error goes away.
Now, change MuseumObject
to a class again. You get an error on mutating
: ‘mutating’ is not valid on instance methods in classes. So mutating
really sets structs apart from classes.
Change MuseumObject
back to a struct. You want object2
to be a copy of object_pd
for this next line of code.
Add this line and comment out the other two showImage()
lines:
object2 .showImage()
Run the playground:
And here’s the changed title for this copy of object_pd
.
Sometimes, you want to hide some of your object’s properties so they’re only visible within the struct or class. This prevents “outside” code from using or modifying these values. For example, set isPublicDomain
to be private
:
private let isPublicDomain: Bool
Making this property private
doesn’t prevent showImage()
from using it. But try typing this outside the struct:
if object.isPublicDomain { }
The first thing you’ll notice: isPublicDomain
doesn’t show up in the auto-completion suggestions. There’s also an error message (click the run button if you don’t see it):
‘isPublicDomain’ is inaccessible due to ‘private’ protection level
Delete the if ...
line and run the playground: There’s no problem passing a value to isPublicDomain
in the initializer.
Comment out the init
method and run the playground — there’s an error message!
‘MuseumObject’ initializer is inaccessible due to ‘private’ protection level
The auto-generated struct initializer doesn’t allow access to the private
property but if you write your own, it’s OK.
That ends this demo. Continue with the lesson for a summary.