Delta

GitHub

Delta is an all-in-one classic video game emulator for non-jailbroken iOS devices.

5,938 stars Swift
RAW Doc

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 β€” a Game Boy Advance emulator for iOS devices Paul Thorsen and I made while in high school together β€” rebuilt from the ground up with modern iOS features and support for more systems.

<p align="center">
<img src="https://user-images.githubusercontent.com/705880/115471008-203aa480-a1ec-11eb-8aba-237a46799543.png" width=75%><br/>
<em>Mario and PokΓ©mon are properties of Nintendo Co., Ltd. and are not associated with Delta or AltStore LLC.</em>
</p>

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
- SNES: Snes9x
- N64: mupen64plus
- GBC: Gambatte
- GBA: visualboyadvance-m
- DS: melonDS
- GEN: 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

<p align="center">
<img src="https://user-images.githubusercontent.com/705880/114452847-c1db4980-9b8d-11eb-8f8f-de7998562222.png" width=100px height=100px>
</p>

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, 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 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 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
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
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
Roxas is my own framework used across my projects, developed to simplify a variety of common tasks used in iOS development.

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
- SNESDeltaCore
- N64DeltaCore
- GBCDeltaCore
- GBADeltaCore
- MelonDSDeltaCore
- 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.

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: [email protected]
* Mastodon: @[email protected]
* Threads: @mattaltgear
* Bluesky: @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.

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 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
- Component Libraries
- Requirements
- Migration Guides
- Communication
- Installation
- Usage
- Intro - Making a Request, Response Handling, Response Validation, Response Caching
- HTTP - HTTP Methods, Parameter Encoding, HTTP Headers, Authentication
- Large Data - Downloading Data to a File, Uploading Data to a Server
- Tools - Statistical Metrics, cURL Command Output
- Advanced Usage
- URL Session - Session Manager, Session Delegate, Request
- Routing - Routing Requests, Adapting and Retrying Requests
- Model Objects - Custom Response Serialization
- Connection - Security, Network Reachability
- Open Radars
- FAQ
- Credits
- Donations
- 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

Component Libraries

In order to keep Alamofire focused specifically on core networking implementations, additional component libraries have been created by the Alamofire Software Foundation to bring additional functionality to the Alamofire ecosystem.

- 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 - 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
- Alamofire 3.0 Migration Guide
- Alamofire 2.0 Migration Guide

Communication

- If you need help, use Stack Overflow. (Tag 'alamofire')
- If you'd like to ask a general question, use Stack Overflow.
- 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 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 '<Your Target Name>' do
pod 'Alamofire', '~> 4.7'
end

Then, run the following command:

bash
$ pod install

Carthage

Carthage is a decentralized dependency manager that builds your dependencies and provides you with binary frameworks.

You can install Carthage with Homebrew 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 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 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 - 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 - 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 - 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, 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. You can follow them on Twitter at @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 [email protected]. Please do not post it to a public issue tracker.

Donations

The ASF 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 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 and
OpenID Connect 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
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.

It also supports the PKCE 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
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,
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;
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 (AppAuthTV).

#### Authorization Server Requirements

AppAuthTV is designed for servers that support the device authorization flow as documented in RFC 8628.

Try

Want to try out AppAuth? Just run:

pod try AppAuth

Follow the instructions in 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,
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,
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, 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:

<sub>Objective-C</sub>

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...

<sub>Swift</sub>

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

<sub>Objective-C</sub>

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:

<sub>Objective-C</sub>

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...
}];

<sub>Swift</sub>

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

<sub>Objective-C</sub>

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.

<sub>Objective-C</sub>

objc
@interface AppDelegate : UIResponder <UIApplicationDelegate>
// property of the app's AppDelegate
@property(nonatomic, strong, nullable) id<OIDExternalUserAgentSession> currentAuthorizationFlow;
@end

<sub>Swift</sub>

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:

<sub>Objective-C</sub>

objc
// property of the containing class
@property(nonatomic, strong, nullable) OIDAuthState *authState;

<sub>Swift</sub>
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:

<sub>Objective-C</sub>

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];
}
}];

<sub>Swift</sub>

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):

<sub>Objective-C</sub>

objc
- (BOOL)application:(UIApplication *)app
openURL:(NSURL *)url
options:(NSDictionary<NSString , id> )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;
}

<sub>Swift</sub>

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:

<sub>Objective-C</sub>

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:

<sub>Objective-C</sub>

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:

<sub>Objective-C</sub>

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.

<sub>Objective-C</sub>

text
/ 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:

<sub>Objective-C</sub>

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
}];

<sub>Swift</sub>

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
protocol.

Instances of the OIDExternalUserAgentare passed into
OIDAuthState.authStateByPresentingAuthorizationRequest:externalUserAgent:callback
and/or
OIDAuthorizationService.presentAuthorizationRequest:externalUserAgent:callback:
rather than using the platform-specific convenience methods (which use the
default user-agents for their respective platforms), like
OIDAuthState.authStateByPresentingAuthorizationRequest:presentingViewController:callback:.

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
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
(in XCode, right click -> Open As -> Source Code)

text
<key>LSApplicationQueriesSchemes</key>
<array>
<string>googlechromes</string>
<string>opera-https</string>
<string>firefox</string>
</array>

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.

<sub>Objective-C</sub>

objc
// performs authentication request
AppDelegate *appDelegate =
(AppDelegate *)[UIApplication sharedApplication].delegate;
id<OIDExternalUserAgent> 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];
}
}];

<sub>Swift</sub>

text
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
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.

Included Samples

Sample apps that explore core AppAuth features are available for iOS, macOS and tvOS; follow the instructions in Examples/README.md to get started.

---

Pods/GoogleAPIClientForREST/README (Pods/GoogleAPIClientForREST/README.md)

Google APIs Client Library for Objective-C for REST #

Project site <https://github.com/google/google-api-objectivec-client-for-rest><br>
Discussion group <http://groups.google.com/group/google-api-objectivec-client>

[](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.
See
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.

Generated interfaces for Google APIs are in the
GeneratedServices folder.

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
by using the
ServiceGenerator.

If you have a problem or want a new feature to be included in the library,
please join the
discussion group.
Be sure to include
http logs
for requests and responses when posting questions. Bugs may also be submitted
on the issues list.

Externally-included projects: The library is built on top of code from the separate
project GTM Session Fetcher. To work
with some remote services, it also needs
Authentication/Authorization.

Google Data APIs: The much older library for XML-based APIs is
still available.

Other useful classes for Mac and iOS developers are available in the
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
for integration instructions, documentation, support information, and terms of
service.

Getting Started

Try either the Objective-C or Swift sample app.
For example, to demo the Objective-C sample project, you have three options:

1. Using CocoaPods's try method:

text
pod try GoogleSignIn

Note, this will default to providing you with the Objective-C sample app.

2. Using CocoaPod's install method:

text
git clone https://github.com/google/GoogleSignIn-iOS
cd GoogleSignIn-iOS/Samples/ObjC/SignInSample/
pod install
open SignInSampleForPod.xcworkspace

3. Using Swift Package Manager:

text
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.

* Add Google Sign-In to your own app by following our
getting started guides.
* Take a look at the
API reference.

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.

Mac Catalyst

Google Sign-In also supports iOS apps that are built for macOS via
Mac Catalyst. In order for your Mac Catalyst app
to store credientials via the Keychain on macOS, you will need to
sign your app.

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:

text
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
.
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
with different values for these properties to customize the button.
This convenience initializer
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(<YOUR_RECT>))

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.

text
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
with the
Google Toolbox for Mac - Session Fetcher
and
Google APIs Client Library for Objective-C For REST
libraries on iOS, macOS, tvOS, and watchOS by providing an implementation of
GTMFetcherAuthorizationProtocol
for authorizing requests with AppAuth.

GTMAppAuth is an alternative authorizer to GTMOAuth2
. 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.
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,
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<OIDExternalUserAgentSession> 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<NSString , id> )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
class with a kSecAttrAccount
value of "OAuth" and a developer supplied value for kSecAttrService.
For this use of generic password items, the combination of account and service
values acts as the
primary key
of the Keychain items. The
kSecAttrAccessible
key is set to
kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly
in order to allow background access after initial device unlock following a
restart. A keyed archive
representation of the relevant GTMAppAuthFetcherAuthorization instance is supplied as the value for
kSecValueData
and this is encrypted and stored by
Keychain Services.

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
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. In the
apps folder run pod install, then open the resulting xcworkspace file.

Be sure to follow the instructions in
Example-iOS/README.md or
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
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<GTMFetcherAuthorizationProtocol> 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 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 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<GTMFetcherAuthorizationProtocol> 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 <https://github.com/google/gtm-session-fetcher><br>
Discussion group <http://groups.google.com/group/google-toolbox-for-mac>

[](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
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": "[email protected]"
},
"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": "[email protected]"
},
"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.

How is SDWebImage better than X?

Who Uses It
----------

Find out who uses SDWebImage and add your app to the list.

How To Use
----------

API documentation is available at CocoaDocs - 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 <SDWebImage/UIImageView+WebCache.h>

...

- (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/


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];

text

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 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 section for more details.

#### Podfile


platform :ios, '7.0'
pod 'SDWebImage', '~>3.8'
text
If you are using Swift, be sure to add use_frameworks! and set your target to iOS 8+:

platform :ios, '8.0'
use_frameworks!
text
#### 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'
text

Installation with Carthage (iOS 8+)

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

#### Cartfile


github "rs/SDWebImage"
text
#### Usage
Swift

If you installed using CocoaPods:


import SDWebImage
text
If you installed manually:

import WebImage
text
Objective-C

@import WebImage;
text

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
text

Add the SDWebImage project to your project

- Download and unzip the last version of the framework from the download page
- 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
text
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
text
and this:

$(inherited)
text

Import headers in your source files

In the source files where you need to use the library, import the header file:

objective-c
#import <SDWebImage/UIImageView+WebCache.h>
text

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.

---

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,
and Gitter Chat Room
(_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<Int64>("id")
let name = Expression<String?>("name")
let email = Expression<String>("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 <- "[email protected]")
let rowid = try db.run(insert)
// INSERT INTO "users" ("name", "email") VALUES ('Alice', '[email protected]')

for user in try db.prepare(users) {
print("id: \(user[id]), name: \(user[name]), email: \(user[email])")
// id: 1, name: Optional("Alice"), email: [email protected]
}
// 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"

text
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 ["[email protected]", "[email protected]"] {
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("[email protected]")
// id: Optional(3), email: Optional("[email protected]")
}

try db.scalar("SELECT count(*) FROM users") // 2

text
[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 10.2) or greater. Version 0.11.6 requires Swift 4.2 (and Xcode 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
text
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
text
2. Update your Podfile to include the following:
ruby
use_frameworks!

target 'YourAppTargetName' do
pod 'SQLite.swift', '~> 0.12.0'
end

text
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")
]
text
2. Build your project:
sh
$ swift build
text
[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
(@stephencelis)


License

SQLite.swift is available under the MIT license. See the LICENSE
file
for more information.

These projects enhance or use SQLite.swift:

- [SQLiteMigrationManager.swift][] (inspired by
[FMDBMigrationManager][])


Alternatives

Looking for something else? Try another Swift wrapper (or [FMDB][]):

- Camembert
- GRDB
- SQLiteDB
- Squeal
- SwiftData
- 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 (<[email protected]>)

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 on iOS or macOS.

Full documentation here.

---

Table of Contents

* System requirements
* Get started
* Register your application
* Obtain an OAuth 2.0 token
* SDK distribution
* CocoaPods
* Carthage
* Manually add subproject
* Configure your project
* Application
.plist file
* Handling the authorization flow
* Initialize a
DropboxClient instance
* Begin the authorization flow
* Handle redirect back into SDK
* Try some API requests
* Dropbox client instance
* Handle the API response
* Request types
* RPC-style request
* Upload-style request
* Download-style request
* Handling responses and errors
* Route-specific errors
* Generic network request errors
* Response handling edge cases
* Customizing network calls
* Configure network client
* Specify API call response queue
*
DropboxClientsManager class
* Single Dropbox user case
* Multiple Dropbox user case
* Examples
* Documentation
* Stone
* Modifications
* 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. 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.

---

SDK distribution

You can integrate the Dropbox Swift SDK into your project using one of several methods.

CocoaPods

To use CocoaPods, a dependency manager for Cocoa projects, you should first install it using the following command:

bash
$ gem install cocoapods
text
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 '<YOUR_PROJECT_NAME>' do
pod 'SwiftyDropbox'
end

text
Then, run the following command to install the dependency:
bash
$ pod install
text
Once your project is integrated with the Dropbox Swift SDK, you can pull SDK updates using the following command:
bash
$ pod update
text
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, 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:

bash
brew update
brew install carthage
text
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
text
Then, run the following command to install the dependency to checkout and build the Dropbox Swift SDK repository:

##### iOS

bash
carthage update --platform iOS
text
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
text
Then, navigate to the Input Files section and add the following path:

$(SRCROOT)/Carthage/Build/iOS/SwiftyDropbox.framework
$(SRCROOT)/Carthage/Build/iOS/Alamofire.framework
text
##### macOS
bash
carthage update --platform Mac
text
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 section of the README.

Then, run the following command to checkout and build the Dropbox Swift SDK repository:

##### iOS

bash
carthage update --platform iOS
text
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
text
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 to the canOpenURL function. You should
add the following code to your application's
.plist file:


<key>LSApplicationQueriesSchemes</key>
<array>
<string>dbapi-8-emm</string>
<string>dbapi-2</string>
</array>
text
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-<APP_KEY>, where <APP_KEY> is your
Dropbox app's app key, which can be found in the App Console.

You should add the following code to your .plist file (but be sure to replace <APP_KEY> with your app's app key):


<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLSchemes</key>
<array>
<string>db-<APP_KEY></string>
</array>
<key>CFBundleURLName</key>
<string></string>
</dict>
</array>
text
After you've made the above changes, your application's .plist file should look something like this:

<p align="center">
<img src="https://github.com/dropbox/SwiftyDropbox/blob/master/Images/InfoPlistExample.png?raw=true" alt="Info .plist Example"/>
</p>

---

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("<APP_KEY>")
return true
}

text
##### macOS
Swift
import SwiftyDropbox

func applicationDidFinishLaunching(_ aNotification: Notification) {
DropboxClientsManager.setupWithAppKeyDesktop("<APP_KEY>")
}

text
---

#### 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)
})
}

text
##### macOS
Swift
import SwiftyDropbox

func myButtonInControllerPressed() {
DropboxClientsManager.authorizeFromController(sharedWorkspace: NSWorkspace.shared,
controller: self,
openURL: { (url: URL) -> Void in
NSWorkspace.shared.open(url)
})
}

text
Beginning the authentication flow via in-app webview will launch a window like this:


<p align="center">
<img src="https://github.com/dropbox/SwiftyDropbox/blob/master/Images/OAuthFlowInit.png?raw=true" alt="Auth Flow Init Example"/>
</p>

---

#### 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
}

text
##### 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)
}
}
}

text
After the end user signs in with their Dropbox login credentials via the in-app webview, they will see a window like this:


<p align="center">
<img src="https://github.com/dropbox/SwiftyDropbox/blob/master/Images/OAuthFlowApproval.png?raw=true" alt="Auth Flow Approval Example"/>
</p>

If they press Allow or Cancel, the db-<APP_KEY> 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

text
or
Swift
import SwiftyDropbox

// Initialize with manually retrieved auth token
let client = DropboxClient(accessToken: "<MY_ACCESS_TOKEN>")

text
---

Handle the API response

The Dropbox User API and Business API 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)
}
}
text
---

#### 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()
}

text
---

#### 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)
}

text
---

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 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. /
text
---

#### 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. /
text
---

#### 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
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 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
....
....
....
}
}
}
text
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: "<MY_ACCESS_TOKEN>",
baseHosts: nil,
userAgent: "CustomUserAgent",
selectUser: nil,
sessionDelegate: mySessionDelegate,
backgroundSessionDelegate: myBackgroundSessionDelegate,
serverTrustPolicyManager: myServerTrustPolicyManager)

DropboxClientsManager.setupWithAppKey("<APP_KEY>", transportClient: transportClient)

text
##### macOS
Swift
import SwiftyDropbox

let transportClient = DropboxTransportClient(accessToken: "<MY_ACCESS_TOKEN>",
baseHosts: nil,
userAgent: "CustomUserAgent",
selectUser: nil,
sessionDelegate: mySessionDelegate,
backgroundSessionDelegate: myBackgroundSessionDelegate,
serverTrustPolicyManager: myServerTrustPolicyManager)

DropboxClientsManager.setupWithAppKeyDesktop("<APP_KEY>", transportClient: transportClient)

text
#### 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: <NSThread: 0x61000007bec0>{number = 4, name = (null)}
print(Thread.main) // Output: <NSThread: 0x608000070100>{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 - View photos from your Dropbox. Supports Apple Watch.

---

Documentation

* Dropbox API v2 Swift SDK
* Dropbox API v2

---

Stone

All of our routes and data types are auto-generated using a framework called 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 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" : "[email protected]",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "[email protected]",
"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" : "[email protected]",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "[email protected]",
"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
}
}

---

{
"images" : [
{
"idiom" : "universal",
"filename" : "[email protected]",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "[email protected]",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "[email protected]",
"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" : "[email protected]",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "[email protected]",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "[email protected]",
"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" : "[email protected]",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "[email protected]",
"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" : "[email protected]",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "[email protected]",
"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" : "[email protected]",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "[email protected]",
"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" : "[email protected]",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "[email protected]",
"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" : "[email protected]",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "[email protected]",
"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" : "[email protected]",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "[email protected]",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "[email protected]",
"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" : "[email protected]",
"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" : "[email protected]",
"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"
}
}

---