In this post, I’ll step through building a simple iOS app. Theoretically, we’ll end up with an app that displays photos one at a time, steps forward and backwards through the collection, and displays the Comment metadata field from the photo. This post will grow as my progress in building the app advances.
What does this have to do with indexes and indexing? Nothing more than the fact that we live in an app world and perhaps you’d be interested in seeing what’s involved in building a simple iOS app.
This post has the following sections:
- XCode to follow along
- Good book on building iOS apps
- Creating a new project
- Running in Simulator
- Adding a photo
- Buttons
- ContentView.swift code to this point
- Background image
- Metadata
- Final ContentView.swift code
iOS is the operating system that runs iPhones, the iPod Touch, iPads, and Apple Watches. This post will step through the process of building an app that runs on all those devices by using the SwiftUI framework for interfaces instead of the older UIKit framework. One implication of this approach is that this app requires iPhones to run iOS 13 or later. Other than that, you don’t need to understand what SwiftUI is.
To follow along with this post and build your own app, you need XCode, Apple’s free development environment for building iOS apps. You can download it from:
https://developer.apple.com/xcode/.
I see from that link that XCode 13 is available; I’m running XCode 12. I don’t know what happens when a version earlier than 12 is used, and I’m guessing that versions 13 and later will work fine. XCode is a bloody big file and I’m on satellite Internet, so someday I’ll upgrade…
I like books and a good one for learning how to build an iOS app is iOS 14 Programming Fundamentals with Swift: Swift, Xcode, and Cocoa Basics by Matt Neuburg, which covers Swift 5.3, XCode 12, and Apple’s Cocoa framework. For those of you more up-to-date than I am,iOS 15 Programming Fundamentals with Swift: Swift, Xcode, and Cocoa Basics by Matt Neuburg covers Swift 5.5 and XCode 13.
I don’t know about the Neuburg iOS 15 book, but his iOS 14 book does not discuss SwiftUI; he only works with the older UIKit. (Again with the SwiftUI versus UIKit!) These two handle building and displaying the app interfaces, known as views. Views are what the app looks like to the world. This is an article that discusses the benefits of the new SwiftUI versus the older UIKit framework for building and displaying views.
Launching XCode
Let’s get started: Launch XCode.

I’m guessing that you’re looking at an opening screen that has Create a new XCode project as a menu item; if so, click it. Otherwise, I don’t know what you’re seeing. Do you have a File menu? If so, under the File menu, choose New then choose Project.

Now you’ve got a screen that lets you select a template for the project. Select iOS and select App, as you see above, then click Next.

Now you have a chance to name this project. Type PhotoBook in the Product Name field. Spaces are okay in the Product Name, but don’t use any other punctuation. Ignore the Team field. The Organization Identifier field is probably blank and needs something put into it. The goal is to create a unique string identifying you or your organization. The convention is to start with com. and then follow that with a name, possibly with dot components that no one else is likely to use. This then creates the Bundle Identifier that you see below the Organization Identifier field. Every app submitted to the App Store needs a unique bundle identifier. This is explained the the Neuburg book or probably with a quick Google search.
Anyway, for Interface, select SwiftUI. For Life Cycle, select SwiftUI App. And for Language, select Swift.
Don’t put any checkmarks into the boxes at the bottom.
Click Next.

Now XCode is asking where it should save the project folder and files. Navigate to where you want it saved; you can move the project folder any time. Do not put a checkmark into the Git box unless you truly want to create a Git repository.
Click Create.
Ta da! You just created an iOS app! You can go ahead and run this. Either click on the right pointing Run triangle in the upper left area or go to the Product menu and select Run. With any luck, you will get a Build Succeeded message. The Simulator will be launched, showing up as a device with Hello, world! on its screen.
You’ve just created an iOS Hello World app.
Click back into XCode. You can stop the Simulator by clicking on the square Stop icon in the upper left area or go to the Product menu and select Stop. Go ahead and click back to the Simulator. It’s now just that device on its home screen. There is probably an icon for PhotoBook on the home screen. You can click it to run your Hello, world again. Now to quit, you’ll need to go back to the home screen, maybe by clicking a button at the bottom or whatever is appropriate for the device that is showing. [Edit: I just walked through this, stopping the program in XCode and then going back to the device in Simulator. There was no icon for PhotoBook on its home screen.]
Click back to XCode to see how to select which device is running in the Simulator. And that really should be plural devices, because you can have more than one device running in the Simulator at one time.

I’m hoping your XCode screen looks enough like mine that we can keep going here. Just to the right of the Play and Stop buttons near the top of the screen is an area that says PhotoBook then the name of a device. On my screenshot above, the device is an iPhone 8. Click on that area to drop down a list of available devices. Select a device and re-run the project, if you like.
When you need to open a project via the File menu, select Open, then drill down to the .xcodeproj file—the XCode project file—and click Open on that file.
Adding a Photo
All right then. Next we’re going to add a photo to this project and replace the Hello, world text with that image. Find a photo you want to add; all the major image formats are supported.
The photo I will be using is called DogsInPhotoBooth.png, a little 72 pixels/inch PNG file that is 504 pixels wide and 720 pixels high. I will be adding this photo to the XCode assets. In that XCode 12 image above, you’ll notice that in the line of icons below the Play and Stop buttons there is a little folder icon on the left end that is red because it’s selected. In the photo above, the tool tip says Show the Source Control Navigator. Click on the folder icon in your XCode, if it isn’t already selected. This shows the files in the project folder. Click on right-pointing triangles as needed to expose the contents of the project and of folders as needed. Click on the folder called Assets.xcassets. This is where we will drag our photo.

In a Finder window, find the photo you want to add. Drag that photo into the central white area of the XCode window. When you reach that area, it will show a border. Drop the photo.

Okay, now click on the ContentView.swift file in the left column.

We’re reaching the heart of it now.
See those lines that say
Text("Hello, world!")
.padding()
That’s what we’re going to replace, and this is what we’re going to replace it with:
Image(uiImage: UIImage(named: "DogsInPhotoBooth")!)
except that you should put in the name of your photo file where I have DogsInPhotoBooth.
Click the right-pointing Run button or go to the Product menu and select Run.
With any luck you’ll see Build Succeeded and then your chosen device will show up in the Simulator. Stop this Run then try selecting different devices to Run in. Notice how your photo looks in each. Some devices have smaller screens than others. Is your photo cut off in the smaller screens? Is it lost in a sea of white on the larger screens?
Let’s fix this by adding the following line below the Image line you added:
.resizable()

Again, try running with various devices in the Simulator. Any better? Or does the photo seem stretched on the bigger screens and scrunched on the smaller screens? Okay then, one more line to fix this aspect ratio problem. Add this below the resizable() you added:
.aspectRatio(contentMode: .fit)
An article on resize() and aspectRatio():
How to resize a SwiftUI Image and keep its aspect ratio
Apple documentation on SwiftUI Image

There we go. We have a one-photo photo book. Next: Buttons!
Buttons
So, this app will display photos. Above, when I changed the Hello, World! text to an image, I simply dragged a photo file into the Assets.xcassets folder. Amazingly, that simple action (plus changing the code to display an image instead of text) allowed us to display a photo. And, in fact, you could drag a whole gob of photos from wherever on your computer to the Assets.xcassets folder, slap on a couple buttons, and move back and forth between the photos, just with Assets.xcassets drag-and-drops.
BUT you won’t be able to access the Comment metadata in each of the photos.
Let’s plow headlong into that wall, shall we? Let’s drag a bunch of photos into the Assets.xcassets folder and slap on a couple buttons to move through them.
Find some photos to add to the photo book, dragging a handful into the Assets.xcassets folder from somewhere on your computer. Now let’s add a couple buttons for moving forward and backwards through this collection of photos.
Click on the ContentView.swift file in the lefthand project navigator pane to show its code. To the right of the code is the preview. Click the resume button to make the preview live. You will see your first photo showing in the currently selected Simulator device. Click the zoom out minus button at the bottom right of the preview pane to see the full display of the device in the preview pane.
In the ContentView.swift code pane, click to put the cursor at the start of line 15, which holds the inner closing curly bracket }.
Hint: Clicking to the right of any opening { or closing } curly bracket highlights its associated partner.
With the cursor at the start of line 15, hit the return key to put a blank line before the inner closing curly bracket. This will give us a place to drag the first button to.
In the upper right corner of the XCode window is a + button. Click it to bring up the Library window or go to the View menu and select Show Library. (To close the Library window just click somewhere else.) In the lefthand column of the Library window, find the Button object and drag it to the blank line you just created.
Notice that in the preview pane, you now have two devices showing: the original showing your first photo and a new one showing some button text in the middle of the device display. XCode is trying to tell you that you now have two views: the photo and the button. We need to bring those both together into one view. We’ll do this by stacking the image and the button.
Hold down the command key ⌘ and click on the word Image in the code. A menu pops up. Select Embed in VStack from the menu. A VStack is a vertical stack. The idea is that we want to vertically stack the photo and the button, but you can’t select the both to include both in the VStack. So right now, only the Image statement is within the VStack and we want the Button in there too. This is a good place for determining which curly bracket is associated with the VStack by clicking on the right side of closing curly brackets until you find the one associated with the VStack. Cut this line and then paste it after the Button statement so that both the Image and the Button statements are in the VStack
As soon as you accomplish this, you’ll see in your preview pane that we’re back to a single view, with tiny button text below the photo.
You’ll notice that the Button has two chunks of information that we need to supply: the action and the content—what the button does and how the button looks. Let’s start with the content. We’ll use a system image for the face of the button because this simplifies button interactions. There’s an app for selecting system images, available at Apple SF Symbols.
Click on the Content placeholder and paste in the following code:
Image(systemName: "arrowtriangle.left.circle.fill").foregroundColor(.black)
.font(.system(size:60))
.font(Font.title.weight(.bold))
Let’s place our second button before we worry about the action field. Command click on the Button and select from the popup menu Embed in HStack. This is the horizontal stack that will hold the two buttons. Copy the existing button code then find the closing curly bracket of the HStack and paste the button code before that curly closing bracket so that the two chunks of Button code are within the HStack. Then update the second button with the right arrow system image name by simply changing left to right in the systemName specification.
You should see two side by side arrows at the bottom of the preview screen next to the code screen. Let’s space those buttons by inserting three Spacer() commands. Put one on the line before each of the Button commands, then put the third on a line before the HStack closing curly bracket. See the following screen shot for the current code and preview screen.

So now we need to add actions to those buttons—to each button we’re going to add a little chunk of code that is executed when the button is clicked. We’ll follow a path of simplicity to move from photo to photo by giving the photos numeric names. Then we can just increment or decrement an index that can then be used to build the name of the photo be be shown.
First we’ll change the names that the project uses to refer to the photos, changing them to numeric names. Click on the Assets.xcassets folder. Its center pane lists the names of the photos you added. When we change those names, we won’t be affecting the original files that you dragged over. The Assets.xcassets is like a map, referring to the original files via whatever name you apply here in XCode.
Assets.xcassets is primarily a management system for the various versions of images like the splash screen or app icon that are needed for various devices. Right now we’re just using its mapping facility.
So click on each asset name in the center white pane and make each file name numeric, changing them to PB000, PB001, PB002, etc. The PB stands for photo book. There’s a Mac utility called Renamer for US$20 that makes it simple to rename a large group of files to sequential numeric names. It doesn’t matter to this code which file is which number, but if you won’t be shuffling your photo book photos then maybe it matters to you which photo is the first to show, which is the second, and so on. In that case, make the first one PB000, the second to show PB001, etc.
Okay, here’s the full code for ContentView.swift that includes the action code for the buttons:
import SwiftUI
let maxImageNumber = 6 // photos numbered 0 to 6
var imageNumber = 0
var numbersArray = Array(0...maxImageNumber).shuffled()
struct ContentView: View {
@State private var imageName = "PB000"
var body: some View {
VStack {
Image(uiImage: UIImage(named: self.imageName)!)
.resizable()
.aspectRatio(contentMode: .fit)
HStack {
Spacer()
Button(action: {
imageNumber -= 1
if (imageNumber < 0) {
imageNumber = maxImageNumber
}
self.imageName = "PB" + String(format: "%03d", numbersArray[imageNumber])
// print(self.imageName)
}) {
Image(systemName: "arrowtriangle.left.circle.fill").foregroundColor(.black)
.font(.system(size:60))
.font(Font.title.weight(.bold))
}
Spacer()
Button(action: {
imageNumber += 1
if (imageNumber > maxImageNumber) {
imageNumber = 0
}
self.imageName = "PB" + String(format: "%03d", numbersArray[imageNumber])
// print(self.imageName)
}) {
Image(systemName: "arrowtriangle.right.circle.fill").foregroundColor(.black)
.font(.system(size:60))
.font(Font.title.weight(.bold))
}
Spacer()
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Keep in mind that we’re referring to our photos by number. Starting near the top:
imageNumber is the variable that holds the number of the current photo to show.
maxImageNumber is the largest value that imageNumber can hold. Photos are numbered starting at zero, so if you have five photos, they’re numbered 0 to 4, which means that maxImageNumber equals 4. Notice that maxImageNumber is declared with let instead of var. This makes it a constant, that is, a value that never changes.
The numbersArray simply holds the photo numbers, so its members equal 0 to maxImageNumber. I shuffle this array so that the photos are shown in a random order. If you want your photos displayed in numeric order, remove the .shuffled() from its declaration.
The @State variable declaration for imageName means that when this variable’s value changes, the view will update. (There is also an aspect to @State about allowing us to modify a value within a struct.) It is recommended to make this a private variable, so sayeth the Google machine.
Notice the Button actions: They simply modify the imageNumber index to our current photo, then build the photo name based on the imageNumber. There’s a commented out print of the imageName to the console for a check that things are working properly. Remove the two slashes and space to uncomment the statement.
Run this code: You have a photo album that you can move through in either direction via the arrow buttons. Notice the icons at the top of the Simulator: Save Screen, Home, and Rotate. Be sure to try rotating the Simulator to see how the photos and buttons react.
Background Image
Something that would add a bit of pizzaz to this photo album app is an image that sits behind the photo, so that if the photo doesn’t fill the screen, this background image shows up instead of just white.
I went to cgbookcase and grabbed a texture. Mine is called Wood_01_2K_Base_Color.jpg.
Drag and drop your background into Assets.xcassets.
To accomplish this background/foreground image thing we’ll need a different kind of stack in our code: a ZStack that layers objects back to front. In ContentView.swift, change the VStack to a ZStack. Then paste the following code above the Image call:
Image("Wood_01_2K_Base_Color")
.resizable()
.ignoresSafeArea()
Replace my Wood_01_2K_Base_Color with the name of your background file. We’ve seen the .resizable() parameter. The .ignoresSafeArea() puts the background out closer to the edges of the display.
This is the code with a ZStack of a background image and the photo plus buttons:
import SwiftUI
let maxImageNumber = 6 // photos numbered 0 to 6
var imageNumber = 0
var numbersArray = Array(0...maxImageNumber).shuffled()
struct ContentView: View {
@State private var imageName = "PB000"
var body: some View {
ZStack {
Image("Wood_01_2K_Base_Color")
.resizable()
.ignoresSafeArea()
VStack{
Image(uiImage: UIImage(named: self.imageName)!)
.resizable()
.aspectRatio(contentMode: .fit)
HStack {
Spacer()
Button(action: {
imageNumber -= 1
if (imageNumber < 0) {
imageNumber = maxImageNumber
}
self.imageName = "PB" + String(format: "%03d", numbersArray[imageNumber])
print(self.imageName)
}) {
Image(systemName: "arrowtriangle.left.circle.fill").foregroundColor(.black)
.font(.system(size:60))
.font(Font.title.weight(.bold))
}
Spacer()
Button(action: {
imageNumber += 1
if (imageNumber > maxImageNumber) {
imageNumber = 0
}
self.imageName = "PB" + String(format: "%03d", numbersArray[imageNumber])
print(self.imageName)
}) {
Image(systemName: "arrowtriangle.right.circle.fill").foregroundColor(.black)
.font(.system(size:60))
.font(Font.title.weight(.bold))
}
Spacer()
}
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}

Metadata
Photos have metadata stored with their image data, behind the scenes. Metadata is data about data; in this case, it’s data about the photo such as its x-y dimensions, the date the photo was taken, and exposure information.
There are two types of photo metadata:
- EXIF data (EXIF = exchangeable image file format) is automatically recorded by your camera. This type of metadata contains information that allows different devices to understand your media file, like resolution, date and time, and the make and model of the device it was captured on.
- IPTC data (IPTC = International Press Telecommunications Council) can be entered by you, the creator of the file. It’s where you can add contextual information, like the Comment field we want to read. When people talk about adding metadata to an image or video, they’re talking about IPTC data.
So we’ll need to add the Comment metadata information to our photos and then read it in our app. [Edit: Using the Comment field in the IPTC metadata turned out to be a dead end; instead we’ll use the UserComment field in the EXIF metadata. But this section that follows is helpful for moving the photo resources from Assets.xcassets to a Photos folder in the project folder.]
On a Mac you can access the Comment metadata field for any file via the Finder’s Get Info command (⌘-i). Scroll through the info window until you find the Comments field, then type a comment. When you close the info window, the comment text will be saved.
So what about the photos you dragged and dropped on the Assets.xcassets pane? Keeping in mind that Assets.xcassets is just a map to the originals of those files, you’ll need to go to the original files on your hard drive to fill in the Comment field.
But don’t go to a lot of trouble here. We’re about to crash into a wall; we won’t make it to the point of reading metadata yet, so you can just pretend to have added comments.
Here’s the ContentView.swift code that reads metadata (or tries to) from Assets.xcassets entries for files in random locations on the computer:
//
// ContentView.swift
// PhotoBook
//
// Created by Sue Klefstad on 10/16/21.
//
import SwiftUI
let maxImageNumber = 6 // photos numbered 0 to 6
var imageNumber = 0
var numbersArray = Array(0...maxImageNumber).shuffled()
struct ContentView: View {
@State private var imageName = "PB000"
var body: some View {
ZStack {
Image("Wood_01_2K_Base_Color")
.resizable()
.ignoresSafeArea()
Image(uiImage: UIImage(named: self.imageName)!)
.resizable()
.aspectRatio(contentMode: .fit)
VStack {
Text(captionText(whichImage: imageNumber))
.foregroundColor(.white)
.font(.system(size: 18, weight: .bold))
.padding(.all, 5.0)
.frame(maxWidth: .infinity, alignment: .center)
.background(Color.init(hue: 0.2722, saturation: 0.89, brightness: 0.29, opacity: 1.0))
Spacer()
HStack{
Spacer()
Button(action: {
imageNumber -= 1
if (imageNumber < 0) {
imageNumber = maxImageNumber
}
self.imageName = "WP" + String(format: "%03d", numbersArray[imageNumber])
// print(self.imageName)
}) {
Image(systemName: "arrowtriangle.left.circle.fill").foregroundColor(.black)
.font(.system(size:60))
.font(Font.title.weight(.bold))
}
Spacer()
Button(action: {
imageNumber += 1
if (imageNumber > maxImageNumber) {
imageNumber = 0
}
self.imageName = "WP" + String(format: "%03d", numbersArray[imageNumber])
// print(self.imageName)
}) {
Image(systemName: "arrowtriangle.right.circle.fill")
.foregroundColor(.black)
.font(.system(size: 60))
.font(Font.title.weight(.bold))
}
Spacer()
}
}
}
}
}
func captionText(whichImage: Int) -> String {
var returnString = ""
let nameOfImage = "WP" + String(format: "%03d", numbersArray[whichImage])
let fileURL = Bundle.main.url(forResource: nameOfImage, withExtension: "png")
let opts : [AnyHashable:Any] = [kCGImageSourceShouldCache : false]
let sourceOfImage = CGImageSourceCreateWithURL(fileURL! as CFURL, opts as CFDictionary)!
let properties = CGImageSourceCopyPropertiesAtIndex(sourceOfImage, 0, opts as CFDictionary) as! [AnyHashable:Any]
print(properties)
returnString = "This will be the Comment field"
return returnString
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
The first difference from our previous code is that the photo Image statement has been pulled out of the VStack, so that now the outer ZStack consists of the background image, then the photo, then a VStack of our new metadata Text code and the Button HStack.
The Text code calls the new captionText function, which returns the Comment field string. Or tries to. It crashes with this error:
Fatal error: Unexpectedly found nil while unwrapping an Optional value: file PhotoBook/ContentView.swift, line 74
2022-01-03 12:50:49.345031-0600 PhotoBook[7670:656124] Fatal error: Unexpectedly found nil while unwrapping an Optional value: file PhotoBook/ContentView.swift, line 74
This is line 74:
let sourceOfImage = CGImageSourceCreateWithURL(fileURL! as CFURL, opts as CFDictionary)!
This is the line that builds a reference to the photo, and it’s not finding the photo. And no wonder: The fileURL parameter specifying the photo is built with a function called Bundle.main.url() while the photo itself is not in the main bundle; it’s in some other location on the computer, not in the app bundle itself.
We need to get the photo files into the project bundle.
In Finder, create a folder called Photos in the project folder, at the same level as the project itself:

Put your photos into this Photos folder. Now you can take the time to put comment metadata into these files so that we have something to read.
The only changes you need to make to the code are in the captionText function, line 71:
let fileURL = Bundle.main.url(forResource: nameOfImage, withExtension: "png", subdirectory: "Photos")
Add that comma and subdirectory: “Photos” to tell the app where to look for photos.
Then in the Image() callout in line 23:
Image(uiImage: UIImage(named: "Photos/\(imageName)")!)
After the named: parameter label add the string that references the Photos folder path before the imageName variable: “Photos/\(imageName)”.
Notice that we don’t see the Photos folder in our project navigator pane. Go to the XCode File menu and select Add Files to “PhotoBook,” bringing up this window:

Click on the dropdown PhotoBook (referring to the project) and move up a level by selecting the next PhotoBook on the list, which refers to the folder:

Select Photos and click Add. Notice that Photos is now included in the project navigator file list:
Now run that script. The console will reflect the print() statements in the code, showing values of available metadata. BUT where’s our Comment field? We’re only accessing the EXIF metadata, not the IPTC that holds the Comment field.
I tried using MDItemCopyAttribute(), which is an Apple core services function, to read the IPTC metadata, but I kept getting a scope error on that function.
I can access EXIF metadata with the above code, and EXIF has a UserComment field. So now I’m going to find an EXIF metadata editor to try adding that field to files, then see if I can read it.
I grabbed a copy of ExifTool by Phil Harvey, a widely used utility for reading and writing file metadata. At a Terminal prompt, change to the Photos directory holding your photo resources, then type exiftool followed by a file name to list all the metadata found in the file (including the Comment field that I’d tried to use but was unable to read with Swift). At a Terminal prompt, use this command to write a comment to the UserComment field:
exiftool -UserComment='This is the comment string that I want in this UserComment' PB001.jpg
Replace PB000.jpg with the name of your file.
To read this UserComment field, this is our current captionText function:
func captionText(whichImage: Int) -> String {
let nameOfImage = "wp" + String(format: "%03d", numbersArray[whichImage])
let fileURL = Bundle.main.url(forResource: nameOfImage, withExtension: "jpg", subdirectory: "Photos")
let opts : [AnyHashable:Any] = [kCGImageSourceShouldCache : false]
let sourceOfImage = CGImageSourceCreateWithURL(fileURL! as CFURL, opts as CFDictionary)!
let properties = CGImageSourceCopyPropertiesAtIndex(sourceOfImage, 0, opts as CFDictionary) as! [AnyHashable:Any]
print("nameOfImage: \(nameOfImage)")
print(properties)
for (key, value) in CGImageSourceCopyPropertiesAtIndex(sourceOfImage, 0, nil) as! [String : Any] {
print("\(key): \(value)")
}
let propertyDict = properties["{Exif}"]
// print("Here is propertyDict:")
// for (key, value) in propertyDict as! [String : Any] {
// print("\(key): \(value)")
// }
var returnString = "returnString"
for (key, value) in propertyDict as! [String : Any] {
if key == "UserComment" {
returnString = value as? String ?? "returnString2"
}
}
return returnString
}
The first things to note in this code are the two for loops that simply print out the dictionary keys and their associated values; one is commented out with forward slashes and the other actively prints to the console. These were key for me in understanding the dictionary structure of the EXIF metadata. I tried using the Apple constant kCGImagePropertyExifUserComment to read the UserComment field but I was not able to retrieve any content with it. And since the for loop worked for displaying the structure of the dictionary, I used it to find the UserComment key, then grabbed its value. Success at last!
So at this point the app pretty much works. I need to randomize the first photo and have it display the correct UserComment string, and I need to handle photos that have no UserComment text. It would be nice to have a title screen and an app icon too.
Here’s the final ContentView.swift code with those items fixed. In addition, the text box only shows up when there’s something in the UserComment field via a test of captionString.
//
//
// ContentView.swift
// PhotoBook
//
// Created by Sue Klefstad on 10/16/21.
//
import SwiftUI
let maxImageNumber = 94 // photos numbered 0 to 94
var imageNumber = 0
let imageNameExtension = ".jpg"
var numbersArray = Array(0...maxImageNumber).shuffled()
struct ContentView: View {
@State private var imageName = "wp" + String(format: "%03d", numbersArray[imageNumber])
var body: some View {
let captionString = captionText(nameOfImage: imageName)
ZStack {
Image("Wood_01_2K_Base_Color")
.resizable()
.ignoresSafeArea()
Image(uiImage: UIImage(named: "Photos/\(imageName)\(imageNameExtension)")!)
.resizable()
.aspectRatio(contentMode: .fit)
VStack {
if captionString != " " {
Text(captionString)
.foregroundColor(.yellow)
.font(.system(size: 18, weight: .bold))
.padding(.all, 5.0)
.frame(maxWidth: .infinity, alignment: .center)
.background(Color.init(hue: 0.249, saturation: 0.89, brightness: 0.34, opacity: 1.0))
}
Spacer()
HStack{
Spacer()
Button(action: {
imageNumber -= 1
if (imageNumber < 0) {
imageNumber = maxImageNumber
}
self.imageName = "wp" + String(format: "%03d", numbersArray[imageNumber])
// print(self.imageName)
}) {
Image(systemName: "arrowtriangle.left.circle.fill").foregroundColor(.black)
.font(.system(size:60))
.font(Font.title.weight(.bold))
}
Spacer()
Button(action: {
imageNumber += 1
if (imageNumber > maxImageNumber) {
imageNumber = 0
}
self.imageName = "wp" + String(format: "%03d", numbersArray[imageNumber])
// print(self.imageName)
}) {
Image(systemName: "arrowtriangle.right.circle.fill")
.foregroundColor(.black)
.font(.system(size: 60))
.font(Font.title.weight(.bold))
}
Spacer()
}
}
}
}
}
func captionText(nameOfImage: String) -> String {
let fileURL = Bundle.main.url(forResource: nameOfImage, withExtension: "jpg", subdirectory: "Photos")
let opts : [AnyHashable:Any] = [kCGImageSourceShouldCache : false]
let sourceOfImage = CGImageSourceCreateWithURL(fileURL! as CFURL, opts as CFDictionary)!
let properties = CGImageSourceCopyPropertiesAtIndex(sourceOfImage, 0, opts as CFDictionary) as! [AnyHashable:Any]
// print("nameOfImage: \(nameOfImage)")
// print(properties)
// for (key, value) in CGImageSourceCopyPropertiesAtIndex(sourceOfImage, 0, nil) as! [String : Any] {
// print("\(key): \(value)")
// }
let propertyDict = properties["{Exif}"]
// print("Here is propertyDict:")
// for (key, value) in propertyDict as! [String : Any] {
// print("\(key): \(value)")
// }
var returnString = " "
if (propertyDict != nil) {
for (key, value) in propertyDict as! [String : Any] {
if key == "UserComment" {
returnString = value as? String ?? " "
}
}
}
else {
returnString = " "
}
return returnString
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}