AppStoreCatalog implements a GUI for displaying multiple App Store products in
a scrolling grid. By tapping on a product, the user is taken to a page where
they can see more info about the app, and optionally purchase it using
SKStoreProductViewController. In a narrow space like an iPhone, the grid is
shown as a single column. In a wider space such as an iPad, it will expand to
more columns.
This project is a SwiftUI rewrite/replacement of an older package called MultiProductViewer.
This functionality was originally (in iOS 5 and iOS 6) implemented by
SKStoreProductViewController itself. You could just pass it your company
identifier, and it would present all of your company's products in a list, and
let the user drill down to get more info and purchase apps. That functionality
stopped working in iOS 7 due to a bug; It was later restored in iOS 8, but by
that time I had already created my own library, the now defunct
MultiProductViewer, which was more flexible and useful than Apple's own.
AppStoreCatalog is a re-implementation which does even more.
Instead of just imitating the old behavior, AppStoreCatalog improves upon it by showing a larger icon, and giving the user the ability to include a small piece of text for each app. It also lets you pick exactly which apps you want to show by supplying multiple lists of your own. This can be used to promote not only your own company's apps, but also groups of apps belonging to clients or friends, or just any apps you like for any reason :)
In the simplistic way of doing things that SKStoreProductViewController
includes, you just provide a company identifier, and StoreKit does the rest.
Since that's not really feasible from the client side using public APIs,
AppStoreCatalog requires you to provide four pieces of data for each app you
want to show: A name, an App Store identifier, an icon URL, and a string
containing a brief description.
The configuration can be easily specified using JSON data, as described later in this document.
Here's the iPhone layout, presented with .sheet():

Here's the iPad layout, presented with .fullScreenCover():

Start off by importing this SPM package into your project in the usual way, using this URL.
Then, create a JSON file with the following structure, containing information for the products you want to display, as shown below. The images referenced as URLs will be sized to fit a screen size of a 100-point square. Most screens in use on modern devices have a pixel density of at least two pixels per point, so it's recommended to use images that are roughly 200x200.
The JSON can be contained within your project or hosted on a server, it's up to you.
{ "productGroups" : [
{ "title" : "Awesome Games",
"products" : [
{
"name" : "FlippyBit",
"details" : "Party like it's 1979! Flippy Bit makes one of 2014's biggest mobile hits look like it's on an old Atari.",
"identifier" : "825459863",
"imageURL" : "https://rebisoft.com/appicons/flippybit512.png"
},
{
"name" : "Pits of Death",
"details" : "Find your way through a mysterious dungeon. Find the arrow and defeat the dragon before you fall into a pit!",
"identifier" : "989109750",
"imageURL" : "https://rebisoft.com/appicons/PitsOfDeathIcon128.png"
},
]
},
{ "title" : "Other Apps",
"products" : [
{
"name" : "Goldy",
"details" : "Goldy is a web browser for iPhone, iPad, and iPod touch that offers one simple feature: Privacy.",
"identifier" : "417317449",
"imageURL" : "https://rebisoft.com/_Media/screen_shot_2011-08-19_at_med.png"
},
]
},
]}
In your application, import this framework into the view where you want to display it:
import AppStoreCatalog
Prepare the catalog data somewhere:
// In production code, please do better than just "try!" as shown in this
// snippet!
let data = try! Data(contentsOf: Bundle.main.url(forResource: "ExampleCatalog",
withExtension: "json")!)
// ... or ...
let url = URL(string: "https://sample.com/all_products.json")!
let (data, response) = try await URLSession.shared.data(from: url)
let catalog = try! AppStoreCatalog(data: data)
When you want to display the view, do something like this. The closure is optional; If it's provided, it will be called any time StoreKit fails to show a product page.
// assuming the existence of `@State var presentingSheet`...
.sheet(isPresented: $presentingSheet) {
AppStoreCatalogView(catalog: catalog) { identifier, error in
print("AppStoreCatalogView failed to show App Store view for product \(identifier):\n\(error.localizedDescription)")
}
}
It's also possible to choose whether or not AppStoreCatalogView will include a
close button. This is not typically necessary for presentation within a Sheet,
but is definitely useful for a full screen presentation. See the included
example application for full usage details and examples.
Tapping on a displayed product will attempt to display StoreKit's purchase page
for that product. If the product's identifier is invalid or cannot be found on
the app store for any reason, a message will be displayed to the user. You can
also give AppStoreCatalogView a closure to be called whenever this happens, in
case you want to log this in some way.
Due to StoreKit limitations, every attempt to display a purchase page fails when running in the Simulator, so you'll see the failure message for every product. Run on a device to see it actually work.
AppStoreCatalog builds and runs on macOS, but it doesn't seem to function quite correctly as of yet, probably due to the maintainer's lack of experience with the macOS incarnation of SwiftUI. This may be fixed in the future, but for now, consider the macOS support here "experimental". Pull requests to improve this, or any other aspect of this package, are welcome!
