### README (README.md)
# Delta
> Delta is an all-in-one classic video game emulator for non-jailbroken iOS devices.
[](https://swift.org/)
[](https://www.gnu.org/licenses/agpl-3.0)
[](http://makeapullrequest.com)
Delta is an iOS application that allows you to emulate and play video games for several classic video game systems, including Game Boy Advance, Nintendo 64, and Nintendo DS. Delta is the spiritual successor to [GBA4iOS](http://www.gba4iosapp.com) — a Game Boy Advance emulator for iOS devices [Paul Thorsen](https://twitter.com/pau1thor) and I made while in high school together — rebuilt from the ground up with modern iOS features and support for more systems.

Mario and Pokémon are properties of Nintendo Co., Ltd. and are not associated with Delta or AltStore LLC.
## Supported Systems
- Nintendo Entertainment System (NES)
- Super Nintendo Entertainment System (SNES)
- Nintendo 64 (N64)
- Game Boy / Game Boy Color (GBC)
- Game Boy Advance (GBA)
- Nintendo DS (DS)
- Sega Genesis / Mega Drive (GEN) **(in beta)**
## Features
- Accurate, full speed emulation thanks to mature underlying emulator cores.
- NES: [Nestopia](https://github.com/0ldsk00l/nestopia)
- SNES: [Snes9x](https://github.com/snes9xgit/snes9x)
- N64: [mupen64plus](https://github.com/mupen64plus/mupen64plus-core)
- GBC: [Gambatte](https://github.com/sinamas/gambatte)
- GBA: [visualboyadvance-m](https://github.com/visualboyadvance-m/visualboyadvance-m)
- DS: [melonDS](https://github.com/Arisotura/melonDS)
- GEN: [Genesis Plus GX](https://github.com/ekeeke/Genesis-Plus-GX)
- Beautiful, native UI.
- Browse and play your favorite games with a UI designed from the ground up for iOS.
- Automatically displays appropriate box art for imported games.
- Change a game’s artwork to anything you want, or select from the built-in game artwork database.
- Controller Support
- Supports PS4, PS5, Xbox One S, Xbox Series X, and MFi game controllers.
- Supports bluetooth (and wired) keyboards, as well as the Apple Smart Keyboard.
- Completely customize button mappings on a per-system, per-controller basis.
- Map buttons to special “Quick Save”, “Quick Load,” and “Fast Forward” actions.
- Custom Controller Skins
- Beautiful built-in controller skins for all systems.
- Import controller skins made by others, or even make your own to share with the world!
- Save States
- Save and load save states for any game from the pause menu.
- Lock save states to prevent them from being accidentally overwritten.
- Automatically makes backup save states to ensure you never lose your progress.
- Support for “Quick Saves,” save states that can be quickly saved/loaded with a single button press (requires external controller).
- Fast Forwarding
- Speed through slower parts of games by running the game much faster than normal.
- Easily enable or disable from the pause menu, or optionally with a mapped button on an external controller.
- Delta Sync
- Sync your games, game saves, save states, cheats, controller skins, and controller mappings between devices.
- View version histories of everything you sync and optionally restore them to earlier versions.
- Supports both Google Drive and Dropbox.
- Hold Button
- Choose buttons for Delta to hold down on your behalf, freeing up your thumbs to press other buttons instead.
- Perfect for games that typically require one button be held down constantly (ex: run button in Mario games, or the A button in Mario Kart).
- 3D/Haptic Touch
- Use 3D or Haptic Touch to “peek” at games, save states, and cheat codes.
- App icon shortcuts allow quick access to your most recently played games, or optionally customize the shortcuts to always include certain games.
- Cheat Codes
- NES
- Game Genie
- SNES:
- Game Genie
- Pro Action Replay
- N64
- GameShark
- GBC
- Game Genie
- GameShark
- GBA
- Action Replay
- Code Breaker
- GameShark
- DS
- Action Replay
- Gyroscope support **(WarioWare: Twisted! only)**
- Microphone support **(DS only)**
## Installation
Delta was originally developed under the impression Apple would allow it into the App Store. Unfortunately Apple later changed their minds, leaving me no choice but to find a new way to distribute Delta. Long story short, this led me to create [AltStore](https://github.com/rileytestut/AltStore), which now serves as the official way to install Delta onto your device.
To install Delta with AltStore:
1. Download AltServer for Mac or PC from https://altstore.io
2. Connect your iOS device to your computer via lightning cable (or USB-C for iPads).
3. [Follow these instructions](https://altstore.io/faq/) to install AltStore onto your device with AltServer.
2. Open AltStore on your device, then navigate to the "Browse" tab.
3. Find Delta, then press the `FREE` button to start installing the app.
Once you've installed Delta with AltStore, **you'll need to refresh it at least once every 7 days to prevent it from expiring** and requiring a re-installation. AltStore will periodically attempt to refresh your apps in the background when on the same WiFi as AltServer, but you can also manually refresh apps by pressing "Refresh All" in AltStore. AltStore will also let you know whenever a new update is released, allowing you to update Delta directly within AltStore.
Alternatively, you are welcome to download the compiled `.ipa`'s from [Releases](https://github.com/rileytestut/Delta/releases) and sideload them using whatever sideloading method you prefer, but you will not receive automatic updates and will have to manually update Delta by re-sideloading each new version.
## Project Overview
Delta was designed from the beginning to be modular, and for that reason each "Delta Core" has its own GitHub repo and is added as a submodule to the main Delta project. Additionally, Delta uses two of my own private frameworks I use to share common functionality between my apps: Roxas and Harmony.
[**Delta**](https://github.com/rileytestut/Delta)
Delta is just a regular, sandboxed iOS application. The Delta app repo (aka this one) contains all the code specific to the Delta app itself, such as storyboards, app-specific view controllers, database logic, etc.
[**DeltaCore**](https://github.com/rileytestut/DeltaCore)
DeltaCore serves as the “middle-man” between the high-level app code and the specific emulation cores. By working with this framework, you have access to all the core Delta features, such as emulation, controller skins, save states, cheat codes, etc. Other potential emulator apps will use this framework extensively.
[**Roxas**](https://github.com/rileytestut/Roxas)
Roxas is my own framework used across my projects, developed to simplify a variety of common tasks used in iOS development.
[**Harmony**](https://github.com/rileytestut/Harmony)
Harmony is my personal syncing framework designed to sync Core Data databases. Harmony listens for changes to an app's persistent store, then syncs any changes with a remote file service (such as Google Drive or Dropbox).
**Delta Cores**
Each system in Delta is implemented as its own "Delta Core", which serves as a standard emulation API Delta can understand regardless of the underlying core. For the most part, you don't interact directly with specific Delta Cores, but rather indirectly through `DeltaCore`.
- [NESDeltaCore](https://github.com/rileytestut/NESDeltaCore)
- [SNESDeltaCore](https://github.com/rileytestut/SNESDeltaCore)
- [N64DeltaCore](https://github.com/rileytestut/N64DeltaCore)
- [GBCDeltaCore](https://github.com/rileytestut/GBCDeltaCore)
- [GBADeltaCore](https://github.com/rileytestut/GBADeltaCore)
- [MelonDSDeltaCore](https://github.com/rileytestut/MelonDSDeltaCore)
- [GPGXDeltaCore](https://github.com/rileytestut/GPGXDeltaCore)
## Minimum Project Requirements
- Xcode 15
- Swift 5.9
- iOS 14.0
## Compilation Instructions
> Delta uses Git LFS to manage large files, so first make sure you have [installed Git LFS](https://git-lfs.github.com).
1. Clone this repository by running the following command in Terminal*
```bash
$ git clone https://github.com/rileytestut/Delta.git
```
2. Update Git submodules
```bash
$ cd Delta
$ git submodule update --init --recursive
```
3. Open `Systems/Systems.xcworkspace` and select the "Systems" project in the project navigator (a.k.a. the left sidebar).
4. Select "Systems" under `Targets`, then click the `Signing & Capabilities` tab.
5. Change `Team` from "Yvette Testut" to your own account.
6. Close `Systems/Systems.xcworkspace`, then open `Delta.xcworkspace`.
6. Repeat steps 4 & 5 with the "Delta" target.
7. Change Delta's `Bundle Identifier` to something unique, such as by appending your GitHub username (ex: `com.rileytestut.Delta.MyGitHubUsername`).
8. Build + run app! 🎉
## Licensing
The Delta codebase is distributed under the **AGPLv3 license**.
## Contact Us
* Email: delta@altstore.io
* Mastodon: [@mattaltgear@mstdn.social](https://mstdn.social/mattaltgear)
* Threads: [@mattaltgear](https://www.threads.com/@mattaltgear)
* Bluesky: [@mattaltgear.bsky.social](https://bsky.app/profile/mattaltgear.bsky.social)
---
### CONTRIBUTING (CONTRIBUTING.md)
Thank you for your interest in contributing to Delta! Delta is an open-source project that aims to provide a seamless and enjoyable experience for playing classic video games on iOS devices. We welcome contributions from anyone who shares our vision and passion for emulation.
# How to Contribute
We are currently accepting these types of contributions for Delta:
- Experimental Features
- Bug fixes
All features added by third-party contributors will be considered *experimental* at first, and are disabled by default unless specifically enabled by the user from the Experimental Features section in Delta's settings. Experimental Features are only available in the beta version of Delta, but once a feature has been sufficiently tested we may choose to "graduate" it into an official Delta feature, at which point it will become available to all users.
For more specific instructions regarding contributing features to Delta, see [ExperimentalFeatures.md](Docs/ExperimentalFeatures.md).
## Contribution Guidelines
**Check out our project board first!**
We have categorized issues and pull requests to highlight areas where help is most needed. You are welcome to contribute something new, but keep in mind that we may focus on more highly-requested items first.
**Keep changes small**
The smaller a pull request is the more likely it will be merged. Make sure your PRs are limited to just the relevant changes and nothing else. Avoid pure formatting changes to code that has not been modified otherwise.
**Make sure contributions are 100% complete**
We can't accept unfinished pull requests, so please make sure your contribution is ready to be merged as-is.
**Extensively test your changes**
Make sure your changes work as expected on different devices and iOS versions, and doesn't result in additional bugs.
**Commit only relevant changes of changed files**
Some files, such as Storyboards and Xcode projects, often contain changes that are unrelated to your specific change. **You should almost never commit an entire Storyboard or Xcode project as-is.**
**Not all contributions will be accepted**
To maximize your chances of getting your pull request accepted, make sure to read all the guidelines carefully and familiarize yourself with the project structure, coding style, and best practices.
## Code Style and Conventions
**Please make sure your code follows these guidelines and is free of compiler warnings before submitting a pull request.**
* All contributions should be pure Swift, no C++ or Objective-C (unless absolutely necessary)
* Follow the [Swift API Design Guidelines](https://swift.org/documentation/api-design-guidelines/) for writing clean, consistent, and expressive Swift code
* Use Allman (a.k.a. brackets on new line) brace style (exceptions: closures and single-line statements)
* Use 4 spaces for indentation. Do not use tabs or mix tabs and spaces.
* Use whitespace liberally to make code easier to read
* Use descriptive variable names
* Lean on the side of commenting (more comments make it more likely to be approved)
* Prefer structs over classes. Classes should be used only if you need a reference type, or to interoperate with ObjC.
When editing existing code, please preserve the original formatting of the code as much as possible (e.g. brace style). Do not make unnecessary changes to whitespace, indentation, line breaks or comments. This helps keep the diffs clean and easy to review.
For example, when editing code that uses the Allman brace style (a.k.a. brackets on new line), make sure any `if`/`else` statements you write also place the bracket on a new line.
## Submitting Pull Requests
All pull requests must adhere to the PR template, filling out each section as appropriate. **Only PRs that follow this template will be accepted.**
Once you've submitted your PR, we will review it and provide feedback as soon as we can.
---
### Docs/ExperimentalFeatures (Docs/ExperimentalFeatures.md)
# Overview
Features contributed to Delta by third-party developers are considered "experimental", and are available for testing in the beta version of Delta. Once a feature has been sufficiently tested, we may choose to "graduate" it into an official Delta feature, at which point it will become available to all users.
Every Experimental Feature can be thought of as a binary flag: it's either enabled or disabled. When disabled, a feature should have no impact on the rest of the app. This allows us to accept contributions freely without affecting the overall stability of Delta.
If a feature requires more state than binary On/Off, you can define as many "options" as needed. Options store additional data required by your feature's implementation. Options can be "hidden" (the default), but can also be automatically exposed in Delta's settings where they can be changed directly by users.
# Guidelines For Experimental Features
Keep the following in mind when contributing new Experimental Features:
* When a feature is disabled, it should have *no* noticeable impact on the rest of the app.
* Avoid touching the core emulation logic.
* Isolate your changes as much as possible from the rest of the app, preferably in separate files. We recommend using Swift extensions to add functionality to existing types (e.g. `GameViewController+ExperimentalFastForward.swift`)
* If your change requires modifying `DeltaCore` or specific cores, make sure the naming and "shape" of any public API follows existing conventions as much as possible. In general, Experimental Features that require modifying cores have a higher bar for acceptance.
# Adding a New Experimental Feature
1. Open `Delta/Experimental Features/ExperimentalFeatures.swift`
2. Add a new property to the `ExperimentalFeatures` struct, annotated with the `@Feature` property wrapper. You do not need to define the property's type as it will be inferred by @Feature.
> The property name (e.g. `variableFastForward`) will be used internally as the `UserDefaults` key for persisting data.
3. Pass in the name of your feature to `@Feature`'s initializer, and optionally a description.
Once you've defined your feature, you can check whether or not it's enabled at runtime via `ExperimentalFeatures.shared.[feature].isEnabled`.
Here's a complete implementation for a new Experimental Feature called "Show Status Bar":
```swift
// ExperimentalFeatures.swift
struct ExperimentalFeatures
{
@Feature(name: "Show Status Bar", description: "Show the Status Bar during gameplay.")
var showStatusBar
}
// GameViewController+ShowStatusBar.swift
extension GameViewController
{
override var prefersStatusBarHidden: Bool {
return !ExperimentalFeatures.shared.showStatusBar.isEnabled
}
}
```
# Adding Options to a Feature
Some features require additional configuration beyond being enabled or disabled. These are referred to as "options", and you can define as many options for a feature as necessary. Whenever an option's value changes, it is automatically persisted to `UserDefaults`.
The option’s underlying type must conform to `OptionValue`. Automatic conformance is provided for all standard property list types, but if you want to use your own type, it must either:
* Conform to `RawRepresentable`, where its raw type is a valid property list type (e.g. enums with string backing), or
* Conform to `Codable`
To declare a feature with options:
1. Create a new Swift file in `Delta/Experimental Features/Features` and name it after your feature (e.g. `VariableFastForward.swift`)
2. Define a new struct named `[FeatureName]Options` (e.g. `VariableFastForwardOptions`)
3. For each configurable value, define a new property on your Options struct with `@Options` property wrapper.
> The property name (e.g. `speed`) will be combined with the feature's property name and used internally as the `UserDefaults` key for persisting data.
4. **If the option represents a non-optional value, you must provide an initial value**. This will be used as the default value if the option has not been configured by user.
5. Follow the above instructions for declaring a feature, but pass in an instance of your `Options` struct to the `options:` parameter in the `@Feature` initializer.
Heres's an example feature "Game Gestures" that shows an instruction alert the first time it is enabled. It uses an `@Option` to store whether the alert has already been shown or not.
```swift
// GameGestures.swift
struct GameGesturesOptions
{
@Option // No parameters = "Hidden" option
var didShowGestureAlert: Bool = false
}
// ExperimentalFeatures.swift
struct ExperimentalFeatures
{
@Feature(name: "Game Gestures", options: GameGesturesOptions())
var gameGestures
}
```
# User-Facing Options
By default, Options are hidden, which means their values can only be changed programmatically.
However, options can also be user-facing, which we generally recommend. User-facing options will automatically appear in the `Experimental Features` section of Delta's settings, where they can be configured manually by users. To define a user-facing option, pass in a value for `name` in the `@Option` initializer, and optionally a `description`.
Because user-facing options are meant to be seen by users, the underlying type must conform to `LocalizedOptionValue`. This protocol refines `OptionValue` with two new methods:
* `localizedDescription`, used to display the value in a human-readable manner.
* `localizedNilDescription`, used to represent the `nil` value in a human-readable manner. The default implementation returns "None".
Heres's an example feature "Game Screenshots" that defines options so users can choose whether to save screenshots to the Photo Library, the Files app, or both. Unlike the above "Game Gestures" example, "Save to Files" and "Save to Photos" will be exposed in the `Experimental Features` section of Delta's settings, where they will appear as switches that the user can toggle.
```swift
// GameScreenshots.swift
struct GameScreenshotsOptions
{
@Option(name: "Save to Files", description: "Save the screenshot to the app's directory in Files.")
var saveToFiles: Bool = true
@Option(name: "Save to Photos", description: "Save the screenshot to the Photo Library.")
var saveToPhotos: Bool = false
}
// ExperimentalFeatures.swift
struct ExperimentalFeatures
{
@Feature(name: "Game Screenshots", options: GameScreenshotsOptions())
var gameScreenshots
}
```
## Types of User-Facing Options
Delta supports 3 types of user-facing options:
* Bool options
* "Picker" options (e.g. array of values)
* Custom options (any other type)
Here's an example feature "VariableFastForward" that uses all 3 types of user-facing options:
```swift
// VariableFastForward.swift
enum FastForwardSpeed: Double, CaseIterable, CustomStringConvertible
{
case x2 = 2
case x3 = 3
case x4 = 4
case x8 = 8
var description: String {
return "\(self.rawValue)x"
}
}
extension FastForwardSpeed: LocalizedOptionValue
{
var localizedDescription: Text {
Text(self.description)
}
static var localizedNilDescription: Text {
Text("Maximum")
}
}
struct VariableFastForwardOptions
{
// Bool option (will appear as inline UISwitch)
@Option(name: "Allow Unrestricted Speeds", description: "Allow speeds that exceed the maximum speed of a system.")
var allowUnrestrictedSpeeds: Bool = false
// "Custom" option (will appear as full-screen view with text field)
@Option(name: "Maximum Speed", description: "Change the maximum fast forward speed across all systems.", detailView: {
TextField("", value: $0, formatter: NumberFormatter())
.keyboardType(.numberPad)
})
var maxSpeed: Int?
// "Picker" options (will appear as standard UIMenu picker)
@Option(name: "Nintendo Entertainment System", values: FastForwardSpeed.allCases)
var nes: FastForwardSpeed?
@Option(name: "Super Nintendo", values: FastForwardSpeed.allCases)
var snes: FastForwardSpeed?
@Option(name: "Nintendo 64", values: FastForwardSpeed.allCases)
var n64: FastForwardSpeed?
// Etc.
}
```
Each type of user-facing option has slightly different requirements, which are detailed below:
### Bool Option
If the property annotated with @Option is a `Bool`, there is nothing more you need to do. Delta will automatically show a toggle on the feature's detail page that can be used by user to update this value.
Example:
```swift
@Option(name: "showStatusBar")
var showStatusBar: Bool = false
```
### "Picker" Option
If there is a known, finite number of supported values for your option, you can pass a `Collection` of them to the `values:` parameter in the `@Option` initializer. Delta will automatically show an inline picker on the feature's detail page that will allow users to select from the preset values.
>
> If the option is an optional type, the picker will automatically include a `nil` option in the picker. You can customize the name used to represent the `nil` option by overriding `LocalizedOptionValue.localizedNilDescription`.
>
Example:
```swift
enum Planet: String { mercury, venus, earth, ... }
extension Planet: LocalizedOptionValue
{
static var localizedNilDescription: Text {
Text("No Favorite Planet")
}
}
@Option(name: "Current Planet", values: Planet.allCases)
var currentPlanet: Planet = .earth
@Option(name: "Favorite Planet", values: Planet.allCases)
var favoritePlanet: Planet? // Optional, so Delta will include `nil` option in picker, displayed as "No Favorite Planet".
```
### "Custom" Option
Every user-facing `@Option` requires some UI in order to be configured by users in Delta's settings. If your option is not one of the ones listed above, you'll need to provide your own `SwiftUI` view. This can be as simple as just an inline `TextField` (e.g. for `String` options), or a completely custom full screen SwiftUI view with access to the entire SwiftUI API (e.g. a full color picker for `Color` options).
To provide your own SwiftUI view, pass in a closure that returns your custom `View` to the `detailView:` parameter in `@Option`'s initializer. The closure passes in a `Binding` to the option's underlying value, which can then be passed into any SwiftUI control that takes a `Binding` (e.g. `Picker`, `Toggle`, `TextField`, etc.) to automatically update the option's value. However this is just a convenience, and you are welcome to update your `@Option` value from your custom view however works best.
By default, custom options will present their SwiftUI views full-screen when tapped. However, if you want your custom view to appear inline (like Bool and "picker" options), you can apply the `displayInline()` modifier to your view.
Example:
```swift
// Inline text field
@Option(name: "Custom Nickname", detailView: {
TextField("", text: $0)
.displayInline()
})
var nickname: String
```
## Using Features
All Experimental Features can be selectively enabled or disabled by the user in the "Experimental Features" section of Delta's settings. To check whether a feature is enabled at runtime, call `ExperimentalFeatures.shared.[feature].isEnabled`. **Your feature implementation must respect this flag and have no noticeable effect on the rest of the app when disabled.**
You can access individual feature options via `ExperimentalFeatures.shared.[feature].[option]`. To access `@Option`-specific properties, such as its `settingsKey`, use the `@Option`'s projected value by prepending the property with a `$` (e.g. `ExperimentalFeatures.shared.[feature].$[option].settingsName`).
Delta will automatically post a `Settings.didChangeNotification` notification whenever a feature is enabled, disabled, or one of its options changes. The `userInfo` dictionary will contain either `Feature.settingsName` or `Option.settingsName` under the `SettingsUserInfoKey.name` key, as well as the new value under the `SettingsUserInfoKey.value` key.
Example:
```swift
// Handler for Settings.didChangeNotification
func settingsDidChange(_ notification: Notification)
{
guard let name = notification.userInfo?[SettingsUserInfoKey.name] as? Settings.Name else { return }
switch name
{
case ExperimentalFeatures.shared.showStatusBar.settingsKey:
// Update status bar
self.setNeedsStatusBarAppearanceUpdate()
case ExperimentalFeatures.shared.customTintColor.settingsKey: fallthrough
case ExperimentalFeatures.shared.customTintColor.$color.settingsKey:
// Update tint color if feature itself is enabled/disabled OR tint color changes.
self.updateTintColor()
default: break
}
}
```
---
### Docs/Pull Request Template (Docs/pull_request_template.md)
Mark the type contribution you are making:
- [ ] Experimental feature (new functionality that can be selectively enabled/disabled)
- [ ] Bug fix (non-breaking change which fixes an issue)
# Description
Summary of your changes, including:
* Why is this change necessary?
* Why did you decide on this solution?
# Testing
List all iOS versions and devices you've tested this change on.
**Example Configurations**:
- iPhone 14, iOS 16.3.1
- iPhone X, iOS 15.7.4
# Checklist
**General (All PRs)**
- [ ] My code follows the style guidelines of this project
- [ ] I have performed a self-review of my code
- [ ] I have commented my code, particularly in hard-to-understand areas
- [ ] My changes generate no new warnings
- [ ] I've tested my changes with different device + OS version configurations
**Experimental Feature-specific**
- [ ] Added property to `ExperimentalFeatures` struct annotated with `@Feature`
- [ ] Uses `@Option`'s to persist all feature-related data
- [ ] Locked *all* behavior changes behind `ExperimentalFeatures.shared.[feature].isEnabled` runtime check
- [ ] Isolates changes to separate files as much as possible (e.g. via Swift extensions)
---
### Pods/Exported Symbols (Pods/exported_symbols.txt)
_ConfigGetSharedDataFilepath_Boogie
_ConfigGetUserConfigPath
_ConfigGetUserCachePath
_ConfigGetUserDataPath
_ConfigOpenSection
_ConfigDeleteSection
_ConfigSaveSection
_ConfigSaveFile
_ConfigSetDefaultInt
_ConfigSetDefaultFloat
_ConfigSetDefaultBool
_ConfigSetDefaultString
_ConfigGetParamInt
_ConfigGetParamFloat
_ConfigGetParamBool
_ConfigGetParamString
_ConfigExternalGetParameter
_ConfigExternalOpen
_ConfigExternalClose
_CoreGetAPIVersions
_ConfigGetParameter
_ConfigSetParameter
_CoreDoCommand
_RSP_PluginStartup
_RSP_PluginShutdown
_RSP_PluginGetVersion
_RSP_RomClosed
_DoRspCycles
_InitiateRSP
_Video_PluginStartup
_Video_PluginShutdown
_Video_PluginGetVersion
_Video_RomOpen
_Video_RomClosed
_VidExt_Init
_VidExt_Quit
_VidExt_ListFullscreenModes
_VidExt_SetVideoMode
_VidExt_SetCaption
_VidExt_ToggleFullScreen
_VidExt_ResizeWindow
_VidExt_GL_GetProcAddress
_VidExt_GL_SetAttribute
_VidExt_GL_GetAttribute
_VidExt_GL_SwapBuffers
_ChangeWindow
_InitiateGFX
_MoveScreen
_ProcessDList
_ProcessRDPList
_ShowCFB
_UpdateScreen
_ViStatusChanged
_ViWidthChanged
_ReadScreen2
_SetRenderingCallback
_FBRead
_FBWrite
_FBGetFrameBufferInfo
_ResizeVideoOutput
---
### Pods/Alamofire/README (Pods/Alamofire/README.md)
[](https://travis-ci.org/Alamofire/Alamofire)
[](https://img.shields.io/cocoapods/v/Alamofire.svg)
[](https://github.com/Carthage/Carthage)
[](https://alamofire.github.io/Alamofire)
[](https://twitter.com/AlamofireSF)
[](https://gitter.im/Alamofire/Alamofire?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
Alamofire is an HTTP networking library written in Swift.
- [Features](#features)
- [Component Libraries](#component-libraries)
- [Requirements](#requirements)
- [Migration Guides](#migration-guides)
- [Communication](#communication)
- [Installation](#installation)
- [Usage](https://github.com/Alamofire/Alamofire/blob/master/Documentation/Usage.md)
- **Intro -** [Making a Request](https://github.com/Alamofire/Alamofire/blob/master/Documentation/Usage.md#making-a-request), [Response Handling](https://github.com/Alamofire/Alamofire/blob/master/Documentation/Usage.md#response-handling), [Response Validation](https://github.com/Alamofire/Alamofire/blob/master/Documentation/Usage.md#response-validation), [Response Caching](https://github.com/Alamofire/Alamofire/blob/master/Documentation/Usage.md#response-caching)
- **HTTP -** [HTTP Methods](https://github.com/Alamofire/Alamofire/blob/master/Documentation/Usage.md#http-methods), [Parameter Encoding](https://github.com/Alamofire/Alamofire/blob/master/Documentation/Usage.md#parameter-encoding), [HTTP Headers](https://github.com/Alamofire/Alamofire/blob/master/Documentation/Usage.md#http-headers), [Authentication](https://github.com/Alamofire/Alamofire/blob/master/Documentation/Usage.md#authentication)
- **Large Data -** [Downloading Data to a File](https://github.com/Alamofire/Alamofire/blob/master/Documentation/Usage.md#downloading-data-to-a-file), [Uploading Data to a Server](https://github.com/Alamofire/Alamofire/blob/master/Documentation/Usage.md#uploading-data-to-a-server)
- **Tools -** [Statistical Metrics](https://github.com/Alamofire/Alamofire/blob/master/Documentation/Usage.md#statistical-metrics), [cURL Command Output](https://github.com/Alamofire/Alamofire/blob/master/Documentation/Usage.md#curl-command-output)
- [Advanced Usage](https://github.com/Alamofire/Alamofire/blob/master/Documentation/AdvancedUsage.md)
- **URL Session -** [Session Manager](https://github.com/Alamofire/Alamofire/blob/master/Documentation/AdvancedUsage.md#session-manager), [Session Delegate](https://github.com/Alamofire/Alamofire/blob/master/Documentation/AdvancedUsage.md#session-delegate), [Request](https://github.com/Alamofire/Alamofire/blob/master/Documentation/AdvancedUsage.md#request)
- **Routing -** [Routing Requests](https://github.com/Alamofire/Alamofire/blob/master/Documentation/AdvancedUsage.md#routing-requests), [Adapting and Retrying Requests](https://github.com/Alamofire/Alamofire/blob/master/Documentation/AdvancedUsage.md#adapting-and-retrying-requests)
- **Model Objects -** [Custom Response Serialization](https://github.com/Alamofire/Alamofire/blob/master/Documentation/AdvancedUsage.md#custom-response-serialization)
- **Connection -** [Security](https://github.com/Alamofire/Alamofire/blob/master/Documentation/AdvancedUsage.md#security), [Network Reachability](https://github.com/Alamofire/Alamofire/blob/master/Documentation/AdvancedUsage.md#network-reachability)
- [Open Radars](#open-radars)
- [FAQ](#faq)
- [Credits](#credits)
- [Donations](#donations)
- [License](#license)
## Features
- [x] Chainable Request / Response Methods
- [x] URL / JSON / plist Parameter Encoding
- [x] Upload File / Data / Stream / MultipartFormData
- [x] Download File using Request or Resume Data
- [x] Authentication with URLCredential
- [x] HTTP Response Validation
- [x] Upload and Download Progress Closures with Progress
- [x] cURL Command Output
- [x] Dynamically Adapt and Retry Requests
- [x] TLS Certificate and Public Key Pinning
- [x] Network Reachability
- [x] Comprehensive Unit and Integration Test Coverage
- [x] [Complete Documentation](https://alamofire.github.io/Alamofire)
## Component Libraries
In order to keep Alamofire focused specifically on core networking implementations, additional component libraries have been created by the [Alamofire Software Foundation](https://github.com/Alamofire/Foundation) to bring additional functionality to the Alamofire ecosystem.
- [AlamofireImage](https://github.com/Alamofire/AlamofireImage) - An image library including image response serializers, `UIImage` and `UIImageView` extensions, custom image filters, an auto-purging in-memory cache and a priority-based image downloading system.
- [AlamofireNetworkActivityIndicator](https://github.com/Alamofire/AlamofireNetworkActivityIndicator) - Controls the visibility of the network activity indicator on iOS using Alamofire. It contains configurable delay timers to help mitigate flicker and can support `URLSession` instances not managed by Alamofire.
## Requirements
- iOS 8.0+ / macOS 10.10+ / tvOS 9.0+ / watchOS 2.0+
- Xcode 8.3+
- Swift 3.1+
## Migration Guides
- [Alamofire 4.0 Migration Guide](https://github.com/Alamofire/Alamofire/blob/master/Documentation/Alamofire%204.0%20Migration%20Guide.md)
- [Alamofire 3.0 Migration Guide](https://github.com/Alamofire/Alamofire/blob/master/Documentation/Alamofire%203.0%20Migration%20Guide.md)
- [Alamofire 2.0 Migration Guide](https://github.com/Alamofire/Alamofire/blob/master/Documentation/Alamofire%202.0%20Migration%20Guide.md)
## Communication
- If you **need help**, use [Stack Overflow](https://stackoverflow.com/questions/tagged/alamofire). (Tag 'alamofire')
- If you'd like to **ask a general question**, use [Stack Overflow](https://stackoverflow.com/questions/tagged/alamofire).
- If you **found a bug**, open an issue.
- If you **have a feature request**, open an issue.
- If you **want to contribute**, submit a pull request.
## Installation
### CocoaPods
[CocoaPods](https://cocoapods.org) is a dependency manager for Cocoa projects. You can install it with the following command:
```bash
$ gem install cocoapods
```
> CocoaPods 1.1+ is required to build Alamofire 4.0+.
To integrate Alamofire into your Xcode project using CocoaPods, specify it in your `Podfile`:
```ruby
source 'https://github.com/CocoaPods/Specs.git'
platform :ios, '10.0'
use_frameworks!
target '' do
pod 'Alamofire', '~> 4.7'
end
```
Then, run the following command:
```bash
$ pod install
```
### Carthage
[Carthage](https://github.com/Carthage/Carthage) is a decentralized dependency manager that builds your dependencies and provides you with binary frameworks.
You can install Carthage with [Homebrew](https://brew.sh/) using the following command:
```bash
$ brew update
$ brew install carthage
```
To integrate Alamofire into your Xcode project using Carthage, specify it in your `Cartfile`:
```ogdl
github "Alamofire/Alamofire" ~> 4.7
```
Run `carthage update` to build the framework and drag the built `Alamofire.framework` into your Xcode project.
### Swift Package Manager
The [Swift Package Manager](https://swift.org/package-manager/) is a tool for automating the distribution of Swift code and is integrated into the `swift` compiler. It is in early development, but Alamofire does support its use on supported platforms.
Once you have your Swift package set up, adding Alamofire as a dependency is as easy as adding it to the `dependencies` value of your `Package.swift`.
#### Swift 3
```swift
dependencies: [
.Package(url: "https://github.com/Alamofire/Alamofire.git", majorVersion: 4)
]
```
#### Swift 4
```swift
dependencies: [
.package(url: "https://github.com/Alamofire/Alamofire.git", from: "4.0.0")
]
```
### Manually
If you prefer not to use any of the aforementioned dependency managers, you can integrate Alamofire into your project manually.
#### Embedded Framework
- Open up Terminal, `cd` into your top-level project directory, and run the following command "if" your project is not initialized as a git repository:
```bash
$ git init
```
- Add Alamofire as a git [submodule](https://git-scm.com/docs/git-submodule) by running the following command:
```bash
$ git submodule add https://github.com/Alamofire/Alamofire.git
```
- Open the new `Alamofire` folder, and drag the `Alamofire.xcodeproj` into the Project Navigator of your application's Xcode project.
> It should appear nested underneath your application's blue project icon. Whether it is above or below all the other Xcode groups does not matter.
- Select the `Alamofire.xcodeproj` in the Project Navigator and verify the deployment target matches that of your application target.
- Next, select your application project in the Project Navigator (blue project icon) to navigate to the target configuration window and select the application target under the "Targets" heading in the sidebar.
- In the tab bar at the top of that window, open the "General" panel.
- Click on the `+` button under the "Embedded Binaries" section.
- You will see two different `Alamofire.xcodeproj` folders each with two different versions of the `Alamofire.framework` nested inside a `Products` folder.
> It does not matter which `Products` folder you choose from, but it does matter whether you choose the top or bottom `Alamofire.framework`.
- Select the top `Alamofire.framework` for iOS and the bottom one for OS X.
> You can verify which one you selected by inspecting the build log for your project. The build target for `Alamofire` will be listed as either `Alamofire iOS`, `Alamofire macOS`, `Alamofire tvOS` or `Alamofire watchOS`.
- And that's it!
> The `Alamofire.framework` is automagically added as a target dependency, linked framework and embedded framework in a copy files build phase which is all you need to build on the simulator and a device.
## Open Radars
The following radars have some effect on the current implementation of Alamofire.
- [`rdar://21349340`](http://www.openradar.me/radar?id=5517037090635776) - Compiler throwing warning due to toll-free bridging issue in test case
- `rdar://26870455` - Background URL Session Configurations do not work in the simulator
- `rdar://26849668` - Some URLProtocol APIs do not properly handle `URLRequest`
- [`rdar://36082113`](http://openradar.appspot.com/radar?id=4942308441063424) - `URLSessionTaskMetrics` failing to link on watchOS 3.0+
## Resolved Radars
The following radars have been resolved over time after being filed against the Alamofire project.
- [`rdar://26761490`](http://www.openradar.me/radar?id=5010235949318144) - Swift string interpolation causing memory leak with common usage (Resolved on 9/1/17 in Xcode 9 beta 6).
## FAQ
### What's the origin of the name Alamofire?
Alamofire is named after the [Alamo Fire flower](https://aggie-horticulture.tamu.edu/wildseed/alamofire.html), a hybrid variant of the Bluebonnet, the official state flower of Texas.
### What logic belongs in a Router vs. a Request Adapter?
Simple, static data such as paths, parameters and common headers belong in the `Router`. Dynamic data such as an `Authorization` header whose value can changed based on an authentication system belongs in a `RequestAdapter`.
The reason the dynamic data MUST be placed into the `RequestAdapter` is to support retry operations. When a `Request` is retried, the original request is not rebuilt meaning the `Router` will not be called again. The `RequestAdapter` is called again allowing the dynamic data to be updated on the original request before retrying the `Request`.
## Credits
Alamofire is owned and maintained by the [Alamofire Software Foundation](http://alamofire.org). You can follow them on Twitter at [@AlamofireSF](https://twitter.com/AlamofireSF) for project updates and releases.
### Security Disclosure
If you believe you have identified a security vulnerability with Alamofire, you should report it as soon as possible via email to security@alamofire.org. Please do not post it to a public issue tracker.
## Donations
The [ASF](https://github.com/Alamofire/Foundation#members) is looking to raise money to officially stay registered as a federal non-profit organization.
Registering will allow us members to gain some legal protections and also allow us to put donations to use, tax free.
Donating to the ASF will enable us to:
- Pay our yearly legal fees to keep the non-profit in good status
- Pay for our mail servers to help us stay on top of all questions and security issues
- Potentially fund test servers to make it easier for us to test the edge cases
- Potentially fund developers to work on one of our projects full-time
The community adoption of the ASF libraries has been amazing.
We are greatly humbled by your enthusiasm around the projects, and want to continue to do everything we can to move the needle forward.
With your continued support, the ASF will be able to improve its reach and also provide better legal safety for the core members.
If you use any of our libraries for work, see if your employers would be interested in donating.
Any amount you can donate today to help us reach our goal would be greatly appreciated.
[](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=W34WPEE74APJQ)
## License
Alamofire is released under the MIT license. [See LICENSE](https://github.com/Alamofire/Alamofire/blob/master/LICENSE) for details.
---
### Pods/AppAuth/README (Pods/AppAuth/README.md)
[](https://github.com/openid/AppAuth-iOS/actions/workflows/tests.yml)
[](https://codecov.io/gh/openid/AppAuth-iOS)
[](https://github.com/Carthage/Carthage)
[](https://swift.org/package-manager)
[](https://cocoapods.org/pods/AppAuth)
[](https://github.com/openid/AppAuth-iOS/blob/master/LICENSE)
[](https://cocoapods.org/pods/AppAuth)
[](https://developer.apple.com/documentation/xcode/creating_a_mac_version_of_your_ipad_app)
AppAuth for iOS and macOS, and tvOS is a client SDK for communicating with
[OAuth 2.0](https://tools.ietf.org/html/rfc6749) and
[OpenID Connect](http://openid.net/specs/openid-connect-core-1_0.html) providers.
It strives to
directly map the requests and responses of those specifications, while following
the idiomatic style of the implementation language. In addition to mapping the
raw protocol flows, convenience methods are available to assist with common
tasks like performing an action with fresh tokens.
It follows the best practices set out in
[RFC 8252 - OAuth 2.0 for Native Apps](https://tools.ietf.org/html/rfc8252)
including using `SFAuthenticationSession` and `SFSafariViewController` on iOS
for the auth request. `UIWebView` and `WKWebView` are explicitly *not*
supported due to the security and usability reasons explained in
[Section 8.12 of RFC 8252](https://tools.ietf.org/html/rfc8252#section-8.12).
It also supports the [PKCE](https://tools.ietf.org/html/rfc7636) extension to
OAuth, which was created to secure authorization codes in public clients when
custom URI scheme redirects are used. The library is friendly to other
extensions (standard or otherwise), with the ability to handle additional params
in all protocol requests and responses.
For tvOS, AppAuth implements [OAuth 2.0 Device Authorization Grant
](https://tools.ietf.org/html/rfc8628) to allow for tvOS sign-ins through a secondary device.
## Specification
### iOS
#### Supported Versions
AppAuth supports iOS 7 and above.
iOS 9+ uses the in-app browser tab pattern
(via `SFSafariViewController`), and falls back to the system browser (mobile
Safari) on earlier versions.
#### Authorization Server Requirements
Both Custom URI Schemes (all supported versions of iOS) and Universal Links
(iOS 9+) can be used with the library.
In general, AppAuth can work with any authorization server that supports
native apps, as documented in [RFC 8252](https://tools.ietf.org/html/rfc8252),
either through custom URI scheme redirects, or universal links.
Authorization servers that assume all clients are web-based, or require clients to maintain
confidentiality of the client secrets may not work well.
### macOS
#### Supported Versions
AppAuth supports macOS (OS X) 10.9 and above.
#### Authorization Server Requirements
AppAuth for macOS supports both custom schemes; a loopback HTTP redirects
via a small embedded server.
In general, AppAuth can work with any authorization server that supports
native apps, as documented in [RFC 8252](https://tools.ietf.org/html/rfc8252);
either through custom URI schemes, or loopback HTTP redirects.
Authorization servers that assume all clients are web-based, or require clients to maintain
confidentiality of the client secrets may not work well.
### tvOS
#### Supported Versions
AppAuth supports tvOS 9.0 and above. Please note that while it is possible to run the standard AppAuth library on tvOS, the documentation below describes implementing [OAuth 2.0 Device Authorization Grant](https://tools.ietf.org/html/rfc8628) (AppAuthTV).
#### Authorization Server Requirements
AppAuthTV is designed for servers that support the device authorization flow as documented in [RFC 8628](https://tools.ietf.org/html/rfc8628).
## Try
Want to try out AppAuth? Just run:
pod try AppAuth
Follow the instructions in [Examples/README.md](Examples/README.md) to configure
with your own OAuth client (you need to update three configuration points with your
client info to try the demo).
## Setup
AppAuth supports four options for dependency management.
### CocoaPods
With [CocoaPods](https://guides.cocoapods.org/using/getting-started.html),
add the following line to your `Podfile`:
pod 'AppAuth'
Then, run `pod install`.
**tvOS:** Use the `TV` subspec:
pod 'AppAuth/TV'
### Swift Package Manager
With [Swift Package Manager](https://swift.org/package-manager),
add the following `dependency` to your `Package.swift`:
```swift
dependencies: [
.package(url: "https://github.com/openid/AppAuth-iOS.git", .upToNextMajor(from: "1.3.0"))
]
```
**tvOS:** Use the `AppAuthTV` target.
### Carthage
With [Carthage](https://github.com/Carthage/Carthage), add the following
line to your `Cartfile`:
github "openid/AppAuth-iOS" "master"
Then, run `carthage bootstrap`.
**tvOS:** Use the `AppAuthTV` framework.
### Static Library
You can also use AppAuth as a static library. This requires linking the library
and your project, and including the headers. Here is a suggested configuration:
1. Create an Xcode Workspace.
2. Add `AppAuth.xcodeproj` to your Workspace.
3. Include libAppAuth as a linked library for your target (in the "General ->
Linked Framework and Libraries" section of your target).
4. Add `AppAuth-iOS/Source` to your search paths of your target ("Build Settings ->
"Header Search Paths").
*Note: There is no static library for AppAuthTV.*
## Auth Flow
AppAuth supports both manual interaction with the authorization server
where you need to perform your own token exchanges, as well as convenience
methods that perform some of this logic for you. This example uses the
convenience method, which returns either an `OIDAuthState` object, or an error.
`OIDAuthState` is a class that keeps track of the authorization and token
requests and responses, and provides a convenience method to call an API with
fresh tokens. This is the only object that you need to serialize to retain the
authorization state of the session.
### Configuration
You can configure AppAuth by specifying the endpoints directly:
Objective-C
```objc
NSURL *authorizationEndpoint =
[NSURL URLWithString:@"https://accounts.google.com/o/oauth2/v2/auth"];
NSURL *tokenEndpoint =
[NSURL URLWithString:@"https://www.googleapis.com/oauth2/v4/token"];
OIDServiceConfiguration *configuration =
[[OIDServiceConfiguration alloc]
initWithAuthorizationEndpoint:authorizationEndpoint
tokenEndpoint:tokenEndpoint];
// perform the auth request...
```
Swift
```swift
let authorizationEndpoint = URL(string: "https://accounts.google.com/o/oauth2/v2/auth")!
let tokenEndpoint = URL(string: "https://www.googleapis.com/oauth2/v4/token")!
let configuration = OIDServiceConfiguration(authorizationEndpoint: authorizationEndpoint,
tokenEndpoint: tokenEndpoint)
// perform the auth request...
```
**tvOS**
Objective-C
```objc
NSURL *deviceAuthorizationEndpoint =
[NSURL URLWithString:@"https://oauth2.googleapis.com/device/code"];
NSURL *tokenEndpoint =
[NSURL URLWithString:@"https://www.googleapis.com/oauth2/v4/token"];
OIDTVServiceConfiguration *configuration =
[[OIDTVServiceConfiguration alloc]
initWithDeviceAuthorizationEndpoint:deviceAuthorizationEndpoint
tokenEndpoint:tokenEndpoint];
// perform the auth request...
```
Or through discovery:
Objective-C
```objc
NSURL *issuer = [NSURL URLWithString:@"https://accounts.google.com"];
[OIDAuthorizationService discoverServiceConfigurationForIssuer:issuer
completion:^(OIDServiceConfiguration *_Nullable configuration,
NSError *_Nullable error) {
if (!configuration) {
NSLog(@"Error retrieving discovery document: %@",
[error localizedDescription]);
return;
}
// perform the auth request...
}];
```
Swift
```swift
let issuer = URL(string: "https://accounts.google.com")!
// discovers endpoints
OIDAuthorizationService.discoverConfiguration(forIssuer: issuer) { configuration, error in
guard let config = configuration else {
print("Error retrieving discovery document: \(error?.localizedDescription ?? "Unknown error")")
return
}
// perform the auth request...
}
```
**tvOS**
Objective-C
```objc
NSURL *issuer = [NSURL URLWithString:@"https://accounts.google.com"];
[OIDTVAuthorizationService discoverServiceConfigurationForIssuer:issuer
completion:^(OIDTVServiceConfiguration *_Nullable configuration,
NSError *_Nullable error) {
if (!configuration) {
NSLog(@"Error retrieving discovery document: %@",
[error localizedDescription]);
return;
}
// perform the auth request...
}];
```
### Authorizing – iOS
First, you need to have a property in your `UIApplicationDelegate`
implementation to hold the session, in order to continue the authorization flow
from the redirect. In this example, the implementation of this delegate is
a class named `AppDelegate`, if your app's application delegate has a different
name, please update the class name in samples below accordingly.
Objective-C
```objc
@interface AppDelegate : UIResponder
// property of the app's AppDelegate
@property(nonatomic, strong, nullable) id currentAuthorizationFlow;
@end
```
Swift
```swift
class AppDelegate: UIResponder, UIApplicationDelegate {
// property of the app's AppDelegate
var currentAuthorizationFlow: OIDExternalUserAgentSession?
}
```
And your main class, a property to store the auth state:
Objective-C
```objc
// property of the containing class
@property(nonatomic, strong, nullable) OIDAuthState *authState;
```
Swift
```swift
// property of the containing class
private var authState: OIDAuthState?
```
Then, initiate the authorization request. By using the
`authStateByPresentingAuthorizationRequest` convenience method, the token
exchange will be performed automatically, and everything will be protected with
PKCE (if the server supports it). AppAuth also lets you perform these
requests manually. See the `authNoCodeExchange` method in the included Example
app for a demonstration:
Objective-C
```objc
// builds authentication request
OIDAuthorizationRequest *request =
[[OIDAuthorizationRequest alloc] initWithConfiguration:configuration
clientId:kClientID
scopes:@[OIDScopeOpenID,
OIDScopeProfile]
redirectURL:kRedirectURI
responseType:OIDResponseTypeCode
additionalParameters:nil];
// performs authentication request
AppDelegate *appDelegate =
(AppDelegate *)[UIApplication sharedApplication].delegate;
appDelegate.currentAuthorizationFlow =
[OIDAuthState authStateByPresentingAuthorizationRequest:request
presentingViewController:self
callback:^(OIDAuthState *_Nullable authState,
NSError *_Nullable error) {
if (authState) {
NSLog(@"Got authorization tokens. Access token: %@",
authState.lastTokenResponse.accessToken);
[self setAuthState:authState];
} else {
NSLog(@"Authorization error: %@", [error localizedDescription]);
[self setAuthState:nil];
}
}];
```
Swift
```swift
// builds authentication request
let request = OIDAuthorizationRequest(configuration: configuration,
clientId: clientID,
clientSecret: clientSecret,
scopes: [OIDScopeOpenID, OIDScopeProfile],
redirectURL: redirectURI,
responseType: OIDResponseTypeCode,
additionalParameters: nil)
// performs authentication request
print("Initiating authorization request with scope: \(request.scope ?? "nil")")
let appDelegate = UIApplication.shared.delegate as! AppDelegate
appDelegate.currentAuthorizationFlow =
OIDAuthState.authState(byPresenting: request, presenting: self) { authState, error in
if let authState = authState {
self.setAuthState(authState)
print("Got authorization tokens. Access token: " +
"\(authState.lastTokenResponse?.accessToken ?? "nil")")
} else {
print("Authorization error: \(error?.localizedDescription ?? "Unknown error")")
self.setAuthState(nil)
}
}
```
*Handling the Redirect*
The authorization response URL is returned to the app via the iOS openURL
app delegate method, so you need to pipe this through to the current
authorization session (created in the previous session):
Objective-C
```objc
- (BOOL)application:(UIApplication *)app
openURL:(NSURL *)url
options:(NSDictionary *)options {
// Sends the URL to the current authorization flow (if any) which will
// process it if it relates to an authorization response.
if ([_currentAuthorizationFlow resumeExternalUserAgentFlowWithURL:url]) {
_currentAuthorizationFlow = nil;
return YES;
}
// Your additional URL handling (if any) goes here.
return NO;
}
```
Swift
```swift
func application(_ app: UIApplication,
open url: URL,
options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool {
// Sends the URL to the current authorization flow (if any) which will
// process it if it relates to an authorization response.
if let authorizationFlow = self.currentAuthorizationFlow,
authorizationFlow.resumeExternalUserAgentFlow(with: url) {
self.currentAuthorizationFlow = nil
return true
}
// Your additional URL handling (if any)
return false
}
```
### Authorizing – MacOS
On macOS, the most popular way to get the authorization response redirect is to
start a local HTTP server on the loopback interface (limited to incoming
requests from the user's machine only). When the authorization is complete, the
user is redirected to that local server, and the authorization response can be
processed by the app. AppAuth takes care of managing the local HTTP server
lifecycle for you.
> #### :bulb: Alternative: Custom URI Schemes
> Custom URI schemes are also supported on macOS, but some browsers display
> an interstitial, which reduces the usability. For an example on using custom
> URI schemes with macOS, See `Example-Mac`.
To receive the authorization response using a local HTTP server, first you need
to have an instance variable in your main class to retain the HTTP redirect
handler:
Objective-C
```objc
OIDRedirectHTTPHandler *_redirectHTTPHandler;
```
Then, as the port used by the local HTTP server varies, you need to start it
before building the authorization request, in order to get the exact redirect
URI to use:
Objective-C
```objc
static NSString *const kSuccessURLString =
@"http://openid.github.io/AppAuth-iOS/redirect/";
NSURL *successURL = [NSURL URLWithString:kSuccessURLString];
// Starts a loopback HTTP redirect listener to receive the code. This needs to be started first,
// as the exact redirect URI (including port) must be passed in the authorization request.
_redirectHTTPHandler = [[OIDRedirectHTTPHandler alloc] initWithSuccessURL:successURL];
NSURL *redirectURI = [_redirectHTTPHandler startHTTPListener:nil];
```
Then, initiate the authorization request. By using the
`authStateByPresentingAuthorizationRequest` convenience method, the token
exchange will be performed automatically, and everything will be protected with
PKCE (if the server supports it). By assigning the return value to the
`OIDRedirectHTTPHandler`'s `currentAuthorizationFlow`, the authorization will
continue automatically once the user makes their choice:
```objc
// builds authentication request
OIDAuthorizationRequest *request =
[[OIDAuthorizationRequest alloc] initWithConfiguration:configuration
clientId:kClientID
clientSecret:kClientSecret
scopes:@[ OIDScopeOpenID ]
redirectURL:redirectURI
responseType:OIDResponseTypeCode
additionalParameters:nil];
// performs authentication request
__weak __typeof(self) weakSelf = self;
_redirectHTTPHandler.currentAuthorizationFlow =
[OIDAuthState authStateByPresentingAuthorizationRequest:request
callback:^(OIDAuthState *_Nullable authState,
NSError *_Nullable error) {
// Brings this app to the foreground.
[[NSRunningApplication currentApplication]
activateWithOptions:(NSApplicationActivateAllWindows |
NSApplicationActivateIgnoringOtherApps)];
// Processes the authorization response.
if (authState) {
NSLog(@"Got authorization tokens. Access token: %@",
authState.lastTokenResponse.accessToken);
} else {
NSLog(@"Authorization error: %@", error.localizedDescription);
}
[weakSelf setAuthState:authState];
}];
```
### Authorizing – tvOS
Ensure that your main class is a delegate of `OIDAuthStateChangeDelegate`, `OIDAuthStateErrorDelegate`, implement the corresponding methods, and include the following property and instance variable:
Objective-C
```objc
// property of the containing class
@property(nonatomic, strong, nullable) OIDAuthState *authState;
// instance variable of the containing class
OIDTVAuthorizationCancelBlock _cancelBlock;
```
Then, build and perform the authorization request.
Objective-C
```
/* Detailed source-code truncated for AI context efficiency. */
```
### Making API Calls
AppAuth gives you the raw token information, if you need it. However, we
recommend that users of the `OIDAuthState` convenience wrapper use the provided
`performActionWithFreshTokens:` method to perform their API calls to avoid
needing to worry about token freshness:
Objective-C
```objc
[_authState performActionWithFreshTokens:^(NSString *_Nonnull accessToken,
NSString *_Nonnull idToken,
NSError *_Nullable error) {
if (error) {
NSLog(@"Error fetching fresh tokens: %@", [error localizedDescription]);
return;
}
// perform your API request using the tokens
}];
```
Swift
```swift
let userinfoEndpoint = URL(string:"https://openidconnect.googleapis.com/v1/userinfo")!
self.authState?.performAction() { (accessToken, idToken, error) in
if error != nil {
print("Error fetching fresh tokens: \(error?.localizedDescription ?? "Unknown error")")
return
}
guard let accessToken = accessToken else {
return
}
// Add Bearer token to request
var urlRequest = URLRequest(url: userinfoEndpoint)
urlRequest.allHTTPHeaderFields = ["Authorization": "Bearer \(accessToken)"]
// Perform request...
}
```
### Custom User-Agents (iOS and macOS)
Each OAuth flow involves presenting an external user-agent to the user, that
allows them to interact with the OAuth authorization server. Typical examples
of a user-agent are the user's browser, or an in-app browser tab incarnation
like `ASWebAuthenticationSession` on iOS.
AppAuth ships with several implementations of an external user-agent out of the
box, including defaults for iOS and macOS suitable for most cases. The default
user-agents typically share persistent cookies with the system default browser,
to improve the chance that the user doesn't need to sign-in all over again.
It is possible to change the user-agent that AppAuth uses, and even write your
own - all without needing to fork the library.
All implementations of the external user-agent, be they included or created by
you need to conform to the
[`OIDExternalUserAgent`](http://openid.github.io/AppAuth-iOS/docs/latest/protocol_o_i_d_external_user_agent-p.html)
protocol.
Instances of the `OIDExternalUserAgent`are passed into
[`OIDAuthState.authStateByPresentingAuthorizationRequest:externalUserAgent:callback`](http://openid.github.io/AppAuth-iOS/docs/latest/interface_o_i_d_auth_state.html#ac762fe2bf95c116f0b437419be211fa1)
and/or
[`OIDAuthorizationService.presentAuthorizationRequest:externalUserAgent:callback:`](http://openid.github.io/AppAuth-iOS/docs/latest/interface_o_i_d_authorization_service.html#ae551f8e6887366a46e49b09b37389b8f)
rather than using the platform-specific convenience methods (which use the
default user-agents for their respective platforms), like
[`OIDAuthState.authStateByPresentingAuthorizationRequest:presentingViewController:callback:`](http://openid.github.io/AppAuth-iOS/docs/latest/category_o_i_d_auth_state_07_i_o_s_08.html#ae32fd0732cd3192cd5219f2655a4c85c).
Popular use-cases for writing your own user-agent implementation include needing
to style the user-agent in ways not supported by AppAuth, and implementing a
fully custom flow with your own business logic. You can take one of the existing
implementations as a starting point to copy, rename, and customize to your
needs.
#### Custom Browser User-Agent
AppAuth for iOS includes a few extra user-agent implementations which you can
try, or use as a reference for your own implementation. One of them,
[`OIDExternalUserAgentIOSCustomBrowser`](http://openid.github.io/AppAuth-iOS/docs/latest/interface_o_i_d_external_user_agent_i_o_s_custom_browser.html)
enables you to use a different browser for authentication, like Chrome for iOS
or Firefox for iOS.
Here's how to configure AppAuth to use a custom browser using the
`OIDExternalUserAgentIOSCustomBrowser` user agent:
First, add the following array to your
[Info.plist](https://github.com/openid/AppAuth-iOS/blob/135f99d2cb4e9d18d310ac2588b905e612461561/Examples/Example-iOS_ObjC/Source/Info.plist#L34)
(in XCode, right click -> Open As -> Source Code)
```
LSApplicationQueriesSchemes
googlechromes
opera-https
firefox
```
This is required so that AppAuth can test for the browser and open the app store
if it's not installed (the default behavior of this user-agent). You only need
to include the URL scheme of the actual browser you intend to use.
Objective-C
```objc
// performs authentication request
AppDelegate *appDelegate =
(AppDelegate *)[UIApplication sharedApplication].delegate;
id userAgent =
[OIDExternalUserAgentIOSCustomBrowser CustomBrowserChrome];
appDelegate.currentAuthorizationFlow =
[OIDAuthState authStateByPresentingAuthorizationRequest:request
externalUserAgent:userAgent
callback:^(OIDAuthState *_Nullable authState,
NSError *_Nullable error) {
if (authState) {
NSLog(@"Got authorization tokens. Access token: %@",
authState.lastTokenResponse.accessToken);
[self setAuthState:authState];
} else {
NSLog(@"Authorization error: %@", [error localizedDescription]);
[self setAuthState:nil];
}
}];
```
Swift
```
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else {
self.logMessage("Error accessing AppDelegate")
return
}
let userAgent = OIDExternalUserAgentIOSCustomBrowser.customBrowserChrome()
appDelegate.currentAuthorizationFlow = OIDAuthState.authState(byPresenting: request, externalUserAgent: userAgent) { authState, error in
if let authState = authState {
self.setAuthState(authState)
self.logMessage("Got authorization tokens. Access token: \(authState.lastTokenResponse?.accessToken ?? "DEFAULT_TOKEN")")
} else {
self.logMessage("Authorization error: \(error?.localizedDescription ?? "DEFAULT_ERROR")")
self.setAuthState(nil)
}
}
```
That's it! With those two changes (which you can try on the included sample),
AppAuth will use Chrome iOS for the authorization request (and open Chrome in
the App Store if it's not installed).
⚠️**Note: the `OIDExternalUserAgentIOSCustomBrowser` user-agent is not intended for consumer apps**. It is designed for
advanced enterprise use-cases where the app developers have greater control over
the operating environment and have special requirements that require a custom
browser like Chrome.
You don't need to stop with the included external user agents either! Since the
[`OIDExternalUserAgent`](http://openid.github.io/AppAuth-iOS/docs/latest/protocol_o_i_d_external_user_agent-p.html)
protocol is part of AppAuth's public API, you can implement your own versions of
it. In the above example,
`userAgent = [OIDExternalUserAgentIOSCustomBrowser CustomBrowserChrome]` would
be replaced with an instantiation of your user-agent implementation.
## API Documentation
Browse the [API documentation](http://openid.github.io/AppAuth-iOS/docs/latest/annotated.html).
## Included Samples
Sample apps that explore core AppAuth features are available for iOS, macOS and tvOS; follow the instructions in [Examples/README.md](Examples/README.md) to get started.
---
### Pods/GoogleAPIClientForREST/README (Pods/GoogleAPIClientForREST/README.md)
# Google APIs Client Library for Objective-C for REST #
**Project site**
**Discussion group**
[](https://travis-ci.org/google/google-api-objectivec-client-for-rest)
Written by Google, this library is a flexible and efficient Objective-C
framework for accessing JSON APIs.
This is the recommended library for accessing JSON-based Google APIs for iOS and
Mac OS X applications. The library is compatible with applications built for
iOS 7 and later, and Mac OS X 10.9 and later.
**To get started** with Google APIs and the Objective-C client library, Read the
[wiki](https://github.com/google/google-api-objectivec-client-for-rest/wiki).
See
[BuildingTheLibrary](https://github.com/google/google-api-objectivec-client-for-rest/wiki/BuildingTheLibrary)
for how to add the library to a Mac or iPhone application project, it covers
directly adding sources or using CocoaPods. Study the
[example applications](https://github.com/google/google-api-objectivec-client-for-rest/tree/master/Examples).
Generated interfaces for Google APIs are in the
[GeneratedServices folder](https://github.com/google/google-api-objectivec-client-for-rest/tree/master/Source/GeneratedServices).
In addition to the pre generated classes included with the library, you can
generate your own source for other services that have a
[discovery document](https://developers.google.com/discovery/v1/reference/apis#resource-representations)
by using the
[ServiceGenerator](https://github.com/google/google-api-objectivec-client-for-rest/wiki/ServiceGenerator).
**If you have a problem** or want a new feature to be included in the library,
please join the
[discussion group](http://groups.google.com/group/google-api-objectivec-client).
Be sure to include
[http logs](https://github.com/google/google-api-objectivec-client-for-rest/wiki#logging-http-server-traffic)
for requests and responses when posting questions. Bugs may also be submitted
on the [issues list](https://github.com/google/google-api-objectivec-client-for-rest/issues).
**Externally-included projects**: The library is built on top of code from the separate
project [GTM Session Fetcher](https://github.com/google/gtm-session-fetcher). To work
with some remote services, it also needs
[Authentication/Authorization](https://github.com/google/google-api-objectivec-client-for-rest/wiki#authentication-and-authorization).
**Google Data APIs**: The much older library for XML-based APIs is
[still available](https://github.com/google/gdata-objectivec-client).
Other useful classes for Mac and iOS developers are available in the
[Google Toolbox for Mac](https://github.com/google/google-toolbox-for-mac).
---
### Pods/GoogleSignIn/README (Pods/GoogleSignIn/README.md)
[](https://cocoapods.org/pods/GoogleSignIn)
[](https://cocoapods.org/pods/GoogleSignIn)
[](https://cocoapods.org/pods/GoogleSignIn)
[](https://github.com/google/GoogleSignIn-iOS/actions/workflows/tests.yml)
# Google Sign-In for iOS and macOS
Get users into your apps quickly and securely, using a registration system they
already use and trust—their Google account.
Visit [our developer site](https://developers.google.com/identity/sign-in/ios/)
for integration instructions, documentation, support information, and terms of
service.
## Getting Started
Try either the [Objective-C](Samples/ObjC) or [Swift](Samples/Swift) sample app.
For example, to demo the Objective-C sample project, you have three options:
1. Using [CocoaPods](https://cocoapods.org/)'s `try` method:
```
pod try GoogleSignIn
```
Note, this will default to providing you with the Objective-C sample app.
2. Using CocoaPod's `install` method:
```
git clone https://github.com/google/GoogleSignIn-iOS
cd GoogleSignIn-iOS/Samples/ObjC/SignInSample/
pod install
open SignInSampleForPod.xcworkspace
```
3. Using [Swift Package Manager](https://swift.org/package-manager/):
```
git clone https://github.com/google/GoogleSignIn-iOS
open GoogleSignIn-iOS/Samples/ObjC/SignInSample/SignInSample.xcodeproj
```
If you would like to see a Swift example, take a look at
[Samples/Swift/DaysUntilBirthday](Samples/Swift/DaysUntilBirthday).
* Add Google Sign-In to your own app by following our
[getting started guides](https://developers.google.com/identity/sign-in/ios/start-integrating).
* Take a look at the
[API reference](https://developers.google.com/identity/sign-in/ios/api/).
## Google Sign-In on macOS
Google Sign-In allows your users to sign-in to your native macOS app using their Google account
and default browser. When building for macOS, the `signInWithConfiguration:` and `addScopes:`
methods take a `presentingWindow:` parameter in place of `presentingViewController:`. Note that
in order for your macOS app to store credientials via the Keychain on macOS, you will need to
[sign your app](https://developer.apple.com/support/code-signing/).
### Mac Catalyst
Google Sign-In also supports iOS apps that are built for macOS via
[Mac Catalyst](https://developer.apple.com/mac-catalyst/). In order for your Mac Catalyst app
to store credientials via the Keychain on macOS, you will need to
[sign your app](https://developer.apple.com/support/code-signing/).
## Using the Google Sign-In Button
There are several ways to add a 'Sign in with Google' button to your app, which
path you choose will depend on your UI framework and target platform.
### SwiftUI (iOS and macOS)
Creating a 'Sign in with Google' button in SwiftUI can be as simple as this:
```
GoogleSignInButton {
GIDSignIn.sharedInstance.signIn(
with: configuration,
presenting: yourViewController) { user, error in
// check `error`; do something with `user`
}
}
```
This example takes advantage of the initializer's [default argument for the
view model](GoogleSignInSwift/Sources/GoogleSignInButton.swift#L39).
The default arguments for the view model will use the light scheme, the
standard button style, and the normal button state.
You can supply an instance of [`GoogleSignInButtonViewModel`](GoogleSignInSwift/Sources/GoogleSignInButtonViewModel.swift)
with different values for these properties to customize the button.
[This convenience initializer](GoogleSignInSwift/Sources/GoogleSignInButton.swift#L56)
provides parameters that you can use to set these values as needed.
### UIKit (iOS)
If you are not using SwiftUI to build your user interfaces, you can either
create `GIDSignInButton` programmatically, or in a Xib/Storyboard.
If you are writing programmatic UI code, it will look something like this:
`let button = GIDSignInButton(frame: CGRect())`
### AppKit (macOS)
Given that `GIDSignInButton` is implemented as a subclass of `UIControl`, it
will not be available on macOS.
You can instead use the SwiftUI Google sign-in button.
Doing so will require that you wrap the SwiftUI button in a hosting view so
that it will be available for use in AppKit.
```
let signInButton = GoogleSignInButton {
GIDSignIn.sharedInstance.signIn(
with: configuration,
presenting: yourViewController) { user, error in
// check `error`; do something with `user`
}
}
let hostedButton = NSHostingView(rootView: signInButton)
```
---
### Pods/GTMAppAuth/README (Pods/GTMAppAuth/README.md)
[](https://cocoapods.org/pods/GTMAppAuth)
[](https://cocoapods.org/pods/GTMAppAuth)
[](https://cocoapods.org/pods/GTMAppAuth)
[](https://github.com/google/GTMAppAuth/actions/workflows/tests.yml)
# GTMAppAuth for Apple Platforms
GTMAppAuth enables you to use [AppAuth](https://github.com/openid/AppAuth-iOS)
with the
[Google Toolbox for Mac - Session Fetcher](https://github.com/google/gtm-session-fetcher)
and
[Google APIs Client Library for Objective-C For REST](https://github.com/google/google-api-objectivec-client-for-rest)
libraries on iOS, macOS, tvOS, and watchOS by providing an implementation of
[`GTMFetcherAuthorizationProtocol`](https://github.com/google/gtm-session-fetcher/blob/2a3b5264108e80d62003b770ff02eb7364ff1365/Source/GTMSessionFetcher.h#L660)
for authorizing requests with AppAuth.
GTMAppAuth is an alternative authorizer to [GTMOAuth2](https://github.com/google/gtm-oauth2)
. The key differentiator is the use of the user's default browser for the
authorization, which is more secure, more usable (the user's session can be
reused) and follows modern OAuth [best practices for native apps](https://datatracker.ietf.org/doc/html/rfc8252).
Compatibility methods for GTMOAuth2 are offered allowing you to migrate
from GTMOAuth2 to GTMAppAuth preserving previously serialized authorizations
(so users shouldn't need to re-authenticate).
## Setup
If you use [CocoaPods](https://guides.cocoapods.org/using/getting-started.html),
simply add:
pod 'GTMAppAuth'
To your `Podfile` and run `pod install`.
## Usage
### Configuration
To configure GTMAppAuth with the OAuth endpoints for Google, you can use the
convenience method:
```objc
OIDServiceConfiguration *configuration =
[GTMAppAuthFetcherAuthorization configurationForGoogle];
```
Alternatively, you can configure GTMAppAuth by specifying the endpoints
directly:
```objc
NSURL *authorizationEndpoint =
[NSURL URLWithString:@"https://accounts.google.com/o/oauth2/v2/auth"];
NSURL *tokenEndpoint =
[NSURL URLWithString:@"https://www.googleapis.com/oauth2/v4/token"];
OIDServiceConfiguration *configuration =
[[OIDServiceConfiguration alloc]
initWithAuthorizationEndpoint:authorizationEndpoint
tokenEndpoint:tokenEndpoint];
// perform the auth request...
```
Or through discovery:
```objc
NSURL *issuer = [NSURL URLWithString:@"https://accounts.google.com"];
[OIDAuthorizationService discoverServiceConfigurationForIssuer:issuer
completion:^(OIDServiceConfiguration *_Nullable configuration,
NSError *_Nullable error) {
if (!configuration) {
NSLog(@"Error retrieving discovery document: %@",
[error localizedDescription]);
return;
}
// perform the auth request...
}];
```
### Authorizing
First, you need to have a way for your UIApplicationDelegate to continue the
authorization flow session from the incoming redirect URI. Typically you could
store the in-progress OIDAuthorizationFlowSession instance in a property:
```objc
// property of the app's UIApplicationDelegate
@property(nonatomic, nullable)
id currentAuthorizationFlow;
```
And in a location accessible by all controllers that need authorization, a
property to store the authorization state:
```objc
// property of the containing class
@property(nonatomic, nullable) GTMAppAuthFetcherAuthorization *authorization;
```
Then, initiate the authorization request. By using the
`authStateByPresentingAuthorizationRequest` method, the OAuth token
exchange will be performed automatically, and everything will be protected with
PKCE (if the server supports it).
```objc
// builds authentication request
OIDAuthorizationRequest *request =
[[OIDAuthorizationRequest alloc] initWithConfiguration:configuration
clientId:kClientID
clientSecret:kClientSecret
scopes:@[OIDScopeOpenID, OIDScopeProfile]
redirectURL:redirectURI
responseType:OIDResponseTypeCode
additionalParameters:nil];
// performs authentication request
self.appDelegate.currentAuthorizationFlow =
[OIDAuthState authStateByPresentingAuthorizationRequest:request
callback:^(OIDAuthState *_Nullable authState,
NSError *_Nullable error) {
if (authState) {
// Creates the GTMAppAuthFetcherAuthorization from the OIDAuthState.
GTMAppAuthFetcherAuthorization *authorization =
[[GTMAppAuthFetcherAuthorization alloc] initWithAuthState:authState];
self.authorization = authorization;
NSLog(@"Got authorization tokens. Access token: %@",
authState.lastTokenResponse.accessToken);
} else {
NSLog(@"Authorization error: %@", [error localizedDescription]);
self.authorization = nil;
}
}];
```
### Handling the Redirect
The authorization response URL is returned to the app via the platform-specific
application delegate method, so you need to pipe this through to the current
authorization session (created in the previous session).
#### macOS Custom URI Scheme Redirect Example
```objc
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
// Other app initialization code ...
// Register for GetURL events.
NSAppleEventManager *appleEventManager =
[NSAppleEventManager sharedAppleEventManager];
[appleEventManager setEventHandler:self
andSelector:@selector(handleGetURLEvent:withReplyEvent:)
forEventClass:kInternetEventClass
andEventID:kAEGetURL];
}
- (void)handleGetURLEvent:(NSAppleEventDescriptor *)event
withReplyEvent:(NSAppleEventDescriptor *)replyEvent {
NSString *URLString = [[event paramDescriptorForKeyword:keyDirectObject] stringValue];
NSURL *URL = [NSURL URLWithString:URLString];
[_currentAuthorizationFlow resumeExternalUserAgentFlowWithURL:URL];
}
```
#### iOS Custom URI Scheme Redirect Example
```objc
- (BOOL)application:(UIApplication *)app
openURL:(NSURL *)url
options:(NSDictionary *)options {
// Sends the URL to the current authorization flow (if any) which will
// process it if it relates to an authorization response.
if ([_currentAuthorizationFlow resumeExternalUserAgentFlowWithURL:url]) {
_currentAuthorizationFlow = nil;
return YES;
}
// Your additional URL handling (if any) goes here.
return NO;
}
```
### Making API Calls
The goal of GTMAppAuth is to enable you to authorize HTTP requests with fresh
tokens following the Session Fetcher pattern, which you can do like so:
```objc
// Creates a GTMSessionFetcherService with the authorization.
// Normally you would save this service object and re-use it for all REST API calls.
GTMSessionFetcherService *fetcherService = [[GTMSessionFetcherService alloc] init];
fetcherService.authorizer = self.authorization;
// Creates a fetcher for the API call.
NSURL *userinfoEndpoint = [NSURL URLWithString:@"https://www.googleapis.com/oauth2/v3/userinfo"];
GTMSessionFetcher *fetcher = [fetcherService fetcherWithURL:userinfoEndpoint];
[fetcher beginFetchWithCompletionHandler:^(NSData *data, NSError *error) {
// Checks for an error.
if (error) {
// OIDOAuthTokenErrorDomain indicates an issue with the authorization.
if ([error.domain isEqual:OIDOAuthTokenErrorDomain]) {
self.authorization = nil;
NSLog(@"Authorization error during token refresh, clearing state. %@",
error);
// Other errors are assumed transient.
} else {
NSLog(@"Transient error during token refresh. %@", error);
}
return;
}
// Parses the JSON response.
NSError *jsonError = nil;
id jsonDictionaryOrArray =
[NSJSONSerialization JSONObjectWithData:data options:0 error:&jsonError];
// JSON error.
if (jsonError) {
NSLog(@"JSON decoding error %@", jsonError);
return;
}
// Success response!
NSLog(@"Success: %@", jsonDictionaryOrArray);
}];
```
### Saving to the Keychain
You can easily save `GTMAppAuthFetcherAuthorization` instances to the Keychain using
the included `GTMAppAuthFetcherAuthorization+Keychain` category.
```objc
// Save to Keychain
[GTMAppAuthFetcherAuthorization saveAuthorization:_authorization
toKeychainForName:kGTMAppAuthExampleAuthorizerKey];
// Restore from Keychain
GTMAppAuthFetcherAuthorization* authorization =
[GTMAppAuthFetcherAuthorization authorizationFromKeychainForName:kGTMAppAuthExampleAuthorizerKey];
// Remove from Keychain
[GTMAppAuthFetcherAuthorization
removeAuthorizationFromKeychainForName:kGTMAppAuthExampleAuthorizerKey];
```
#### Keychain Storage
`GTMAppAuthFetcherAuthorization` instances are stored using Keychain items of the
[`kSecClassGenericPassword`](https://developer.apple.com/documentation/security/ksecclassgenericpassword?language=objc)
class with a [`kSecAttrAccount`](https://developer.apple.com/documentation/security/ksecattraccount?language=objc)
value of "OAuth" and a developer supplied value for [`kSecAttrService`](https://developer.apple.com/documentation/security/ksecattrservice?language=objc).
For this use of generic password items, the combination of account and service
values acts as the
[primary key](https://developer.apple.com/documentation/security/1542001-security_framework_result_codes/errsecduplicateitem?language=objc)
of the Keychain items. The
[`kSecAttrAccessible`](https://developer.apple.com/documentation/security/ksecattraccessible?language=objc)
key is set to
[`kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly`](https://developer.apple.com/documentation/security/ksecattraccessibleafterfirstunlockthisdeviceonly?language=objc)
in order to allow background access after initial device unlock following a
restart. A [keyed archive](https://developer.apple.com/documentation/foundation/nskeyedarchiver?language=objc)
representation of the relevant `GTMAppAuthFetcherAuthorization` instance is supplied as the value for
[`kSecValueData`](https://developer.apple.com/documentation/security/ksecvaluedata?language=objc)
and this is encrypted and stored by
[Keychain Services](https://developer.apple.com/documentation/security/keychain_services?language=objc).
For macOS, two Keychain storage options are available: the traditional file-based Keychain storage
which uses access control lists and the more modern [data protection keychain storage](https://developer.apple.com/documentation/security/ksecusedataprotectionkeychain?language=objc)
which uses Keychain access control groups. By default, GTMAppAuth uses the file-based Keychain storage on macOS. You may opt into using data protection keychain storage by using the parameter
`useDataProtectionKeychain:YES` in your method calls. Note that Keychain items stored via one
storage type will not be available via the other and macOS apps that choose to use the data
protection Keychain will need to be signed in order for Keychain operations to succeed.
#### GTMOAuth2 Compatibility
To assist the migration from GTMOAuth2 to GTMAppAuth, GTMOAuth2-compatible
Keychain methods are provided in `GTMOAuth2KeychainCompatibility`.
```objc
// Restore from Keychain
GTMAppAuthFetcherAuthorization *auth =
[GTMOAuth2KeychainCompatibility authForGoogleFromKeychainForName:kKeychainItemName
clientID:clientID
clientSecret:clientSecret];
// Remove from Keychain
[GTMOAuth2KeychainCompatibility removeAuthFromKeychainForName:kKeychainItemName];
```
You can also save to GTMOAuth2 format, though this is discouraged (you
should save in GTMAppAuth format as described above).
```objc
// Save to Keychain
[GTMOAuth2KeychainCompatibility saveAuthToKeychainForName:kKeychainItemName
authentication:authorization];
```
## Included Samples
Try out one of the included sample apps under [Examples](Examples). In the
apps folder run `pod install`, then open the resulting `xcworkspace` file.
Be sure to follow the instructions in
[Example-iOS/README.md](Examples/Example-iOS/README.md) or
[Example-macOS/README.md](Examples/Example-macOS/README.md) to configure
your own OAuth client ID for use with the example.
## Differences with GTMOAuth2
### Authorization Method
GTMAppAuth uses the browser to present the authorization request, while
GTMOAuth2 uses an embedded web-view. Migrating to GTMAppAuth will require you
to change how you authorize the user. Follow the instructions above to get the
authorization. You can then create a `GTMAppAuthFetcherAuthorization` object
with the `initWithAuthState:authState` initializer.
Once you have the `GTMAppAuthFetcherAuthorization` you can continue to make REST
calls as before.
### Error Handling
GTMAppAuth's error handling is also different. There are no notifications,
instead you need to inspect NSError in the callback. If the error domain is
`OIDOAuthTokenErrorDomain`, it indicates an authorization error, you should
clear your authorization state and consider prompting the user to authorize
again. Other errors are generally considered transient, meaning that you should
retry the request after a delay.
### Serialization
The serialization format is different between GTMOAuth2 and GTMAppAuth, though
we have methods to help you migrate from one to the other without losing any
data.
## Migrating from GTMOAuth2
### OAuth Client Registration
Typically, GTMOAuth2 clients are registered with Google as type "Other". This is
correct for macOS, but on iOS clients should be registered with the type "iOS".
If you're migrating an iOS client, in the *same project as your existing client*,
[register a new iOS client](https://console.developers.google.com/apis/credentials?project=_)
to be used with GTMAppAuth.
### Changing your Authorization Flows
Both GTMOAuth2 and GTMAppAuth support the `GTMFetcherAuthorizationProtocol`
allowing you to use the authorization with the session fetcher. Where you
previously had a property like `GTMOAuth2Authentication *authorization` change the
type to reference the protocol instead, i.e.:
`id authorization`. This allows you to switch
the authorization implementation under the hood to GTMAppAuth.
Then, follow the instructions above to replace authorization request
(where you ask the user to grant access) with the GTMAppAuth approach. If you
created a new OAuth client, use that for these requests.
### Serialization & Migrating Existing Grants
GTMAppAuth has a new data format and APIs for serialization. Unlike
GTMOAuth2, GTMAppAuth serializes the configuration and history of the
authorization, including the client id, and a record of the authorization
request that resulted in the authorization grant.
The client ID used for GTMAppAuth is [different](#oauth-client-registration) to
the one used for GTMOAuth2. In order to keep track of the different client ids
used for new and old grants, it's recommended to migrate to the new
serialization format, which will store that for you.
[GTMOAuth2-compatible serialization](#gtmoauth2-compatible-serialization) is
also offered, but not fully supported.
Change how you serialize your `authorization` object using the new methods
using the following example.
```objc
// Serialize to Keychain
[GTMAppAuthFetcherAuthorization saveAuthorization:(GTMAppAuthFetcherAuthorization *)authorization
toKeychainForName:kNewKeychainName];
```
Be sure to use a *new* name for the keychain. Don't reuse your old one!
For deserializing, we can preserve all existing grants (so users who authorized
your app in GTMOAuth2 don't have to authorize it again). Remember that when
deserializing the *old* data you need to use your *old* keychain name, and
the old client id and client secret (if those changed), and that when
serializing to the *new* format, use the *new* keychain name.
Once again, pay particular care to use the old details when deserializing the
GTMOAuth2 keychain, and the new details for all other GTMAppAuth calls.
Keychain migration example:
```objc
// Attempt to deserialize from Keychain in GTMAppAuth format.
id authorization =
[GTMAppAuthFetcherAuthorization authorizationFromKeychainForName:kNewKeychainName];
// If no data found in the new format, try to deserialize data from GTMOAuth2
if (!authorization) {
// Tries to load the data serialized by GTMOAuth2 using old keychain name.
// If you created a new client id, be sure to use the *previous* client id and secret here.
authorization =
[GTMOAuth2KeychainCompatibility authForGoogleFromKeychainForName:kPreviousKeychainName
clientID:kPreviousClientID
clientSecret:kPreviousClientSecret];
if (authorization) {
// Remove previously stored GTMOAuth2-formatted data.
[GTMOAuth2KeychainCompatibility removeAuthFromKeychainForName:kPreviousKeychainName];
// Serialize to Keychain in GTMAppAuth format.
[GTMAppAuthFetcherAuthorization saveAuthorization:(GTMAppAuthFetcherAuthorization *)authorization
toKeychainForName:kNewKeychainName];
}
}
```
---
### Pods/GTMSessionFetcher/README (Pods/GTMSessionFetcher/README.md)
# Google Toolbox for Mac - Session Fetcher
**Project site**
**Discussion group**
[](https://github.com/google/gtm-session-fetcher/actions/workflows/swiftpm.yml)
[](https://github.com/google/gtm-session-fetcher/actions/workflows/cocoapods.yml)
`GTMSessionFetcher` makes it easy for Cocoa applications to perform http
operations. The fetcher is implemented as a wrapper on `NSURLSession`, so its
behavior is asynchronous and uses operating-system settings.
Features include:
- Simple to build; only one source/header file pair is required
- Simple to use: takes just two lines of code to fetch a request
- Supports upload and download sessions
- Flexible cookie storage
- Automatic retry on errors, with exponential backoff
- Support for generating multipart MIME upload streams
- Easy, convenient logging of http requests and responses
- Supports plug-in authentication such as with GTMAppAuth
- Easily testable; self-mocking
- Automatic rate limiting when created by the `GTMSessionFetcherService` factory class
- Fully independent of other projects
**To get started** please read
[USING.md](https://github.com/google/google-api-objectivec-client-for-rest/blob/main/USING.md)
for detailed information.
---
### Pods/Local Podspecs/Harmony.Podspec.Json (Pods/Local Podspecs/Harmony.podspec.json)
{
"name": "Harmony",
"version": "0.1",
"summary": "iOS Syncing Framework",
"description": "iOS framework that automatically syncs Core Data databases across different backends.",
"homepage": "https://github.com/rileytestut/Harmony",
"platforms": {
"ios": "14.0"
},
"source": {
"git": "https://github.com/rileytestut/Harmony.git"
},
"authors": {
"Riley Testut": "riley@rileytestut.com"
},
"social_media_url": "https://twitter.com/rileytestut",
"source_files": "Harmony/**/*.{h,m,swift}",
"public_header_files": "Harmony/Harmony.h",
"header_mappings_dir": "",
"resources": [
"Harmony/**/*.xcdatamodeld",
"Harmony/**/*.xcmappingmodel"
],
"dependencies": {
"Roxas": []
},
"subspecs": [
{
"name": "Harmony-Dropbox",
"source_files": "Backends/Dropbox/Harmony-Dropbox/**/*.swift",
"dependencies": {
"SwiftyDropbox": [
"~> 5.0.0"
]
}
},
{
"name": "Harmony-Drive",
"source_files": "Backends/Drive/Harmony-Drive/**/*.swift",
"dependencies": {
"GoogleAPIClientForREST/Drive": [
"~> 1.3.0"
],
"GoogleSignIn": [
"~> 6.2"
]
}
}
]
}
---
### Pods/Local Podspecs/Roxas.Podspec.Json (Pods/Local Podspecs/Roxas.podspec.json)
{
"name": "Roxas",
"version": "0.1",
"summary": "Private iOS Development Framework",
"description": "Private iOS Development Framework used by me in my projects.",
"homepage": "https://github.com/rileytestut/roxas",
"platforms": {
"ios": "12.0"
},
"source": {
"git": "http://github.com/rileytestut/Roxas.git"
},
"authors": {
"Riley Testut": "riley@rileytestut.com"
},
"social_media_url": "https://twitter.com/rileytestut",
"source_files": "Roxas/*.{h,m}",
"public_header_files": "Roxas/*.h",
"private_header_files": "Roxas/RSTCellContentDataSource_Subclasses.h",
"prefix_header_file": "Roxas/Roxas-Prefix.pch",
"resources": "Roxas/*.xib"
}
---
### Pods/SDWebImage/README (Pods/SDWebImage/README.md)
Web Image
=========
[](https://travis-ci.org/rs/SDWebImage)
[](http://cocoadocs.org/docsets/SDWebImage/)
[](http://cocoadocs.org/docsets/SDWebImage/)
[](https://www.apache.org/licenses/LICENSE-2.0.html)
[](https://www.versioneye.com/objective-c/sdwebimage/3.3)
[](https://www.versioneye.com/objective-c/sdwebimage/references)
[](https://github.com/rs/SDWebImage)
This library provides a category for UIImageView with support for remote images coming from the web.
It provides:
- An `UIImageView` category adding web image and cache management to the Cocoa Touch framework
- An asynchronous image downloader
- An asynchronous memory + disk image caching with automatic cache expiration handling
- Animated GIF support
- WebP format support
- A background image decompression
- A guarantee that the same URL won't be downloaded several times
- A guarantee that bogus URLs won't be retried again and again
- A guarantee that main thread will never be blocked
- Performances!
- Use GCD and ARC
- Arm64 support
NOTE: Version 3.8 of SDWebImage requires iOS 7 or later (because of NSURLSession).
Versions 3.7 to 3.0 requires iOS 5.1.1. If you need iOS < 5.0 support, please use the last [2.0 version](https://github.com/rs/SDWebImage/tree/2.0-compat).
[How is SDWebImage better than X?](https://github.com/rs/SDWebImage/wiki/How-is-SDWebImage-better-than-X%3F)
Who Uses It
----------
Find out [who uses SDWebImage](https://github.com/rs/SDWebImage/wiki/Who-Uses-SDWebImage) and add your app to the list.
How To Use
----------
API documentation is available at [CocoaDocs - SDWebImage](http://cocoadocs.org/docsets/SDWebImage/)
### Using UIImageView+WebCache category with UITableView
Just #import the UIImageView+WebCache.h header, and call the sd_setImageWithURL:placeholderImage:
method from the tableView:cellForRowAtIndexPath: UITableViewDataSource method. Everything will be
handled for you, from async downloads to caching management.
```objective-c
#import
...
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *MyIdentifier = @"MyIdentifier";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:MyIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:MyIdentifier] autorelease];
}
// Here we use the new provided sd_setImageWithURL: method to load the web image
[cell.imageView sd_setImageWithURL:[NSURL URLWithString:@"http://www.domain.com/path/to/image.jpg"]
placeholderImage:[UIImage imageNamed:@"placeholder.png"]];
cell.textLabel.text = @"My Text";
return cell;
}
```
### Using blocks
With blocks, you can be notified about the image download progress and whenever the image retrieval
has completed with success or not:
```objective-c
// Here we use the new provided sd_setImageWithURL: method to load the web image
[cell.imageView sd_setImageWithURL:[NSURL URLWithString:@"http://www.domain.com/path/to/image.jpg"]
placeholderImage:[UIImage imageNamed:@"placeholder.png"]
completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) {
... completion code here ...
}];
```
Note: neither your success nor failure block will be call if your image request is canceled before completion.
### Using SDWebImageManager
The SDWebImageManager is the class behind the UIImageView+WebCache category. It ties the
asynchronous downloader with the image cache store. You can use this class directly to benefit
from web image downloading with caching in another context than a UIView (ie: with Cocoa).
Here is a simple example of how to use SDWebImageManager:
```objective-c
SDWebImageManager *manager = [SDWebImageManager sharedManager];
[manager downloadImageWithURL:imageURL
options:0
progress:^(NSInteger receivedSize, NSInteger expectedSize) {
// progression tracking code
}
completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
if (image) {
// do something with image
}
}];
```
### Using Asynchronous Image Downloader Independently
It's also possible to use the async image downloader independently:
```objective-c
SDWebImageDownloader *downloader = [SDWebImageDownloader sharedDownloader];
[downloader downloadImageWithURL:imageURL
options:0
progress:^(NSInteger receivedSize, NSInteger expectedSize) {
// progression tracking code
}
completed:^(UIImage *image, NSData *data, NSError *error, BOOL finished) {
if (image && finished) {
// do something with image
}
}];
```
### Using Asynchronous Image Caching Independently
It is also possible to use the async based image cache store independently. SDImageCache
maintains a memory cache and an optional disk cache. Disk cache write operations are performed
asynchronous so it doesn't add unnecessary latency to the UI.
The SDImageCache class provides a singleton instance for convenience but you can create your own
instance if you want to create separated cache namespace.
To lookup the cache, you use the `queryDiskCacheForKey:done:` method. If the method returns nil, it means the cache
doesn't currently own the image. You are thus responsible for generating and caching it. The cache
key is an application unique identifier for the image to cache. It is generally the absolute URL of
the image.
```objective-c
SDImageCache *imageCache = [[SDImageCache alloc] initWithNamespace:@"myNamespace"];
[imageCache queryDiskCacheForKey:myCacheKey done:^(UIImage *image) {
// image is not nil if image was found
}];
```
By default SDImageCache will lookup the disk cache if an image can't be found in the memory cache.
You can prevent this from happening by calling the alternative method `imageFromMemoryCacheForKey:`.
To store an image into the cache, you use the storeImage:forKey: method:
```objective-c
[[SDImageCache sharedImageCache] storeImage:myImage forKey:myCacheKey];
```
By default, the image will be stored in memory cache as well as on disk cache (asynchronously). If
you want only the memory cache, use the alternative method storeImage:forKey:toDisk: with a negative
third argument.
### Using cache key filter
Sometime, you may not want to use the image URL as cache key because part of the URL is dynamic
(i.e.: for access control purpose). SDWebImageManager provides a way to set a cache key filter that
takes the NSURL as input, and output a cache key NSString.
The following example sets a filter in the application delegate that will remove any query-string from
the URL before to use it as a cache key:
```objective-c
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
SDWebImageManager.sharedManager.cacheKeyFilter = ^(NSURL *url) {
url = [[NSURL alloc] initWithScheme:url.scheme host:url.host path:url.path];
return [url absoluteString];
};
// Your app init code...
return YES;
}
```
Common Problems
---------------
### Using dynamic image size with UITableViewCell
UITableView determines the size of the image by the first image set for a cell. If your remote images
don't have the same size as your placeholder image, you may experience strange anamorphic scaling issue.
The following article gives a way to workaround this issue:
[http://www.wrichards.com/blog/2011/11/sdwebimage-fixed-width-cell-images/](http://www.wrichards.com/blog/2011/11/sdwebimage-fixed-width-cell-images/)
### Handle image refresh
SDWebImage does very aggressive caching by default. It ignores all kind of caching control header returned by the HTTP server and cache the returned images with no time restriction. It implies your images URLs are static URLs pointing to images that never change. If the pointed image happen to change, some parts of the URL should change accordingly.
If you don't control the image server you're using, you may not be able to change the URL when its content is updated. This is the case for Facebook avatar URLs for instance. In such case, you may use the `SDWebImageRefreshCached` flag. This will slightly degrade the performance but will respect the HTTP caching control headers:
``` objective-c
[imageView sd_setImageWithURL:[NSURL URLWithString:@"https://graph.facebook.com/olivier.poitrey/picture"]
placeholderImage:[UIImage imageNamed:@"avatar-placeholder.png"]
options:SDWebImageRefreshCached];
```
### Add a progress indicator
See this category: https://github.com/JJSaccolo/UIActivityIndicator-for-SDWebImage
Installation
------------
There are three ways to use SDWebImage in your project:
- using CocoaPods
- copying all the files into your project
- importing the project as a static library
### Installation with CocoaPods
[CocoaPods](http://cocoapods.org/) is a dependency manager for Objective-C, which automates and simplifies the process of using 3rd-party libraries in your projects. See the [Get Started](http://cocoapods.org/#get_started) section for more details.
#### Podfile
```
platform :ios, '7.0'
pod 'SDWebImage', '~>3.8'
```
If you are using Swift, be sure to add `use_frameworks!` and set your target to iOS 8+:
```
platform :ios, '8.0'
use_frameworks!
```
#### Subspecs
There are 3 subspecs available now: `Core`, `MapKit` and `WebP` (this means you can install only some of the SDWebImage modules. By default, you get just `Core`, so if you need `WebP`, you need to specify it).
Podfile example:
```
pod 'SDWebImage/WebP'
```
### Installation with Carthage (iOS 8+)
[Carthage](https://github.com/Carthage/Carthage) is a lightweight dependency manager for Swift and Objective-C. It leverages CocoaTouch modules and is less invasive than CocoaPods.
To install with carthage, follow the instruction on [Carthage](https://github.com/Carthage/Carthage)
#### Cartfile
```
github "rs/SDWebImage"
```
#### Usage
Swift
If you installed using CocoaPods:
```
import SDWebImage
```
If you installed manually:
```
import WebImage
```
Objective-C
```
@import WebImage;
```
### Installation by cloning the repository
In order to gain access to all the files from the repository, you should clone it.
```
git clone --recursive https://github.com/rs/SDWebImage.git
```
### Add the SDWebImage project to your project
- Download and unzip the last version of the framework from the [download page](https://github.com/rs/SDWebImage/releases)
- Right-click on the project navigator and select "Add Files to "Your Project":
- In the dialog, select SDWebImage.framework:
- Check the "Copy items into destination group's folder (if needed)" checkbox
### Add dependencies
- In you application project app’s target settings, find the "Build Phases" section and open the "Link Binary With Libraries" block:
- Click the "+" button again and select the "ImageIO.framework", this is needed by the progressive download feature:
### Add Linker Flag
Open the "Build Settings" tab, in the "Linking" section, locate the "Other Linker Flags" setting and add the "-ObjC" flag:
Alternatively, if this causes compilation problems with frameworks that extend optional libraries, such as Parse, RestKit or opencv2, instead of the -ObjC flag use:
```
-force_load SDWebImage.framework/Versions/Current/SDWebImage
```
If you're using Cocoa Pods and have any frameworks that extend optional libraries, such as Parsen RestKit or opencv2, instead of the -ObjC flag use:
```
-force_load $(TARGET_BUILD_DIR)/libPods.a
```
and this:
```
$(inherited)
```
### Import headers in your source files
In the source files where you need to use the library, import the header file:
```objective-c
#import
```
### Build Project
At this point your workspace should build without error. If you are having problem, post to the Issue and the
community can help you solve it.
Future Enhancements
-------------------
- LRU memory cache cleanup instead of reset on memory warning
## Licenses
All source code is licensed under the [MIT License](https://raw.github.com/rs/SDWebImage/master/LICENSE).
---
### Pods/SMCalloutView/README (Pods/SMCalloutView/README.md)
Overview
--------
SMCalloutView aims to be an exact replica of the private UICalloutView system
control.
We all love those "bubbles" you get when clicking pins in MKMapView. But
sadly, it's impossible to present this bubble-style "Callout" UI anywhere
outside MKMapView. Phooey! So this class _painstakingly_ recreates this handy
control for your pleasure.
Usage
-----
To use SMCalloutView in your own projects, simply copy the files
`SMCalloutView.h` and `SMCalloutView.m`.
SMCalloutView, by default, will render in the new style introduced with
iOS 7. If you need the old style, simply include `SMClassicCalloutView.h`
and `SMClassicCalloutView.m` in your project as well. There is a special
class constructor, `+[SMCalloutView platformCalloutView]` which will
automatically select the appropriate callout class for the current platform.
The comments in `SMCalloutView.h` do a lot of explaining on how to use the
class, but the main function you'll need is `presentCalloutFromRect:`. You'll
specify the view you'd like to add the callout to, as well as the rect
defining the "target" that the popup should point at. The target rect should
be _in the coordinate system of the target view_ (just like the similarly-
named `UIPopover` method). Most likely this will be `target.frame` if you're
adding the callout view as a sibling of the target view, or it would be
`target.bounds` if you're adding the callout view to the target itself.
You can study the included project's UIViewController subclasses for a working
example.
Questions
---------
#### How do I change the height of the callout?
If you use only the `title/titleView/subtitle/subtitleView` properties, the
callout will always be the "system standard" height. If you assign the
`contentView` property however, then the callout will size to fit the
`contentView` and the other properties are ignored.
[#29]: https://github.com/nfarina/calloutview/issues/29
#### Can I customize the background graphics?
Yes, the callout background is an instance of `SMCalloutBackgroundView`. You
can set your own custom `View` subclass to be the background, or you can use
one of the built-in subclasses:
- `SMCalloutMaskedBackgroundView` renders an iOS-7 style background.
- `SMCalloutImageBackgroundView` lets you specify each of the image
"fragments" that make up a horizontally-stretchable background.
- `SMCalloutDrawnBackgroundView` draws the background at any size using
CoreGraphics methods. You can copy the `-drawRect` method and change the
parameters to suit your needs.
#### Can I use the callout with the Google Maps iOS SDK?
Check out [ryanmaxwell's demo project][googlemaps] for an example of one way
to do this. ([More discussion on this topic][#25])
[googlemaps]: https://github.com/ryanmaxwell/GoogleMapsCalloutView
[#25]: https://github.com/nfarina/calloutview/issues/25
#### Have you recreated more of MapKit?
Nope, but other intrepid coders have!
- For an awesome replacement of the pulsing blue "Current Location" dot, check
out [Sam Vermette's SVPulsingAnnotationView][dot].
- And for the outdoor map data and tiles themselves, check out [Mapbox's iOS
SDK][mapbox], a complete open-source solution for custom maps. They even use
`SMCalloutView` out of the box!
[dot]: https://github.com/samvermette/SVPulsingAnnotationView
[mapbox]: https://www.mapbox.com/mobile/
More Info
---------
You can read more info if you wish in the [blog post][].
[blog post]: http://nfarina.com/post/78014139253/smcalloutview-for-ios-7
---
### Pods/SQLite.Swift/README (Pods/SQLite.swift/README.md)
# SQLite.swift
[![Build Status][TravisBadge]][TravisLink] [![CocoaPods Version][CocoaPodsVersionBadge]][CocoaPodsVersionLink] [![Swift5 compatible][Swift5Badge]][Swift5Link] [![Platform][PlatformBadge]][PlatformLink] [![Carthage compatible][CartagheBadge]][CarthageLink] [![Join the chat at https://gitter.im/stephencelis/SQLite.swift][GitterBadge]][GitterLink]
A type-safe, [Swift][]-language layer over [SQLite3][].
[SQLite.swift][] provides compile-time confidence in SQL statement
syntax _and_ intent.
## Features
- A pure-Swift interface
- A type-safe, optional-aware SQL expression builder
- A flexible, chainable, lazy-executing query layer
- Automatically-typed data access
- A lightweight, uncomplicated query and parameter binding interface
- Developer-friendly error handling and debugging
- [Full-text search][] support
- [Well-documented][See Documentation]
- Extensively tested
- [SQLCipher][] support via CocoaPods
- Active support at
[StackOverflow](http://stackoverflow.com/questions/tagged/sqlite.swift),
and [Gitter Chat Room](https://gitter.im/stephencelis/SQLite.swift)
(_experimental_)
[SQLCipher]: https://www.zetetic.net/sqlcipher/
[Full-text search]: Documentation/Index.md#full-text-search
[See Documentation]: Documentation/Index.md#sqliteswift-documentation
## Usage
```swift
import SQLite
let db = try Connection("path/to/db.sqlite3")
let users = Table("users")
let id = Expression("id")
let name = Expression("name")
let email = Expression("email")
try db.run(users.create { t in
t.column(id, primaryKey: true)
t.column(name)
t.column(email, unique: true)
})
// CREATE TABLE "users" (
// "id" INTEGER PRIMARY KEY NOT NULL,
// "name" TEXT,
// "email" TEXT NOT NULL UNIQUE
// )
let insert = users.insert(name <- "Alice", email <- "alice@mac.com")
let rowid = try db.run(insert)
// INSERT INTO "users" ("name", "email") VALUES ('Alice', 'alice@mac.com')
for user in try db.prepare(users) {
print("id: \(user[id]), name: \(user[name]), email: \(user[email])")
// id: 1, name: Optional("Alice"), email: alice@mac.com
}
// SELECT * FROM "users"
let alice = users.filter(id == rowid)
try db.run(alice.update(email <- email.replace("mac.com", with: "me.com")))
// UPDATE "users" SET "email" = replace("email", 'mac.com', 'me.com')
// WHERE ("id" = 1)
try db.run(alice.delete())
// DELETE FROM "users" WHERE ("id" = 1)
try db.scalar(users.count) // 0
// SELECT count(*) FROM "users"
```
SQLite.swift also works as a lightweight, Swift-friendly wrapper over the C
API.
```swift
let stmt = try db.prepare("INSERT INTO users (email) VALUES (?)")
for email in ["betty@icloud.com", "cathy@icloud.com"] {
try stmt.run(email)
}
db.totalChanges // 3
db.changes // 1
db.lastInsertRowid // 3
for row in try db.prepare("SELECT id, email FROM users") {
print("id: \(row[0]), email: \(row[1])")
// id: Optional(2), email: Optional("betty@icloud.com")
// id: Optional(3), email: Optional("cathy@icloud.com")
}
try db.scalar("SELECT count(*) FROM users") // 2
```
[Read the documentation][See Documentation] or explore more,
interactively, from the Xcode project’s playground.
For a more comprehensive example, see
[this article][Create a Data Access Layer with SQLite.swift and Swift 2]
and the [companion repository][SQLiteDataAccessLayer2].
[Create a Data Access Layer with SQLite.swift and Swift 2]: http://masteringswift.blogspot.com/2015/09/create-data-access-layer-with.html
[SQLiteDataAccessLayer2]: https://github.com/hoffmanjon/SQLiteDataAccessLayer2/tree/master
## Installation
> _Note:_ Version 0.12 requires Swift 5 (and [Xcode](https://developer.apple.com/xcode/downloads/) 10.2) or greater. Version 0.11.6 requires Swift 4.2 (and [Xcode](https://developer.apple.com/xcode/downloads/) 10.1) or greater.
### Carthage
[Carthage][] is a simple, decentralized dependency manager for Cocoa. To
install SQLite.swift with Carthage:
1. Make sure Carthage is [installed][Carthage Installation].
2. Update your Cartfile to include the following:
```ruby
github "stephencelis/SQLite.swift" ~> 0.12.0
```
3. Run `carthage update` and
[add the appropriate framework][Carthage Usage].
[Carthage]: https://github.com/Carthage/Carthage
[Carthage Installation]: https://github.com/Carthage/Carthage#installing-carthage
[Carthage Usage]: https://github.com/Carthage/Carthage#adding-frameworks-to-an-application
### CocoaPods
[CocoaPods][] is a dependency manager for Cocoa projects. To install
SQLite.swift with CocoaPods:
1. Make sure CocoaPods is [installed][CocoaPods Installation]. (SQLite.swift
requires version 1.6.1 or greater.)
```sh
# Using the default Ruby install will require you to use sudo when
# installing and updating gems.
[sudo] gem install cocoapods
```
2. Update your Podfile to include the following:
```ruby
use_frameworks!
target 'YourAppTargetName' do
pod 'SQLite.swift', '~> 0.12.0'
end
```
3. Run `pod install --repo-update`.
[CocoaPods]: https://cocoapods.org
[CocoaPods Installation]: https://guides.cocoapods.org/using/getting-started.html#getting-started
### Swift Package Manager
The [Swift Package Manager][] is a tool for managing the distribution of
Swift code.
1. Add the following to your `Package.swift` file:
```swift
dependencies: [
.package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.12.0")
]
```
2. Build your project:
```sh
$ swift build
```
[Swift Package Manager]: https://swift.org/package-manager
### Manual
To install SQLite.swift as an Xcode sub-project:
1. Drag the **SQLite.xcodeproj** file into your own project.
([Submodule][], clone, or [download][] the project first.)
2. In your target’s **General** tab, click the **+** button under **Linked
Frameworks and Libraries**.
3. Select the appropriate **SQLite.framework** for your platform.
4. **Add**.
Some additional steps are required to install the application on an actual
device:
5. In the **General** tab, click the **+** button under **Embedded
Binaries**.
6. Select the appropriate **SQLite.framework** for your platform.
7. **Add**.
[Xcode]: https://developer.apple.com/xcode/downloads/
[Submodule]: http://git-scm.com/book/en/Git-Tools-Submodules
[download]: https://github.com/stephencelis/SQLite.swift/archive/master.zip
## Communication
[See the planning document] for a roadmap and existing feature requests.
[Read the contributing guidelines][]. The _TL;DR_ (but please; _R_):
- Need **help** or have a **general question**? [Ask on Stack
Overflow][] (tag `sqlite.swift`).
- Found a **bug** or have a **feature request**? [Open an issue][].
- Want to **contribute**? [Submit a pull request][].
[See the planning document]: /Documentation/Planning.md
[Read the contributing guidelines]: ./CONTRIBUTING.md#contributing
[Ask on Stack Overflow]: http://stackoverflow.com/questions/tagged/sqlite.swift
[Open an issue]: https://github.com/stephencelis/SQLite.swift/issues/new
[Submit a pull request]: https://github.com/stephencelis/SQLite.swift/fork
## Author
- [Stephen Celis](mailto:stephen@stephencelis.com)
([@stephencelis](https://twitter.com/stephencelis))
## License
SQLite.swift is available under the MIT license. See [the LICENSE
file](./LICENSE.txt) for more information.
## Related
These projects enhance or use SQLite.swift:
- [SQLiteMigrationManager.swift][] (inspired by
[FMDBMigrationManager][])
## Alternatives
Looking for something else? Try another Swift wrapper (or [FMDB][]):
- [Camembert](https://github.com/remirobert/Camembert)
- [GRDB](https://github.com/groue/GRDB.swift)
- [SQLiteDB](https://github.com/FahimF/SQLiteDB)
- [Squeal](https://github.com/nerdyc/Squeal)
- [SwiftData](https://github.com/ryanfowler/SwiftData)
- [SwiftSQLite](https://github.com/chrismsimpson/SwiftSQLite)
[Swift]: https://swift.org/
[SQLite3]: http://www.sqlite.org
[SQLite.swift]: https://github.com/stephencelis/SQLite.swift
[TravisBadge]: https://img.shields.io/travis/stephencelis/SQLite.swift/master.svg?style=flat
[TravisLink]: https://travis-ci.org/stephencelis/SQLite.swift
[CocoaPodsVersionBadge]: https://cocoapod-badges.herokuapp.com/v/SQLite.swift/badge.png
[CocoaPodsVersionLink]: http://cocoadocs.org/docsets/SQLite.swift
[PlatformBadge]: https://cocoapod-badges.herokuapp.com/p/SQLite.swift/badge.png
[PlatformLink]: http://cocoadocs.org/docsets/SQLite.swift
[CartagheBadge]: https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat
[CarthageLink]: https://github.com/Carthage/Carthage
[GitterBadge]: https://badges.gitter.im/stephencelis/SQLite.swift.svg
[GitterLink]: https://gitter.im/stephencelis/SQLite.swift
[Swift5Badge]: https://img.shields.io/badge/swift-5-orange.svg?style=flat
[Swift5Link]: https://developer.apple.com/swift/
[SQLiteMigrationManager.swift]: https://github.com/garriguv/SQLiteMigrationManager.swift
[FMDB]: https://github.com/ccgus/fmdb
[FMDBMigrationManager]: https://github.com/layerhq/FMDBMigrationManager
---
### Pods/SQLite.Swift/LICENSE (Pods/SQLite.swift/LICENSE.txt)
(The MIT License)
Copyright (c) 2014-2015 Stephen Celis ()
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
---
### Pods/SwiftyDropbox/README (Pods/SwiftyDropbox/README.md)
# Dropbox for Swift
The Official Dropbox Swift SDK for integrating with Dropbox [API v2](https://www.dropbox.com/developers/documentation/http/documentation) on iOS or macOS.
Full documentation [here](http://dropbox.github.io/SwiftyDropbox/api-docs/latest/).
---
## Table of Contents
* [System requirements](#system-requirements)
* [Get started](#get-started)
* [Register your application](#register-your-application)
* [Obtain an OAuth 2.0 token](#obtain-an-oauth-20-token)
* [SDK distribution](#sdk-distribution)
* [CocoaPods](#cocoapods)
* [Carthage](#carthage)
* [Manually add subproject](#manually-add-subproject)
* [Configure your project](#configure-your-project)
* [Application `.plist` file](#application-plist-file)
* [Handling the authorization flow](#handling-the-authorization-flow)
* [Initialize a `DropboxClient` instance](#initialize-a-dropboxclient-instance)
* [Begin the authorization flow](#begin-the-authorization-flow)
* [Handle redirect back into SDK](#handle-redirect-back-into-sdk)
* [Try some API requests](#try-some-api-requests)
* [Dropbox client instance](#dropbox-client-instance)
* [Handle the API response](#handle-the-api-response)
* [Request types](#request-types)
* [RPC-style request](#rpc-style-request)
* [Upload-style request](#upload-style-request)
* [Download-style request](#download-style-request)
* [Handling responses and errors](#handling-responses-and-errors)
* [Route-specific errors](#route-specific-errors)
* [Generic network request errors](#generic-network-request-errors)
* [Response handling edge cases](#response-handling-edge-cases)
* [Customizing network calls](#customizing-network-calls)
* [Configure network client](#configure-network-client)
* [Specify API call response queue](#specify-api-call-response-queue)
* [`DropboxClientsManager` class](#dropboxclientsmanager-class)
* [Single Dropbox user case](#single-dropbox-user-case)
* [Multiple Dropbox user case](#multiple-dropbox-user-case)
* [Examples](#examples)
* [Documentation](#documentation)
* [Stone](#stone)
* [Modifications](#modifications)
* [Bugs](#bugs)
---
## System requirements
- iOS 9.0+
- macOS 10.11+
- Xcode 10.0+
- Swift 4.2+
## Get Started
### Register your application
Before using this SDK, you should register your application in the [Dropbox App Console](https://dropbox.com/developers/apps). This creates a record of your app with Dropbox that will be associated with the API calls you make.
### Obtain an OAuth 2.0 token
All requests need to be made with an OAuth 2.0 access token. An OAuth token represents an authenticated link between a Dropbox app and
a Dropbox user account or team.
Once you've created an app, you can go to the App Console and manually generate an access token to authorize your app to access your own Dropbox account.
Otherwise, you can obtain an OAuth token programmatically using the SDK's pre-defined auth flow. For more information, [see below](https://github.com/dropbox/SwiftyDropbox#handling-the-authorization-flow).
---
## SDK distribution
You can integrate the Dropbox Swift SDK into your project using one of several methods.
### CocoaPods
To use [CocoaPods](http://cocoapods.org), a dependency manager for Cocoa projects, you should first install it using the following command:
```bash
$ gem install cocoapods
```
Then navigate to the directory that contains your project and create a new file called `Podfile`. You can do this either with `pod init`, or open an existing Podfile, and then add `pod 'SwiftyDropbox'` to the main loop. Your Podfile should look something like this:
```ruby
use_frameworks!
target '' do
pod 'SwiftyDropbox'
end
```
Then, run the following command to install the dependency:
```bash
$ pod install
```
Once your project is integrated with the Dropbox Swift SDK, you can pull SDK updates using the following command:
```bash
$ pod update
```
**Note**: SwiftyDropbox requires CocoaPods 1.0.0+ when using Alamofire 4.0.0+. Because of this requirement, the CocoaPods App (which uses CocoaPods 1.0.0) cannot be used.
---
### Carthage
You can also integrate the Dropbox Swift SDK into your project using [Carthage](https://github.com/Carthage/Carthage), a decentralized dependency manager for Cocoa. Carthage offers more flexibility than CocoaPods, but requires some additional work. You can install Carthage (with Xcode 7+) via [Homebrew](http://brew.sh/):
```bash
brew update
brew install carthage
```
To install the Dropbox Swift SDK via Carthage, you need to create a `Cartfile` in your project with the following contents:
```
# SwiftyDropbox
github "https://github.com/dropbox/SwiftyDropbox" ~> 5.0.0
```
Then, run the following command to install the dependency to checkout and build the Dropbox Swift SDK repository:
##### iOS
```bash
carthage update --platform iOS
```
In the Project Navigator in Xcode, select your project, and then navigate to **General** > **Linked Frameworks and Libraries**, then drag and drop `SwiftyDropbox.framework` (from `Carthage/Build/iOS`). Then to add SwiftyDropbox's Alamofire dependency, drag and drop `Alamofire.framework` (from `Carthage/Build/iOS`) to **Linked Frameworks and Libraries**, as well.
Then, navigate to **Build Phases** > **+** > **New Run Script Phase**. In the newly-created **Run Script** section, add the following code to the script body area (beneath the "Shell" box):
```
/usr/local/bin/carthage copy-frameworks
```
Then, navigate to the **Input Files** section and add the following path:
```
$(SRCROOT)/Carthage/Build/iOS/SwiftyDropbox.framework
$(SRCROOT)/Carthage/Build/iOS/Alamofire.framework
```
##### macOS
```bash
carthage update --platform Mac
```
In the Project Navigator in Xcode, select your project, and then navigate to **General** > **Embedded Binaries**, then drag and drop `SwiftyDropbox.framework` (from `Carthage/Build/Mac`). Then to add SwiftyDropbox's Alamofire dependency, drag and drop `Alamofire.framework` (from `Carthage/Build/Mac`) to **Linked Frameworks and Libraries**, as well.
Then navigate to **Build Phases** > **+** > **New Copy Files Phase**. In the newly-created **Copy Files** section, click the **Destination** drop-down menu and select **Products Directory**, then drag and drop `SwiftyDropbox.framework.dSYM` (from `Carthage/Build/Mac`).
---
### Manually add subproject
Finally, you can also integrate the Dropbox Swift SDK into your project manually with the help of Carthage. Please take the following steps:
Create a `Cartfile` in your project with the same contents as the Cartfile listed in the [Carthage](#carthage) section of the README.
Then, run the following command to checkout and build the Dropbox Swift SDK repository:
##### iOS
```bash
carthage update --platform iOS
```
Once you have checked-out out all the necessary code via Carthage, drag the `Carthage/Checkouts/SwiftyDropbox/Source/SwiftyDropbox/SwiftyDropbox.xcodeproj` file into your project as a subproject.
Then, in the Project Navigator in Xcode, select your project, and then navigate to your project's build target > **General** > **Embedded Binaries** > **+** and then add the `SwiftyDropbox.framework` file for the iOS platform.
Finally, to retrieve SwiftyDropbox's Alamofire dependency, drag the `Carthage/Checkouts/Alamofire/Alamofire.xcodeproj` project into your project (as you did with `SwiftyDropbox.xcodeproj`). Then, in the Project Navigator in Xcode, select your project, and then navigate to your project's build target > **General** > **Linked Frameworks and Libraries** > **+** and then add the `Alamofire.framework` file for the iOS platform.
##### macOS
```bash
carthage update --platform Mac
```
Once you have checked-out out all the necessary code via Carthage, drag the `Carthage/Checkouts/SwiftyDropbox/Source/SwiftyDropbox/SwiftyDropbox.xcodeproj` file into your project as a subproject.
Then, in the Project Navigator in Xcode, select your project, and then navigate to your project's build target > **General** > **Embedded Binaries** > **+** and then add the `SwiftyDropbox.framework` file for the macOS platform.
Finally, to retrieve SwiftyDropbox's Alamofire dependency, drag the `Carthage/Checkouts/Alamofire/Alamofire.xcodeproj` project into your project (as you did with `SwiftyDropbox.xcodeproj`). Then, in the Project Navigator in Xcode, select your project, and then navigate to your project's build target > **General** > **Linked Frameworks and Libraries** > **+** and then add the `Alamofire.framework` file for the macOS platform.
---
## Configure your project
Once you have integrated the Dropbox Swift SDK into your project, there are a few additional steps to take before you can begin making API calls.
### Application `.plist` file
If you are compiling on iOS SDK 9.0, you will need to modify your application's `.plist` to handle Apple's [new security changes](https://developer.apple.com/videos/wwdc/2015/?id=703) to the `canOpenURL` function. You should
add the following code to your application's `.plist` file:
```
LSApplicationQueriesSchemes
dbapi-8-emm
dbapi-2
```
This allows the Swift SDK to determine if the official Dropbox iOS app is installed on the current device. If it is installed, then the official Dropbox iOS app can be used to programmatically obtain an OAuth 2.0 access token.
Additionally, your application needs to register to handle a unique Dropbox URL scheme for redirect following completion of the OAuth 2.0 authorization flow. This URL scheme should have the format `db-`, where `` is your
Dropbox app's app key, which can be found in the [App Console](https://dropbox.com/developers/apps).
You should add the following code to your `.plist` file (but be sure to replace `` with your app's app key):
```
CFBundleURLTypes
CFBundleURLSchemes
db-
CFBundleURLName
```
After you've made the above changes, your application's `.plist` file should look something like this:
---
### Handling the authorization flow
There are three methods to programmatically retrieve an OAuth 2.0 access token:
* **Direct auth** (iOS only): This launches the official Dropbox iOS app (if installed), authenticates via the official app, then redirects back into the SDK
* **Safari view controller auth** (iOS only): This launches a `SFSafariViewController` to facillitate the auth flow. This is desirable because it is safer for the end-user, and pre-existing session data can be used to avoid requiring the user to re-enter their Dropbox credentials.
* **Redirect to external browser** (macOS only): This launches the user's default browser to facillitate the auth flow. This is also desirable because it is safer for the end-user, and pre-existing session data can be used to avoid requiring the user to re-enter their Dropbox credentials.
To facilitate the above authorization flows, you should take the following steps:
---
#### Initialize a `DropboxClient` instance
From your application delegate:
##### iOS
```Swift
import SwiftyDropbox
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
DropboxClientsManager.setupWithAppKey("")
return true
}
```
##### macOS
```Swift
import SwiftyDropbox
func applicationDidFinishLaunching(_ aNotification: Notification) {
DropboxClientsManager.setupWithAppKeyDesktop("")
}
```
---
#### Begin the authorization flow
You can commence the auth flow by calling `authorizeFromController:controller:openURL` method in your application's
view controller.
From your view controller:
##### iOS
```Swift
import SwiftyDropbox
func myButtonInControllerPressed() {
DropboxClientsManager.authorizeFromController(UIApplication.shared,
controller: self,
openURL: { (url: URL) -> Void in
UIApplication.shared.openURL(url)
})
}
```
##### macOS
```Swift
import SwiftyDropbox
func myButtonInControllerPressed() {
DropboxClientsManager.authorizeFromController(sharedWorkspace: NSWorkspace.shared,
controller: self,
openURL: { (url: URL) -> Void in
NSWorkspace.shared.open(url)
})
}
```
Beginning the authentication flow via in-app webview will launch a window like this:
---
#### Handle redirect back into SDK
To handle the redirection back into the Swift SDK once the authentication flow is complete, you should add the following code in your application's delegate:
##### iOS
```Swift
import SwiftyDropbox
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
if let authResult = DropboxClientsManager.handleRedirectURL(url) {
switch authResult {
case .success:
print("Success! User is logged into Dropbox.")
case .cancel:
print("Authorization flow was manually canceled by user!")
case .error(_, let description):
print("Error: \(description)")
}
}
return true
}
```
##### macOS
```Swift
import SwiftyDropbox
func applicationDidFinishLaunching(_ aNotification: Notification) {
...... // code outlined above goes here
NSAppleEventManager.shared().setEventHandler(self,
andSelector: #selector(handleGetURLEvent),
forEventClass: AEEventClass(kInternetEventClass),
andEventID: AEEventID(kAEGetURL))
}
func handleGetURLEvent(_ event: NSAppleEventDescriptor?, replyEvent: NSAppleEventDescriptor?) {
if let aeEventDescriptor = event?.paramDescriptor(forKeyword: AEKeyword(keyDirectObject)) {
if let urlStr = aeEventDescriptor.stringValue {
let url = URL(string: urlStr)!
if let authResult = DropboxClientsManager.handleRedirectURL(url) {
switch authResult {
case .success:
print("Success! User is logged into Dropbox.")
case .cancel:
print("Authorization flow was manually canceled by user!")
case .error(_, let description):
print("Error: \(description)")
}
}
// this brings your application back the foreground on redirect
NSApp.activate(ignoringOtherApps: true)
}
}
}
```
After the end user signs in with their Dropbox login credentials via the in-app webview, they will see a window like this:
If they press **Allow** or **Cancel**, the `db-` redirect URL will be launched from the webview, and will be handled in your application
delegate's `application:handleOpenURL` method, from which the result of the authorization can be parsed.
Now you're ready to begin making API requests!
---
## Try some API requests
Once you have obtained an OAuth 2.0 token, you can try some API v2 calls using the Swift SDK.
### Dropbox client instance
Start by creating a reference to the `DropboxClient` or `DropboxTeamClient` instance that you will use to make your API calls.
```Swift
import SwiftyDropbox
// Reference after programmatic auth flow
let client = DropboxClientsManager.authorizedClient
```
or
```Swift
import SwiftyDropbox
// Initialize with manually retrieved auth token
let client = DropboxClient(accessToken: "")
```
---
### Handle the API response
The Dropbox [User API](https://www.dropbox.com/developers/documentation/http/documentation) and [Business API](https://www.dropbox.com/developers/documentation/http/teams) have three types of requests: RPC, Upload and Download.
The response handlers for each request type are similar to one another. The arguments for the handler blocks are as follows:
* **route result type** (`Void` if the route does not have a return type)
* **network error** (either a route-specific error or generic network error)
* **output content** (`URL` / `Data` reference to downloaded output for Download-style endpoints only)
Note: Response handlers are required for all endpoints. Progress handlers, on the other hand, are optional for all endpoints.
---
### Request types
#### RPC-style request
```Swift
client.files.createFolder(path: "/test/path/in/Dropbox/account").response { response, error in
if let response = response {
print(response)
} else if let error = error {
print(error)
}
}
```
---
#### Upload-style request
```Swift
let fileData = "testing data example".data(using: String.Encoding.utf8, allowLossyConversion: false)!
let request = client.files.upload(path: "/test/path/in/Dropbox/account", input: TestData.fileData)
.response { response, error in
if let response = response {
print(response)
} else if let error = error {
print(error)
}
}
.progress { progressData in
print(progressData)
}
// in case you want to cancel the request
if someConditionIsSatisfied {
request.cancel()
}
```
---
#### Download-style request
```Swift
// Download to URL
let fileManager = FileManager.default
let directoryURL = fileManager.urls(for: .documentDirectory, in: .userDomainMask)[0]
let destURL = directoryURL.appendingPathComponent("myTestFile")
let destination: (URL, HTTPURLResponse) -> URL = { temporaryURL, response in
return destURL
}
client.files.download(path: "/test/path/in/Dropbox/account", overwrite: true, destination: destination)
.response { response, error in
if let response = response {
print(response)
} else if let error = error {
print(error)
}
}
.progress { progressData in
print(progressData)
}
// Download to Data
client.files.download(path: "/test/path/in/Dropbox/account")
.response { response, error in
if let response = response {
let responseMetadata = response.0
print(responseMetadata)
let fileContents = response.1
print(fileContents)
} else if let error = error {
print(error)
}
}
.progress { progressData in
print(progressData)
}
```
---
### Handling responses and errors
Dropbox API v2 deals largely with two data types: **structs** and **unions**. Broadly speaking, most route **arguments** are struct types and most route **errors** are union types.
**NOTE:** In this context, "structs" and "unions" are terms specific to the Dropbox API, and not to any of the languages that are used to query the API, so you should avoid thinking of them in terms of their Swift definitions.
**Struct types** are "traditional" object types, that is, composite types made up of a collection of one or more instance fields. All public instance fields are accessible at runtime, regardless of runtime state.
**Union types**, on the other hand, represent a single value that can take on multiple value types, depending on state. We capture all of these different type scenarios under one "union object", but that object will exist only as one type at runtime. Each union state type, or **tag**, may have an associated value (if it doesn't, the union state type is said to be **void**). Associated value types can either be primitives, structs or unions. Although the Swift SDK represents union types as objects with multiple instance fields, at most one instance field is accessible at runtime, depending on the tag state of the union.
For example, the [/delete](https://www.dropbox.com/developers/documentation/http/documentation#files-delete) endpoint returns an error, `Files.DeleteError`, which is a union type. The `Files.DeleteError` union can take on two different tag states: `path_lookup`
(if there is a problem looking up the path) or `path_write` (if there is a problem writing -- or in this case deleting -- to the path). Here, both tag states have non-void associated values (of types `Files.LookupError` and `Files.WriteError`, respectively).
In this way, one union object is able to capture a multitude of scenarios, each of which has their own value type.
To properly handle union types, you should pass each union through a switch statement, and check each possible tag state associated with the union. Once you have determined the current tag state of the union, you can then access the value associated with that tag state (provided there exists an associated value type, i.e., it's not **void**).
---
#### Route-specific errors
```
/* Detailed source-code truncated for AI context efficiency. */
```
---
#### Generic network request errors
In the case of a network error, errors are either specific to the endpoint (as shown above) or more generic errors.
To determine if an error is route-specific or not, the error object should be cast as a `CallError`, and depending on the type of error, handled in the appropriate switch statement.
```
/* Detailed source-code truncated for AI context efficiency. */
```
---
#### Response handling edge cases
Some routes return union types as result types, so you should be prepared to handle these results in the same way that you handle union route errors. Please consult the [documentation](https://www.dropbox.com/developers/documentation/http/documentation)
for each endpoint that you use to ensure you are properly handling the route's response type.
A few routes return result types that are **datatypes with subtypes**, that is, structs that can take on multiple state types like unions.
For example, the [/delete](https://www.dropbox.com/developers/documentation/http/documentation#files-delete) endpoint returns a generic `Metadata` type, which can exist either as a `FileMetadata` struct, a `FolderMetadata` struct, or a `DeletedMetadata` struct.
To determine at runtime which subtype the `Metadata` type exists as, pass the object through a switch statement, and check for each possible class, with the result casted accordingly. See below:
```Swift
client.files.deleteV2(path: "/test/path/in/Dropbox/account").response { response, error in
if let response = response {
switch response {
case let fileMetadata as Files.FileMetadata:
print("File metadata: \(fileMetadata)")
case let folderMetadata as Files.FolderMetadata:
print("Folder metadata: \(folderMetadata)")
case let deletedMetadata as Files.DeletedMetadata:
print("Deleted entity's metadata: \(deletedMetadata)")
}
} else if let error = error {
switch error as CallError {
case .routeError(let boxed, let userMessage, let errorSummary, let requestId):
// a route-specific error occurred
// see handling above
case .internalServerError(let code, let message, let requestId):
....
....
// a not route-specific error occurred
// see handling above
....
....
....
}
}
}
```
This `Metadata` object is known as a **datatype with subtypes** in our API v2 documentation.
Datatypes with subtypes are a way combining structs and unions. Datatypes with subtypes are struct objects that contain a tag, which specifies which subtype the object exists as at runtime. The reason we have this construct, as with unions, is so we can capture a multitude of scenarios with one object.
In the above example, the `Metadata` type can exists as `FileMetadata`, `FolderMetadata` or `DeleteMetadata`. Each of these types have common instances fields like "name" (the name for the file, folder or deleted type), but also instance fields that are specific to the particular subtype. In order to leverage inheritance, we set a common supertype called `Metadata` which captures all of the common instance fields, but also has a tag instance field, which specifies which subtype the object currently exists as.
In this way, datatypes with subtypes are a hybrid of structs and unions. Only a few routes return result types like this.
---
### Customizing network calls
#### Configure network client
It is possible to configure the networking client used by the SDK to make API requests. You can supply custom fields like a custom user agent or custom delegates to manage response handler code, or a custom server trust policy. See below:
##### iOS
```Swift
import SwiftyDropbox
let transportClient = DropboxTransportClient(accessToken: "",
baseHosts: nil,
userAgent: "CustomUserAgent",
selectUser: nil,
sessionDelegate: mySessionDelegate,
backgroundSessionDelegate: myBackgroundSessionDelegate,
serverTrustPolicyManager: myServerTrustPolicyManager)
DropboxClientsManager.setupWithAppKey("", transportClient: transportClient)
```
##### macOS
```Swift
import SwiftyDropbox
let transportClient = DropboxTransportClient(accessToken: "",
baseHosts: nil,
userAgent: "CustomUserAgent",
selectUser: nil,
sessionDelegate: mySessionDelegate,
backgroundSessionDelegate: myBackgroundSessionDelegate,
serverTrustPolicyManager: myServerTrustPolicyManager)
DropboxClientsManager.setupWithAppKeyDesktop("", transportClient: transportClient)
```
#### Specify API call response queue
By default, response/progress handler code runs on the main thread. You can set a custom response queue for each API call that you make via the `response` method, in the event want your response/progress handler code to run on a different thread:
```Swift
let client = DropboxClientsManager.authorizedClient!
client.files.listFolder(path: "").response(queue: DispatchQueue(label: "MyCustomSerialQueue")) { response, error in
if let result = response {
print(Thread.current) // Output: {number = 4, name = (null)}
print(Thread.main) // Output: {number = 1, name = (null)}
print(result)
}
}
```
---
### `DropboxClientsManager` class
The Swift SDK includes a convenience class, `DropboxClientsManager`, for integrating the different functions of the SDK into one class.
#### Single Dropbox user case
For most apps, it is reasonable to assume that only one Dropbox account (and access token) needs to be managed at a time. In this case, the `DropboxClientsManager` flow looks like this:
* call `setupWithAppKey`/`setupWithAppKeyDesktop` (or `setupWithTeamAppKey`/`setupWithTeamAppKeyDesktop`) in integrating app's app delegate
* client manager determines whether any access tokens are stored -- if any exist, one token is arbitrarily chosen to use
* if no token is found, call `authorizeFromController`/`authorizeFromControllerDesktop` to initiate the OAuth flow
* if auth flow is initiated, call `handleRedirectURL` (or `handleRedirectURLTeam`) in integrating app's app delegate to handle auth redirect back into the app and store the retrieved access token (using a `DropboxOAuthManager` instance)
* client manager instantiates a `DropboxTransportClient` (if not supplied by the user)
* client manager instantiates a `DropboxClient` (or `DropboxTeamClient`) with the transport client as a field
The `DropboxClient` (or `DropboxTeamClient`) is then used to make all of the desired API calls.
* call `unlinkClients` to logout Dropbox user and clear all access tokens
#### Multiple Dropbox user case
For some apps, it is necessary to manage more than one Dropbox account (and access token) at a time. In this case, the `DropboxClientsManager` flow looks like this:
* access token uids are managed by the app that is integrating with the SDK for later lookup
* call `setupWithAppKeyMultiUser`/`setupWithAppKeyMultiUserDesktop` (or `setupWithTeamAppKeyMultiUser`/`setupWithTeamAppKeyMultiUserDesktop`) in integrating app's app delegate
* client manager determines whether an access token is stored with the`tokenUid` as a key -- if one exists, this token is chosen to use
* if no token is found, call `authorizeFromController`/`authorizeFromControllerDesktop` to initiate the OAuth flow
* if auth flow is initiated, call `handleRedirectURL` (or `handleRedirectURLTeam`) in integrating app's app delegate to handle auth redirect back into the app and store the retrieved access token (using a `DropboxOAuthManager` instance)
* at this point, the app that is integrating with the SDK should persistently save the `tokenUid` from the `DropboxAccessToken` field of the `DropboxOAuthResult` object returned from the `handleRedirectURL` (or `handleRedirectURLTeam`) method
* `tokenUid` can be reused either to authorize a new user mid-way through an app's lifecycle via `reauthorizeClient` (or `reauthorizeTeamClient`) or when the app initially launches via `setupWithAppKeyMultiUser`/`setupWithAppKeyMultiUserDesktop` (or `setupWithTeamAppKeyMultiUser`/`setupWithTeamAppKeyMultiUserDesktop`)
* client manager instantiates a `DropboxTransportClient` (if not supplied by the user)
* client manager instantiates a `DropboxClient` (or `DropboxTeamClient`) with the transport client as a field
The `DropboxClient` (or `DropboxTeamClient`) is then used to make all of the desired API calls.
* call `resetClients` to logout Dropbox user but not clear any access tokens
* if specific access tokens need to be removed, use the `clearStoredAccessToken` method in `DropboxOAuthManager`
---
## Examples
* [PhotoWatch](https://github.com/dropbox/PhotoWatch) - View photos from your Dropbox. Supports Apple Watch.
---
## Documentation
* [Dropbox API v2 Swift SDK](http://dropbox.github.io/SwiftyDropbox/api-docs/latest/)
* [Dropbox API v2](https://www.dropbox.com/developers/documentation/http/documentation)
---
## Stone
All of our routes and data types are auto-generated using a framework called [Stone](https://github.com/dropbox/stone).
The `stone` repo contains all of the Swift specific generation logic, and the `spec` repo contains the language-neutral API endpoint specifications which serve
as input to the language-specific generators.
---
## Modifications
If you're interested in modifying the SDK codebase, you should take the following steps:
* clone this GitHub repository to your local filesystem
* run `git submodule init` and then `git submodule update`
* navigate to `TestSwifty_[iOS|macOS]`
* check the CocoaPods version installed (via `pod --version`) is same as "locked" in `TestSwifty_[iOS|macOS]/Podfile.lock`
* run `pod install`
* open `TestSwifty_[iOS|macOS]/TestSwifty_[iOS|macOS].xcworkspace` in Xcode
* implement your changes to the SDK source code.
To ensure your changes have not broken any existing functionality, you can run a series of integration tests:
* create a new app on https://www.dropbox.com/developers/apps/, with "Full Dropbox" access. Note the App key
* open Info.plist and configure the "URL types > Item 0 (Editor) > URL Schemes > Item 0" key to db-"App key"
* open AppDelegate.swift and replace "FULL_DROPBOX_APP_KEY" with the App key as well
* run the test app on your device and follow the on-screen instructions
---
## Bugs
Please post any bugs to the [issue tracker](https://github.com/dropbox/SwiftyDropbox/issues) found on the project's GitHub page.
Please include the following with your issue:
- a description of what is not working right
- sample code to help replicate the issue
Thank you!
---
### Resources/Profanity (Resources/Profanity.txt)
2g1c
2 girls 1 cup
acrotomophilia
anal
anilingus
anus
arsehole
ass
asshole
assmunch
auto erotic
autoerotic
babeland
baby batter
ball gag
ball gravy
ball kicking
ball licking
ball sack
ball sucking
bangbros
bareback
barely legal
barenaked
bastardo
bastinado
bbw
bdsm
beaver cleaver
beaver lips
bestiality
bi curious
big black
big breasts
big knockers
big tits
bimbos
birdlock
bitch
black cock
blonde action
blonde on blonde action
blow j
blow your l
blue waffle
blumpkin
bollocks
bondage
boner
boob
boobs
booty call
brown showers
brunette action
bukkake
bulldyke
bullet vibe
bung hole
bunghole
busty
butt
buttcheeks
butthole
camel toe
camgirl
camslut
camwhore
carpet muncher
carpetmuncher
chocolate rosebuds
circlejerk
cleveland steamer
clit
clitoris
clover clamps
clusterfuck
cock
cocks
coprolagnia
coprophilia
cornhole
cum
cumming
cunnilingus
cunt
darkie
date rape
daterape
deep throat
deepthroat
dick
dildo
dirty pillows
dirty sanchez
dog style
doggie style
doggiestyle
doggy style
doggystyle
dolcett
domination
dominatrix
dommes
donkey punch
double dong
double penetration
dp action
eat my ass
ecchi
ejaculation
erotic
erotism
escort
ethical slut
eunuch
faggot
fecal
felch
fellatio
feltch
female squirting
femdom
figging
fingering
fisting
foot fetish
footjob
frotting
fuck
fucker
fucking
fuck buttons
fudge packer
fudgepacker
futanari
g-spot
gang bang
gay sex
genitals
giant cock
girl on
girl on top
girls gone wild
goatcx
goatse
gokkun
golden shower
goo girl
goodpoop
goregasm
grope
group sex
guro
hand job
handjob
hard core
hardcore
hitler
hentai
homoerotic
honkey
hooker
hot chick
how to kill
how to murder
huge fat
humping
incest
intercourse
jack off
jail bait
jailbait
jerk off
jew
jews
jigaboo
jiggaboo
jiggerboo
jizz
juggs
kill
kike
kinbaku
kinkster
kinky
knobbing
leather restraint
leather straight jacket
lemon party
lolita
lovemaking
make me come
male squirting
masturbate
menage a trois
milf
missionary position
motherfucker
mound of venus
mr hands
muff diver
muffdiving
murder
nambla
nawashi
negro
neonazi
nig nog
nigga
nigger
nimphomania
nipple
nipples
nsfw images
nude
nudity
nympho
nymphomania
octopussy
omorashi
one cup two girls
one guy one jar
orgasm
orgy
paedophile
panties
panty
pedobear
pedophile
pegging
penis
phone sex
piece of shit
piss pig
pissing
pisspig
playboy
pleasure chest
pole smoker
ponyplay
poof
poop chute
poopchute
porn
porno
pornography
prince albert piercing
pthc
pubes
pussy
queaf
raghead
raging boner
rape
raping
rapist
rectum
reverse cowgirl
rimjob
rimming
rosy palm
rosy palm and her 5 sisters
rusty trombone
s&m
sadism
scat
schlong
scissoring
semen
sex
sexo
sexy
shaved beaver
shaved pussy
shemale
shibari
shit
shitcock
shitty
shota
shrimping
slanteye
slut
smut
snatch
snowballing
sodomize
sodomy
spic
spooge
spread legs
strap on
strapon
strappado
strip club
style doggy
suck
sucks
suicide girls
sultry women
swastika
swinger
tainted love
taste my
tea bagging
threesome
throating
tied up
tight white
tit
tits
titties
titty
tongue in a
topless
tosser
towelhead
tranny
tribadism
tub girl
tubgirl
tushy
twat
twink
twinkie
two girls one cup
undressing
upskirt
urethra play
urophilia
vagina
venus mound
vibrator
violet blue
violet wand
vorarephilia
voyeur
vulva
wank
wet dream
wetback
white power
women rapping
wrapping men
wrinkled starfish
xx
xxx
yaoi
yellow showers
yiffy
zoophilia
---
### Resources/AppIcon.Icon/Icon.Json (Resources/AppIcon.icon/icon.json)
{
"fill" : {
"linear-gradient" : [
"display-p3:0.54510,0.15686,0.96863,1.00000",
"srgb:0.26667,0.00000,0.63137,1.00000"
]
},
"groups" : [
{
"layers" : [
{
"image-name" : "delta_icon.svg",
"name" : "delta_icon",
"position" : {
"scale" : 0.88,
"translation-in-points" : [
0,
0
]
}
},
{
"glass" : false,
"image-name" : "delta_shadow.svg",
"name" : "delta_shadow",
"position" : {
"scale" : 0.88,
"translation-in-points" : [
0,
0
]
}
}
],
"shadow" : {
"kind" : "neutral",
"opacity" : 0.5
},
"translucency" : {
"enabled" : true,
"value" : 0.5
}
}
],
"supported-platforms" : {
"circles" : [
"watchOS"
],
"squares" : "shared"
}
}
---
### Resources/Assets.Xcassets/Contents.Json (Resources/Assets.xcassets/Contents.json)
{
"info" : {
"author" : "xcode",
"version" : 1
}
}
---
### Resources/Assets.Xcassets/BoxArt.Imageset/Contents.Json (Resources/Assets.xcassets/BoxArt.imageset/Contents.json)
{
"images" : [
{
"filename" : "BoxArt.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "BoxArt@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "BoxArt@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
---
### Resources/Assets.Xcassets/BoxArtGlass.Imageset/Contents.Json (Resources/Assets.xcassets/BoxArtGlass.imageset/Contents.json)
{
"images" : [
{
"filename" : "BoxArt_customGlass.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
---
### Resources/Assets.Xcassets/Colors/Contents.Json (Resources/Assets.xcassets/Colors/Contents.json)
{
"info" : {
"author" : "xcode",
"version" : 1
}
}
---
### Resources/Assets.Xcassets/Colors/DarkGray.Colorset/Contents.Json (Resources/Assets.xcassets/Colors/DarkGray.colorset/Contents.json)
{
"info" : {
"version" : 1,
"author" : "xcode"
},
"colors" : [
{
"idiom" : "universal",
"color" : {
"color-space" : "srgb",
"components" : {
"red" : "0.150",
"alpha" : "1.000",
"blue" : "0.150",
"green" : "0.150"
}
}
}
]
}
---
### Resources/Assets.Xcassets/Colors/LightPurple.Colorset/Contents.Json (Resources/Assets.xcassets/Colors/LightPurple.colorset/Contents.json)
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0xFF",
"green" : "0xBC",
"red" : "0xD6"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
---
### Resources/Assets.Xcassets/Colors/Purple.Colorset/Contents.Json (Resources/Assets.xcassets/Colors/Purple.colorset/Contents.json)
{
"info" : {
"version" : 1,
"author" : "xcode"
},
"colors" : [
{
"idiom" : "universal",
"color" : {
"color-space" : "srgb",
"components" : {
"red" : "139",
"alpha" : "1.000",
"blue" : "247",
"green" : "40"
}
}
}
]
}
---
### Resources/Assets.Xcassets/Controller Icons/Contents.Json (Resources/Assets.xcassets/Controller Icons/Contents.json)
{
"info" : {
"author" : "xcode",
"version" : 1
}
}
---
### Resources/Assets.Xcassets/Controller Icons/DS.Imageset/Contents.Json (Resources/Assets.xcassets/Controller Icons/DS.imageset/Contents.json)
{
"images" : [
{
"filename" : "DS.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"template-rendering-intent" : "template"
}
}
---
### Resources/Assets.Xcassets/Controller Icons/GBA.Imageset/Contents.Json (Resources/Assets.xcassets/Controller Icons/GBA.imageset/Contents.json)
{
"images" : [
{
"filename" : "GBA.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"template-rendering-intent" : "template"
}
}
---
### Resources/Assets.Xcassets/Controller Icons/GBC.Imageset/Contents.Json (Resources/Assets.xcassets/Controller Icons/GBC.imageset/Contents.json)
{
"images" : [
{
"filename" : "GBC.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"template-rendering-intent" : "template"
}
}
---
### Resources/Assets.Xcassets/Controller Icons/Genesis.Imageset/Contents.Json (Resources/Assets.xcassets/Controller Icons/Genesis.imageset/Contents.json)
{
"images" : [
{
"filename" : "Genesis.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"template-rendering-intent" : "template"
}
}
---
### Resources/Assets.Xcassets/Controller Icons/N64.Imageset/Contents.Json (Resources/Assets.xcassets/Controller Icons/N64.imageset/Contents.json)
{
"images" : [
{
"filename" : "N64.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"template-rendering-intent" : "template"
}
}
---
### Resources/Assets.Xcassets/Controller Icons/NES.Imageset/Contents.Json (Resources/Assets.xcassets/Controller Icons/NES.imageset/Contents.json)
{
"images" : [
{
"filename" : "NES.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"template-rendering-intent" : "template"
}
}
---
### Resources/Assets.Xcassets/Controller Icons/SNES.Imageset/Contents.Json (Resources/Assets.xcassets/Controller Icons/SNES.imageset/Contents.json)
{
"images" : [
{
"filename" : "SNES.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"template-rendering-intent" : "template"
}
}
---
### Resources/Assets.Xcassets/Custom Symbols/Contents.Json (Resources/Assets.xcassets/Custom Symbols/Contents.json)
{
"info" : {
"author" : "xcode",
"version" : 1
}
}
---
### Resources/Assets.Xcassets/Custom Symbols/Gamecontroller.Slash.Symbolset/Contents.Json (Resources/Assets.xcassets/Custom Symbols/gamecontroller.slash.symbolset/Contents.json)
{
"info" : {
"author" : "xcode",
"version" : 1
},
"symbols" : [
{
"filename" : "gamecontroller.slash.svg",
"idiom" : "universal"
}
]
}
---
### Resources/Assets.Xcassets/Delta.Imageset/Contents.Json (Resources/Assets.xcassets/Delta.imageset/Contents.json)
{
"images" : [
{
"filename" : "DeltaSplash.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true
}
}
---
### Resources/Assets.Xcassets/DeltaPlaceholder.Imageset/Contents.Json (Resources/Assets.xcassets/DeltaPlaceholder.imageset/Contents.json)
{
"images" : [
{
"filename" : "DeltaPlaceholder.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "DeltaPlaceholder@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "DeltaPlaceholder@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
---
### Resources/Assets.Xcassets/DS Home Screen.Imageset/Contents.Json (Resources/Assets.xcassets/DS Home Screen.imageset/Contents.json)
{
"images" : [
{
"filename" : "DS.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
---
### Resources/Assets.Xcassets/Link.Imageset/Contents.Json (Resources/Assets.xcassets/Link.imageset/Contents.json)
{
"images" : [
{
"idiom" : "universal",
"filename" : "Link@1x.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "Link@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "Link@3x.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
},
"properties" : {
"template-rendering-intent" : "template"
}
}
---
### Resources/Assets.Xcassets/LinkOut.Symbolset/Contents.Json (Resources/Assets.xcassets/LinkOut.symbolset/Contents.json)
{
"info" : {
"author" : "xcode",
"version" : 1
},
"symbols" : [
{
"filename" : "LinkOut.svg",
"idiom" : "universal"
}
]
}
---
### Resources/Assets.Xcassets/Pause Icons/Contents.Json (Resources/Assets.xcassets/Pause Icons/Contents.json)
{
"info" : {
"version" : 1,
"author" : "xcode"
}
}
---
### Resources/Assets.Xcassets/Pause Icons/CheatCodes.Imageset/Contents.Json (Resources/Assets.xcassets/Pause Icons/CheatCodes.imageset/Contents.json)
{
"images" : [
{
"idiom" : "universal",
"filename" : "cheats@1x.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "cheats@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "cheats@3x.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
},
"properties" : {
"template-rendering-intent" : "template"
}
}
---
### Resources/Assets.Xcassets/Pause Icons/FastForward.Imageset/Contents.Json (Resources/Assets.xcassets/Pause Icons/FastForward.imageset/Contents.json)
{
"images" : [
{
"idiom" : "universal",
"filename" : "FastForward.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "FastForward@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "FastForward@3x.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
},
"properties" : {
"template-rendering-intent" : "template"
}
}
---
### Resources/Assets.Xcassets/Pause Icons/LoadSaveState.Imageset/Contents.Json (Resources/Assets.xcassets/Pause Icons/LoadSaveState.imageset/Contents.json)
{
"images" : [
{
"idiom" : "universal",
"filename" : "LoadSaveState.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "LoadSaveState@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "LoadSaveState@3x.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
},
"properties" : {
"template-rendering-intent" : "template"
}
}
---
### Resources/Assets.Xcassets/Pause Icons/Lu.Imageset/Contents.Json (Resources/Assets.xcassets/Pause Icons/Lu.imageset/Contents.json)
{
"images" : [
{
"filename" : "36x36_white3.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "72x72_white3.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "108x108_white3.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
---
### Resources/Assets.Xcassets/Pause Icons/Pause.Imageset/Contents.Json (Resources/Assets.xcassets/Pause Icons/Pause.imageset/Contents.json)
{
"images" : [
{
"idiom" : "universal",
"filename" : "Pause.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "Pause@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "Pause@3x.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
},
"properties" : {
"template-rendering-intent" : "template"
}
}
---
### Resources/Assets.Xcassets/Pause Icons/SaveSaveState.Imageset/Contents.Json (Resources/Assets.xcassets/Pause Icons/SaveSaveState.imageset/Contents.json)
{
"images" : [
{
"idiom" : "universal",
"filename" : "SaveSaveState.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "SaveSaveState@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "SaveSaveState@3x.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
},
"properties" : {
"template-rendering-intent" : "template"
}
}
---
### Resources/Assets.Xcassets/Pause Icons/Screenshot.Imageset/Contents.Json (Resources/Assets.xcassets/Pause Icons/Screenshot.imageset/Contents.json)
{
"images" : [
{
"filename" : "screenshot.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "screenshot@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "screenshot@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"template-rendering-intent" : "template"
}
}
---
### Resources/Assets.Xcassets/Pause Icons/SustainButtons.Imageset/Contents.Json (Resources/Assets.xcassets/Pause Icons/SustainButtons.imageset/Contents.json)
{
"images" : [
{
"idiom" : "universal",
"filename" : "sustain@1x.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "sustain@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "sustain@3x.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
},
"properties" : {
"template-rendering-intent" : "template"
}
}
---
### Resources/Assets.Xcassets/Riley.Imageset/Contents.Json (Resources/Assets.xcassets/Riley.imageset/Contents.json)
{
"images" : [
{
"filename" : "riley.jpg",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
---
### Resources/Assets.Xcassets/SettingsButton.Imageset/Contents.Json (Resources/Assets.xcassets/SettingsButton.imageset/Contents.json)
{
"images" : [
{
"idiom" : "universal",
"filename" : "gear_1.pdf"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
},
"properties" : {
"template-rendering-intent" : "template"
}
}
---
### Resources/Assets.Xcassets/Shane.Imageset/Contents.Json (Resources/Assets.xcassets/Shane.imageset/Contents.json)
{
"images" : [
{
"filename" : "shane.jpeg",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
---
### Resources/Assets.Xcassets/Social Media/Contents.Json (Resources/Assets.xcassets/Social Media/Contents.json)
{
"info" : {
"author" : "xcode",
"version" : 1
}
}
---
### Resources/Assets.Xcassets/Social Media/Bluesky.Imageset/Contents.Json (Resources/Assets.xcassets/Social Media/Bluesky.imageset/Contents.json)
{
"images" : [
{
"filename" : "bluesky.png",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"template-rendering-intent" : "template"
}
}
---
### Resources/Assets.Xcassets/Social Media/BlueskyAppIcon.Imageset/Contents.Json (Resources/Assets.xcassets/Social Media/BlueskyAppIcon.imageset/Contents.json)
{
"images" : [
{
"filename" : "Bluesky.jpg",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
---
### Resources/Assets.Xcassets/Social Media/GitHub.Imageset/Contents.Json (Resources/Assets.xcassets/Social Media/GitHub.imageset/Contents.json)
{
"images" : [
{
"filename" : "GitHub.png",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"template-rendering-intent" : "template"
}
}
---
### Resources/Assets.Xcassets/Social Media/GitHubAppIcon.Imageset/Contents.Json (Resources/Assets.xcassets/Social Media/GitHubAppIcon.imageset/Contents.json)
{
"images" : [
{
"filename" : "github.png",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
---
### Resources/Assets.Xcassets/Social Media/Mastodon.Imageset/Contents.Json (Resources/Assets.xcassets/Social Media/Mastodon.imageset/Contents.json)
{
"images" : [
{
"filename" : "mastodon.png",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"template-rendering-intent" : "template"
}
}
---
### Resources/Assets.Xcassets/Social Media/MastodonAppIcon.Imageset/Contents.Json (Resources/Assets.xcassets/Social Media/MastodonAppIcon.imageset/Contents.json)
{
"images" : [
{
"filename" : "Mastodon.png",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
---
### Resources/Assets.Xcassets/Social Media/Threads.Imageset/Contents.Json (Resources/Assets.xcassets/Social Media/Threads.imageset/Contents.json)
{
"images" : [
{
"filename" : "threads_icon.png",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"template-rendering-intent" : "template"
}
}
---
### Resources/Assets.Xcassets/Social Media/ThreadsAppIcon.Imageset/Contents.Json (Resources/Assets.xcassets/Social Media/ThreadsAppIcon.imageset/Contents.json)
{
"images" : [
{
"filename" : "Threads.png",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
---
### Resources/Icons.Xcassets/Contents.Json (Resources/Icons.xcassets/Contents.json)
{
"info" : {
"author" : "xcode",
"version" : 1
}
}
---
### Resources/Icons.Xcassets/Classic/Contents.Json (Resources/Icons.xcassets/Classic/Contents.json)
{
"info" : {
"author" : "xcode",
"version" : 1
}
}
---
### Resources/Icons.Xcassets/Classic/BrokenIcon.Appiconset/Contents.Json (Resources/Icons.xcassets/Classic/BrokenIcon.appiconset/Contents.json)
{
"images" : [
{
"filename" : "BrokenDelta.png",
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
---
### Resources/Icons.Xcassets/Classic/OriginalIcon.Appiconset/Contents.Json (Resources/Icons.xcassets/Classic/OriginalIcon.appiconset/Contents.json)
{
"images" : [
{
"filename" : "original-1024.png",
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
---
### Resources/Icons.Xcassets/Modern/Contents.Json (Resources/Icons.xcassets/Modern/Contents.json)
{
"info" : {
"author" : "xcode",
"version" : 1
}
}
---
### Resources/Icons.Xcassets/Modern/DeltaRed.Appiconset/Contents.Json (Resources/Icons.xcassets/Modern/DeltaRed.appiconset/Contents.json)
{
"images" : [
{
"filename" : "DeltaRed.png",
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
---
### Resources/Icons.Xcassets/Modern/GBA4iOS.Appiconset/Contents.Json (Resources/Icons.xcassets/Modern/GBA4iOS.appiconset/Contents.json)
{
"images" : [
{
"filename" : "GBA4iOS.png",
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
---
### Resources/Icons.Xcassets/Modern/ModernIcon.Appiconset/Contents.Json (Resources/Icons.xcassets/Modern/ModernIcon.appiconset/Contents.json)
{
"images" : [
{
"filename" : "Icon1024.png",
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"filename" : "AppIconDark.png",
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "tinted"
}
],
"filename" : "AppIconTinted.png",
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
---
### Resources/Icons.Xcassets/Patrons/Contents.Json (Resources/Icons.xcassets/Patrons/Contents.json)
{
"info" : {
"author" : "xcode",
"version" : 1
}
}
---
### Resources/Icons.Xcassets/Patrons/DeltaGold.Appiconset/Contents.Json (Resources/Icons.xcassets/Patrons/DeltaGold.appiconset/Contents.json)
{
"images" : [
{
"filename" : "DeltaGold.png",
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
---
### Resources/Icons.Xcassets/Patrons/Halogenide.Appiconset/Contents.Json (Resources/Icons.xcassets/Patrons/Halogenide.appiconset/Contents.json)
{
"images" : [
{
"filename" : "Halide@72ppi.png",
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
---
### Resources/Icons.Xcassets/Patrons/KetchupIcon.Appiconset/Contents.Json (Resources/Icons.xcassets/Patrons/KetchupIcon.appiconset/Contents.json)
{
"images" : [
{
"filename" : "KetchupIcon.png",
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"filename" : "KetchupIcon_Dark.png",
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "tinted"
}
],
"filename" : "KetchupIcon_Dark 1.png",
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
---
### Resources/Icons.Xcassets/Patrons/MicrochipIcon.Appiconset/Contents.Json (Resources/Icons.xcassets/Patrons/MicrochipIcon.appiconset/Contents.json)
{
"images" : [
{
"filename" : "MicrochipIcon.png",
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"filename" : "Microchip - Dark.png",
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "tinted"
}
],
"filename" : "Microchip - Tinted.png",
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
---
### Resources/Icons.Xcassets/Patrons Button Pack/Contents.Json (Resources/Icons.xcassets/Patrons - Button Pack/Contents.json)
{
"info" : {
"author" : "xcode",
"version" : 1
}
}
---
### Resources/Icons.Xcassets/Patrons Button Pack/GBAIcon.Appiconset/Contents.Json (Resources/Icons.xcassets/Patrons - Button Pack/GBAIcon.appiconset/Contents.json)
{
"images" : [
{
"filename" : "GBA.png",
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"filename" : "GBA_Dark.png",
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "tinted"
}
],
"filename" : "AppIcon_Tinted.png",
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
---
### Resources/Icons.Xcassets/Patrons Button Pack/GBCIcon.Appiconset/Contents.Json (Resources/Icons.xcassets/Patrons - Button Pack/GBCIcon.appiconset/Contents.json)
{
"images" : [
{
"filename" : "GBC_Grape.png",
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"filename" : "GBC_Dark.png",
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "tinted"
}
],
"filename" : "AppIcon_Tinted.png",
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
---
### Resources/Icons.Xcassets/Patrons Button Pack/GBCIcon Berry.Appiconset/Contents.Json (Resources/Icons.xcassets/Patrons - Button Pack/GBCIcon_Berry.appiconset/Contents.json)
{
"images" : [
{
"filename" : "GBC_Berry.png",
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"filename" : "GBC_Dark.png",
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "tinted"
}
],
"filename" : "AppIcon_Tinted.png",
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
---
### Resources/Icons.Xcassets/Patrons Button Pack/GBCIcon Dandelion.Appiconset/Contents.Json (Resources/Icons.xcassets/Patrons - Button Pack/GBCIcon_Dandelion.appiconset/Contents.json)
{
"images" : [
{
"filename" : "GBC_Dandelion.png",
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"filename" : "GBC_Dark.png",
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "tinted"
}
],
"filename" : "AppIcon_Tinted.png",
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
---
### Resources/Icons.Xcassets/Patrons Button Pack/GBCIcon Kiwi.Appiconset/Contents.Json (Resources/Icons.xcassets/Patrons - Button Pack/GBCIcon_Kiwi.appiconset/Contents.json)
{
"images" : [
{
"filename" : "GBC_Kiwi.png",
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"filename" : "GBC_Dark.png",
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "tinted"
}
],
"filename" : "AppIcon_Tinted.png",
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
---
### Resources/Icons.Xcassets/Patrons Button Pack/GBCIcon Teal.Appiconset/Contents.Json (Resources/Icons.xcassets/Patrons - Button Pack/GBCIcon_Teal.appiconset/Contents.json)
{
"images" : [
{
"filename" : "GBC_Teal.png",
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"filename" : "GBC_Dark.png",
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "tinted"
}
],
"filename" : "AppIcon_Tinted.png",
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
---
### Resources/Icons.Xcassets/Patrons Button Pack/GBIcon.Appiconset/Contents.Json (Resources/Icons.xcassets/Patrons - Button Pack/GBIcon.appiconset/Contents.json)
{
"images" : [
{
"filename" : "GameBoy.png",
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"filename" : "GameBoy_Dark.png",
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "tinted"
}
],
"filename" : "AppIcon_Tinted.png",
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
---
### Resources/Icons.Xcassets/Patrons Button Pack/N64Icon.Appiconset/Contents.Json (Resources/Icons.xcassets/Patrons - Button Pack/N64Icon.appiconset/Contents.json)
{
"images" : [
{
"filename" : "N64.png",
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"filename" : "N64_Dark.png",
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "tinted"
}
],
"filename" : "AppIcon_Tinted.png",
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
---
### Resources/Icons.Xcassets/Patrons Button Pack/NESIcon.Appiconset/Contents.Json (Resources/Icons.xcassets/Patrons - Button Pack/NESIcon.appiconset/Contents.json)
{
"images" : [
{
"filename" : "NES.png",
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"filename" : "NES_Dark.png",
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "tinted"
}
],
"filename" : "AppIcon_Tinted.png",
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
---
### Resources/Icons.Xcassets/Patrons Button Pack/SNESIcon.Appiconset/Contents.Json (Resources/Icons.xcassets/Patrons - Button Pack/SNESIcon.appiconset/Contents.json)
{
"images" : [
{
"filename" : "SNES.png",
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"filename" : "SNES_Dark.png",
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "tinted"
}
],
"filename" : "AppIcon_Tinted.png",
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
---
### Resources/Icons.Xcassets/Raw/Contents.Json (Resources/Icons.xcassets/Raw/Contents.json)
{
"info" : {
"author" : "xcode",
"version" : 1
}
}
---
### Resources/Icons.Xcassets/Raw/AppIcon.Imageset/Contents.Json (Resources/Icons.xcassets/Raw/AppIcon.imageset/Contents.json)
{
"images" : [
{
"filename" : "GlassIcon_Light.png",
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"filename" : "GlassIcon_Dark.png",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
---
### Resources/Icons.Xcassets/Raw/Classic/Contents.Json (Resources/Icons.xcassets/Raw/Classic/Contents.json)
{
"info" : {
"author" : "xcode",
"version" : 1
}
}
---
### Resources/Icons.Xcassets/Raw/Classic/BrokenIcon.Imageset/Contents.Json (Resources/Icons.xcassets/Raw/Classic/BrokenIcon.imageset/Contents.json)
{
"images" : [
{
"filename" : "BrokenDelta.png",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
---
### Resources/Icons.Xcassets/Raw/Classic/OriginalIcon.Imageset/Contents.Json (Resources/Icons.xcassets/Raw/Classic/OriginalIcon.imageset/Contents.json)
{
"images" : [
{
"filename" : "original-1024.png",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
---
### Resources/Icons.Xcassets/Raw/Modern/Contents.Json (Resources/Icons.xcassets/Raw/Modern/Contents.json)
{
"info" : {
"author" : "xcode",
"version" : 1
}
}
---
### Resources/Icons.Xcassets/Raw/Modern/DeltaRed.Imageset/Contents.Json (Resources/Icons.xcassets/Raw/Modern/DeltaRed.imageset/Contents.json)
{
"images" : [
{
"filename" : "DeltaRed.png",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
---
### Resources/Icons.Xcassets/Raw/Modern/GBA4iOS.Imageset/Contents.Json (Resources/Icons.xcassets/Raw/Modern/GBA4iOS.imageset/Contents.json)
{
"images" : [
{
"filename" : "GBA4iOS.png",
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"filename" : "AppIcon_Dark.png",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
---
### Resources/Icons.Xcassets/Raw/Modern/ModernIcon.Imageset/Contents.Json (Resources/Icons.xcassets/Raw/Modern/ModernIcon.imageset/Contents.json)
{
"images" : [
{
"filename" : "Icon1024.png",
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"filename" : "AppIcon_Dark.png",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
---
### Resources/Icons.Xcassets/Raw/Patrons/Contents.Json (Resources/Icons.xcassets/Raw/Patrons/Contents.json)
{
"info" : {
"author" : "xcode",
"version" : 1
}
}
---
### Resources/Icons.Xcassets/Raw/Patrons/DeltaGold.Imageset/Contents.Json (Resources/Icons.xcassets/Raw/Patrons/DeltaGold.imageset/Contents.json)
{
"images" : [
{
"filename" : "DeltaGold.png",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
---
### Resources/Icons.Xcassets/Raw/Patrons/Halogenide.Imageset/Contents.Json (Resources/Icons.xcassets/Raw/Patrons/Halogenide.imageset/Contents.json)
{
"images" : [
{
"filename" : "Halide@72ppi.png",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
---
### Resources/Icons.Xcassets/Raw/Patrons/KetchupIcon.Imageset/Contents.Json (Resources/Icons.xcassets/Raw/Patrons/KetchupIcon.imageset/Contents.json)
{
"images" : [
{
"filename" : "KetchupIcon.png",
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"filename" : "Ketchup_Dark.png",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
---
### Resources/Icons.Xcassets/Raw/Patrons/MicrochipIcon.Imageset/Contents.Json (Resources/Icons.xcassets/Raw/Patrons/MicrochipIcon.imageset/Contents.json)
{
"images" : [
{
"filename" : "MicrochipIcon.png",
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"filename" : "Microchip - Dark.png",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
---
### Resources/Icons.Xcassets/Raw/Patrons Button Pack/Contents.Json (Resources/Icons.xcassets/Raw/Patrons - Button Pack/Contents.json)
{
"info" : {
"author" : "xcode",
"version" : 1
}
}
---
### Resources/Icons.Xcassets/Raw/Patrons Button Pack/GBAIcon.Imageset/Contents.Json (Resources/Icons.xcassets/Raw/Patrons - Button Pack/GBAIcon.imageset/Contents.json)
{
"images" : [
{
"filename" : "GBA.png",
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"filename" : "GBA_Dark.png",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
---
### Resources/Icons.Xcassets/Raw/Patrons Button Pack/GBCIcon.Imageset/Contents.Json (Resources/Icons.xcassets/Raw/Patrons - Button Pack/GBCIcon.imageset/Contents.json)
{
"images" : [
{
"filename" : "GBC_Grape.png",
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"filename" : "GBC_Dark.png",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
---
### Resources/Icons.Xcassets/Raw/Patrons Button Pack/GBCIcon Berry.Imageset/Contents.Json (Resources/Icons.xcassets/Raw/Patrons - Button Pack/GBCIcon_Berry.imageset/Contents.json)
{
"images" : [
{
"filename" : "GBC_Berry.png",
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"filename" : "GBC_Dark.png",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
---
### Resources/Icons.Xcassets/Raw/Patrons Button Pack/GBCIcon Dandelion.Imageset/Contents.Json (Resources/Icons.xcassets/Raw/Patrons - Button Pack/GBCIcon_Dandelion.imageset/Contents.json)
{
"images" : [
{
"filename" : "GBC_Dandelion.png",
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"filename" : "GBC_Dark.png",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
---
### Resources/Icons.Xcassets/Raw/Patrons Button Pack/GBCIcon Kiwi.Imageset/Contents.Json (Resources/Icons.xcassets/Raw/Patrons - Button Pack/GBCIcon_Kiwi.imageset/Contents.json)
{
"images" : [
{
"filename" : "GBC_Kiwi.png",
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"filename" : "GBC_Dark.png",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
---
### Resources/Icons.Xcassets/Raw/Patrons Button Pack/GBCIcon Teal.Imageset/Contents.Json (Resources/Icons.xcassets/Raw/Patrons - Button Pack/GBCIcon_Teal.imageset/Contents.json)
{
"images" : [
{
"filename" : "GBC_Teal.png",
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"filename" : "GBC_Dark.png",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
---
### Resources/Icons.Xcassets/Raw/Patrons Button Pack/GBIcon.Imageset/Contents.Json (Resources/Icons.xcassets/Raw/Patrons - Button Pack/GBIcon.imageset/Contents.json)
{
"images" : [
{
"filename" : "GameBoy.png",
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"filename" : "GameBoy_Dark.png",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
---
### Resources/Icons.Xcassets/Raw/Patrons Button Pack/N64Icon.Imageset/Contents.Json (Resources/Icons.xcassets/Raw/Patrons - Button Pack/N64Icon.imageset/Contents.json)
{
"images" : [
{
"filename" : "N64.png",
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"filename" : "N64_Dark.png",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
---
### Resources/Icons.Xcassets/Raw/Patrons Button Pack/NESIcon.Imageset/Contents.Json (Resources/Icons.xcassets/Raw/Patrons - Button Pack/NESIcon.imageset/Contents.json)
{
"images" : [
{
"filename" : "NES.png",
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"filename" : "NES_Dark.png",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
---
### Resources/Icons.Xcassets/Raw/Patrons Button Pack/SNESIcon.Imageset/Contents.Json (Resources/Icons.xcassets/Raw/Patrons - Button Pack/SNESIcon.imageset/Contents.json)
{
"images" : [
{
"filename" : "SNES.png",
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"filename" : "SNES_Dark.png",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
---
### Resources/MicrochipIcon.Icon/Icon.Json (Resources/MicrochipIcon.icon/icon.json)
{
"color-space-for-untagged-svg-colors" : "display-p3",
"fill-specializations" : [
{
"value" : {
"linear-gradient" : [
"display-p3:0.64706,0.39216,0.92549,1.00000",
"display-p3:0.34118,0.12941,0.62353,1.00000"
]
}
},
{
"appearance" : "dark",
"value" : "system-dark"
}
],
"groups" : [
{
"blend-mode-specializations" : [
{
"value" : "plus-lighter"
},
{
"appearance" : "dark",
"value" : "plus-lighter"
},
{
"appearance" : "tinted",
"value" : "screen"
}
],
"blur-material-specializations" : [
{
"appearance" : "dark",
"value" : null
},
{
"appearance" : "tinted",
"value" : 0.25
}
],
"layers" : [
{
"blend-mode-specializations" : [
{
"value" : "plus-lighter"
},
{
"appearance" : "dark",
"value" : "normal"
},
{
"appearance" : "tinted",
"value" : "normal"
}
],
"fill-specializations" : [
{
"value" : {
"linear-gradient" : [
"display-p3:0.84314,0.84314,0.84314,1.00000",
"display-p3:0.84314,0.84314,0.84314,0.55000"
]
}
},
{
"appearance" : "dark",
"value" : {
"linear-gradient" : [
"display-p3:0.94868,0.85523,0.83495,1.00000",
"display-p3:0.73573,0.65403,0.99808,0.79980"
]
}
},
{
"appearance" : "tinted",
"value" : {
"linear-gradient" : [
"display-p3:1.00000,1.00000,1.00000,1.00000",
"display-p3:0.86213,0.87172,0.87172,1.00000"
]
}
}
],
"glass-specializations" : [
{
"value" : true
},
{
"appearance" : "tinted",
"value" : true
}
],
"image-name-specializations" : [
{
"value" : "04_symbols.svg"
},
{
"idiom" : "square",
"value" : "logo.svg"
}
],
"name" : "04_symbols-alt",
"opacity-specializations" : [
{
"value" : 1
},
{
"appearance" : "tinted",
"value" : 1
}
],
"position-specializations" : [
{
"idiom" : "square",
"value" : {
"scale" : 1,
"translation-in-points" : [
0,
0
]
}
}
]
},
{
"blend-mode-specializations" : [
{
"value" : "plus-lighter"
},
{
"appearance" : "dark",
"value" : "normal"
},
{
"appearance" : "tinted",
"value" : "normal"
}
],
"fill-specializations" : [
{
"value" : {
"solid" : "display-p3:0.86271,0.86271,0.86271,0.55000"
}
},
{
"appearance" : "dark",
"value" : {
"solid" : "display-p3:0.76641,0.69055,0.97849,0.80000"
}
},
{
"appearance" : "tinted",
"value" : "none"
}
],
"glass-specializations" : [
{
"value" : false
},
{
"appearance" : "tinted",
"value" : true
}
],
"image-name-specializations" : [
{
"value" : "04_symbols.svg"
},
{
"idiom" : "square",
"value" : "delta-text.svg"
}
],
"name" : "04_symbols-alt",
"opacity-specializations" : [
{
"value" : 1
},
{
"appearance" : "tinted",
"value" : 1
}
],
"position-specializations" : [
{
"idiom" : "square",
"value" : {
"scale" : 0.88,
"translation-in-points" : [
0,
0
]
}
}
]
}
],
"lighting-specializations" : [
{
"value" : "individual"
},
{
"appearance" : "tinted",
"value" : "combined"
}
],
"opacity-specializations" : [
{
"value" : 0.65
},
{
"appearance" : "dark",
"value" : 0.6
},
{
"appearance" : "tinted",
"value" : 1
}
],
"position-specializations" : [
{
"idiom" : "square",
"value" : {
"scale" : 1,
"translation-in-points" : [
0,
2
]
}
}
],
"shadow-specializations" : [
{
"value" : {
"kind" : "none",
"opacity" : 0.5
}
},
{
"appearance" : "tinted",
"value" : {
"kind" : "layer-color",
"opacity" : 0.8
}
}
],
"specular-specializations" : [
{
"value" : false
},
{
"appearance" : "dark",
"value" : false
},
{
"appearance" : "tinted",
"value" : true
}
],
"translucency-specializations" : [
{
"value" : {
"enabled" : false,
"value" : 0.5
}
},
{
"appearance" : "dark",
"value" : {
"enabled" : false,
"value" : 0
}
},
{
"appearance" : "tinted",
"value" : {
"enabled" : true,
"value" : 0.65
}
}
]
},
{
"blend-mode-specializations" : [
{
"appearance" : "tinted",
"value" : "plus-lighter"
}
],
"blur-material-specializations" : [
{
"value" : 1
},
{
"appearance" : "dark",
"value" : 1
},
{
"appearance" : "tinted",
"value" : null
}
],
"hidden-specializations" : [
{
"idiom" : "square",
"value" : false
}
],
"layers" : [
{
"blend-mode-specializations" : [
{
"value" : "hard-light"
},
{
"appearance" : "tinted",
"value" : "hard-light"
}
],
"glass-specializations" : [
{
"value" : false
},
{
"appearance" : "tinted",
"value" : false
}
],
"hidden" : false,
"image-name-specializations" : [
{
"value" : "texture.svg"
},
{
"idiom" : "square",
"value" : "texture.svg"
}
],
"name" : "texture",
"opacity-specializations" : [
{
"value" : 0.07
},
{
"appearance" : "tinted",
"value" : 0.2
}
]
},
{
"blend-mode-specializations" : [
{
"appearance" : "dark",
"value" : "normal"
},
{
"appearance" : "tinted",
"value" : "normal"
}
],
"fill-specializations" : [
{
"value" : {
"linear-gradient" : [
"display-p3:0.24706,0.24706,0.28627,1.00000",
"display-p3:0.14826,0.14826,0.17246,1.00000"
]
}
},
{
"appearance" : "dark",
"value" : {
"linear-gradient" : [
"display-p3:0.32549,0.22353,0.48235,1.00000",
"display-p3:0.25490,0.16863,0.38431,1.00000"
]
}
},
{
"appearance" : "tinted",
"value" : {
"solid" : "srgb:0.50000,0.50000,0.50000,1.00000"
}
}
],
"glass-specializations" : [
{
"value" : true
},
{
"appearance" : "tinted",
"value" : true
}
],
"hidden" : false,
"image-name-specializations" : [
{
"value" : "02_chip.png"
},
{
"idiom" : "square",
"value" : "02_chip.svg"
}
],
"name" : "02_chip",
"opacity-specializations" : [
{
"appearance" : "tinted",
"value" : 1
}
]
}
],
"lighting-specializations" : [
{
"value" : "individual"
},
{
"appearance" : "tinted",
"value" : "individual"
}
],
"opacity-specializations" : [
{
"value" : 1
},
{
"appearance" : "tinted",
"value" : 0.6
}
],
"position-specializations" : [
{
"idiom" : "square",
"value" : {
"scale" : 1,
"translation-in-points" : [
0,
0
]
}
}
],
"shadow-specializations" : [
{
"value" : {
"kind" : "layer-color",
"opacity" : 0.35
}
},
{
"appearance" : "dark",
"value" : {
"kind" : "layer-color",
"opacity" : 0.2
}
},
{
"appearance" : "tinted",
"value" : {
"kind" : "layer-color",
"opacity" : 1
}
}
],
"specular-specializations" : [
{
"value" : true
},
{
"appearance" : "tinted",
"value" : true
}
],
"translucency-specializations" : [
{
"value" : {
"enabled" : true,
"value" : 0.25
}
},
{
"appearance" : "dark",
"value" : {
"enabled" : true,
"value" : 0.4
}
},
{
"appearance" : "tinted",
"value" : {
"enabled" : true,
"value" : 0.71
}
}
]
},
{
"blend-mode-specializations" : [
{
"value" : "normal"
},
{
"appearance" : "dark",
"value" : "normal"
},
{
"appearance" : "tinted",
"value" : "plus-lighter"
}
],
"blur-material-specializations" : [
{
"value" : null
},
{
"appearance" : "tinted",
"value" : null
}
],
"hidden" : false,
"layers" : [
{
"blend-mode-specializations" : [
{
"appearance" : "tinted",
"value" : "normal"
}
],
"fill-specializations" : [
{
"appearance" : "tinted",
"value" : "automatic"
}
],
"glass-specializations" : [
{
"appearance" : "tinted",
"value" : true
}
],
"hidden" : false,
"image-name-specializations" : [
{
"value" : "01_connectors.png"
},
{
"idiom" : "square",
"value" : "01_connectors.svg"
}
],
"name" : "01_connectors",
"opacity-specializations" : [
{
"appearance" : "tinted",
"value" : 1
}
],
"position-specializations" : [
{
"idiom" : "square",
"value" : {
"scale" : 1,
"translation-in-points" : [
0,
0
]
}
}
]
}
],
"lighting" : "individual",
"opacity-specializations" : [
{
"appearance" : "dark",
"value" : 0.94
},
{
"appearance" : "tinted",
"value" : 1
}
],
"shadow-specializations" : [
{
"value" : {
"kind" : "layer-color",
"opacity" : 1
}
},
{
"appearance" : "dark",
"value" : {
"kind" : "layer-color",
"opacity" : 0.4
}
},
{
"appearance" : "tinted",
"value" : {
"kind" : "layer-color",
"opacity" : 1
}
}
],
"specular-specializations" : [
{
"value" : true
},
{
"appearance" : "dark",
"value" : true
},
{
"appearance" : "tinted",
"value" : true
}
],
"translucency-specializations" : [
{
"value" : {
"enabled" : true,
"value" : 0.4
}
},
{
"appearance" : "dark",
"value" : {
"enabled" : true,
"value" : 0.75
}
},
{
"appearance" : "tinted",
"value" : {
"enabled" : true,
"value" : 0.75
}
}
]
},
{
"blend-mode-specializations" : [
{
"appearance" : "tinted",
"value" : "normal"
}
],
"blur-material-specializations" : [
{
"value" : null
},
{
"appearance" : "tinted",
"value" : null
}
],
"layers" : [
{
"fill-specializations" : [
{
"value" : {
"solid" : "display-p3:0.89412,0.76471,0.37647,1.00000"
}
},
{
"appearance" : "dark",
"value" : {
"solid" : "display-p3:0.89412,0.76471,0.37647,1.00000"
}
},
{
"appearance" : "tinted",
"value" : "none"
}
],
"glass-specializations" : [
{
"appearance" : "dark",
"value" : true
},
{
"appearance" : "tinted",
"value" : false
}
],
"image-name" : "thnx.svg",
"name" : "thnx",
"opacity-specializations" : [
{
"value" : 0.4
},
{
"appearance" : "tinted",
"value" : 0.4
}
],
"position-specializations" : [
{
"idiom" : "square",
"value" : {
"scale" : 1,
"translation-in-points" : [
0,
425
]
}
}
]
},
{
"blend-mode" : "plus-darker",
"fill-specializations" : [
{
"value" : {
"linear-gradient" : [
"display-p3:0.80000,0.80000,0.80000,0.50000",
"display-p3:0.80000,0.80000,0.80000,0.80242"
]
}
},
{
"appearance" : "dark",
"value" : {
"linear-gradient" : [
"display-p3:0.80000,0.80000,0.80000,0.25362",
"display-p3:0.80000,0.80000,0.80000,0.80242"
]
}
},
{
"appearance" : "tinted",
"value" : {
"linear-gradient" : [
"display-p3:0.80000,0.80000,0.80000,0.20092",
"display-p3:0.80000,0.80000,0.80000,0.79910"
]
}
}
],
"glass-specializations" : [
{
"value" : true
},
{
"appearance" : "tinted",
"value" : true
}
],
"hidden" : false,
"image-name-specializations" : [
{
"value" : "00_stripes.svg"
},
{
"idiom" : "square",
"value" : "00_stripes.png"
}
],
"name" : "00_stripes",
"opacity-specializations" : [
{
"value" : 0.2
},
{
"appearance" : "dark",
"value" : 0.1
},
{
"appearance" : "tinted",
"value" : 0
}
]
}
],
"lighting" : "individual",
"opacity" : 1,
"shadow" : {
"kind" : "none",
"opacity" : 0.5
},
"specular-specializations" : [
{
"value" : true
},
{
"appearance" : "dark",
"value" : false
},
{
"appearance" : "tinted",
"value" : true
}
],
"translucency-specializations" : [
{
"value" : {
"enabled" : false,
"value" : 0.5
}
},
{
"appearance" : "dark",
"value" : {
"enabled" : false,
"value" : 0.5
}
},
{
"appearance" : "tinted",
"value" : {
"enabled" : false,
"value" : 0.5
}
}
]
}
],
"supported-platforms" : {
"squares" : "shared"
}
}
---