We’ve already written code that dynamically generates a QR code based on the user’s name and email address, but with a little extra code we can also let the user share that QR code outside the app. This is another example of where ShareLink
comes in handy, although this time we'll place it inside a context menu.
Start by opening MeView.swift, and adding the contextMenu()
modifier to the QR code image, like this:
Image(uiImage: generateQRCode(from: "\(name)\n\(emailAddress)"))
.interpolation(.none)
.resizable()
.scaledToFit()
.frame(width: 200, height: 200)
.contextMenu {
let image = generateQRCode(from: "\(name)\n\(emailAddress)")
ShareLink(item: Image(uiImage: image), preview: SharePreview("My QR Code", image: Image(uiImage: image)))
}
As you can see, we need to convert the UIImage
of our QR code to a SwiftUI Image
view, which can then be handed to the system's share sheet.
We could save a little work by caching the generated QR code, however a more important side effect of that is that we wouldn’t have to pass in the name and email address each time – duplicating that data means if we change one copy in the future we need to change the other too.
To add this change, first add a new @State
property that will store the code we generate:
@State private var qrCode = UIImage()
Now modify generateQRCode()
so that it quietly stores the new code in our cache before sending it back:
if let cgimg = context.createCGImage(outputImage, from: outputImage.extent) {
qrCode = UIImage(cgImage: cgimg)
return qrCode
}
And now our context menu button can use the cached code:
.contextMenu {
ShareLink(item: Image(uiImage: qrCode), preview: SharePreview("My QR Code", image: Image(uiImage: qrCode)))
}
Before you try the context menu yourself, make sure you add the same project option we had for the Instafilter project – you need to add a permission request string to your project’s configuration options.
In case you’ve forgotten how to do that, here are the steps you need:
And now go ahead and run the app – you're likely to find things don't work quite as planned. In fact, back in Xcode you might see a purple warning line in the generateQRCode()
method: "Modifying state during view update, this will cause undefined behavior."
What this means is that our current view body calls generateQRCode()
to create the shareable image that we're attaching our context menu to, but calling that method now saves a value in the qrCode
property we marked with @State
, which in turn causes the view body to be reinvoked – it creates a loop, so SwiftUI bails out and flags a big warning for us.
To fix this we need to make the Image
view use our cached QR code, like this:
Image(uiImage: qrCode)
And then use a combination of onAppear()
and onChange()
to make sure the code is updated when the view is first shown, and also when either the name or email address changes.
This means creating a new method that updates our code in one place:
func updateCode() {
qrCode = generateQRCode(from: "\(name)\n\(emailAddress)")
}
Then attaching some extra modifiers below navigationTitle()
:
.onAppear(perform: updateCode)
.onChange(of: name, updateCode)
.onChange(of: emailAddress, updateCode)
Tip: Now that updateCode()
updates the value of qrCode
directly, we can go back to the earlier version of generateQRCode()
, which simply returns the new value:
if let cgimg = context.createCGImage(outputImage, from: outputImage.extent) {
return UIImage(cgImage: cgimg)
}
Now this step is done, and done properly – you should be able to run the app, switch to the Me tab, then long press the QR code to bring up your new context menu.
BUILD THE ULTIMATE PORTFOLIO APP Most Swift tutorials help you solve one specific problem, but in my Ultimate Portfolio App series I show you how to get all the best practices into a single app: architecture, testing, performance, accessibility, localization, project organization, and so much more, all while building a SwiftUI app that works on iOS, macOS and watchOS.
Sponsor Hacking with Swift and reach the world's largest Swift community!
Link copied to your pasteboard.