- Published on
Tags pt. 2
- Authors
- Name
- Misiel Rodriguez
- @misiel
Make sure to read part 1 if you haven't!
Going into Tags I had no clue what App Extensions were or how they worked.
In Android, I believe the similar counter part is Intents which is a data/messaging object that can be used to request/receive an action from another app component. Intents are commonly used to communicate between apps. For example, say I wanted to share an image from my photo roll into Instagram; the Instagram app will have a listener that is looking out for an Intent that contains an image and presents itself as an option to be selected in the share sheet.
In iOS, I'm not sure if this is true for every App Extenstion, but for Share Extenstions it is treated as a separate app. This is quite different than just making/manipulating an Intent object in your own app. This means, the share extension has its own Xcode target, its own code, its own debug terminal etc.
With this realization, I knew I was in for a bunch of new findings. Let's jump into what I learned!
How Should My User Interact w/ The Share Extension?
When my user goes to share an image and sees my app as an option, what should their experience be?
This is the first question I sought to answer before I jumped into code. Again, I looked at Pocket and Instapaper as examples. Instapaper just saves the image/URL to the app and presents an animation to let the user know it's been saved. Pocket does more of what I wanted which was to present the user the option to select a tag before saving it. To have some text to display in the main table view's rows on my home screen, I wanted to prompt the user to create a "title" for each image they save and then also be able to select from a list of pre-made tags (in the future I'll add an "Add Tag" button to the share extension so that users can just begin saving directly from there instead of having to interact with the app first).
Here's what my final design ended up looking like:
Image 1 Image 2 Image 3 Saving an image from Share Sheet
By default when you first create your share extension, it's able to appear for any data type that gets shared. This includes images, URLs, text, etc. This is probably not the functionality you want out the box as your app may not be able to handle everything or even intended to. To fix this, we'd have to go to our "Share App"'s Info.plist
and fix the NSExtensionActivationRule
. More of this is covered under the App Extension Rule documentation. As stated before, for now I only want my app to be available when a user selects 1 image to share so I added the following rule with a value of 1: NSExtensionActivationSupportsImageWithMaxCount
Sharing Data Between apps
Sweet! My app now appears as a share option at the right time. Now how do I display the right data?
I want to avoid duplicating code as much as possible so when I saw I couldn't directly call HomeViewModel.shared.saveTagWithItem()
from my Share Extension view controller I was like "what gives?" I didn't realize at first that code between app targets by default are not shared. Being that my HomeViewModel
was the source of truth for data throughout the app, I had to look into how can I get this data so that the user has the correct list of tags to select from in the share extension.
Turns out, all we simply had to do was just go into XCode's file inspector for our file and select what targets are able to use its code:
With this selected, both my Tags app and the share extension were able to communicate with the HomeViewModel
. From here, I was able to retrieve the tags to display in the share extension's table view and also directly save to the list of tags.
This next part isn't about sharing data between apps, but does involve sharing data so I figured I'd throw it in this section as well. Now in image 2 and 3
of the How Should My User Interact w/ The Share Extension? section, my share extension is basically 2 VCs that communicate with each other. In the main app, I have my different VCs communicate through the HomeViewModel, but I figured that was a bit overkill for the share extension. Although I love Combine and I could have added another observable into the view model, I went with a much simpler approach of using the delegate pattern. This has been something I've been avoiding in my ios dev journey but knew I would have to learn it eventually!
Image 3, shows the TagSelectionVC
which just displays a table view of the user's current saved tags. The user then taps a tag and is brought back to the first VC where the tag is now displayed as a selected option where the user is naming and saving their image. To have that tag be transferred between both VCs, I did the following:
// TagSelectionViewController.swift
@objc(TagSelectionViewControllerDelegate)
protocol TagSelectionViewControllerDelegate {
@objc optional func tagSelection(
_ sender: TagSelectionViewController,
selectedTag: String)
}
class TagSelectionViewController: UITableViewController {
var listOfTags: Array<TagModel>!
var delegate: TagSelectionViewControllerDelegate!
// Here whenever a user selects a tag (row), the delegate's tagSelection() method is called with the selected tag being sent
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if let theDelegate = delegate {
selectedTagTitle = listOfTags[indexPath.row].title
theDelegate.tagSelection?(self, selectedTag: selectedTagTitle)
}
}
}
// ShareViewController.swift
lazy var tagConfigurationItem: SLComposeSheetConfigurationItem = {
let item = SLComposeSheetConfigurationItem()
item!.title = "Tags"
item!.value = self.selectedTagName
item!.tapHandler = self.showTagSelection
return item!
}()
private func showTagSelection() {
let vc = TagSelectionViewController(style: UITableView.Style.plain)
vc.delegate = self
vc.listOfTags = Array(self.listOfTags)
pushConfigurationViewController(vc)
}
// The function is implemented here where ShareVC just extracts the selected tag info and does what it has do with it throughout the class
func tagSelection(_ sender: TagSelectionViewController, selectedTag: String) {
tagConfigurationItem.value = selectedTag
self.selectedTagName = selectedTag
popConfigurationViewController()
}
This simple delegate pattern makes it so that ShareViewController
has to conform to TagSelectionViewControllerDelegate
and sets itself as the delegate that TagSelectionViewController
is using in its class for the didSelectRowAt()
method. This pattern was new to me and a little archaic coming from the modern reactive frameworks for today, but sometimes the best solutions are the simplest and we shouldn't overengineer where we don't need to! I made the personal decision of using HomeViewModel
as the only share code between both targets and having the delegate pattern be a sort of "in-house" solution for the separate TagsShare
target instead of duplicating some Combine code.
Conclusion
And that's it for creating the Share extension and some of my learnings! For the full code, please visit the Projects tab of my site and view the code on github. I can't wait to tackle what's next and share more of what I learn. Not only has technical blogging served as a source of communication of my learnings, but it's also been a source of reference for me to see what I've been working on, what I've learned and when, and how am I progressing as an iOS dev.
Thanks for reading!