Working with App Groups and Extensions
1. Configuring App Groups
| Property | Description | Value |
|---|---|---|
| App Group Identifier | Unique string identifier for the app group | group.com.yourcompany.appgroup |
| Entitlements File | File containing app group entitlements | AppGroup.entitlements |
Example: App Group Entitlements
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.application-groups</key>
<array>
<string>group.com.yourcompany.appgroup</string>
</array>
</dict>
</plist>
2. Sharing Data with UserDefaults
| Property | Value | Description |
|---|---|---|
| suiteName | String? |
Name of the app group suite to share data with |
Example: Accessing Shared UserDefaults
let userDefaults = UserDefaults(suiteName: "group.com.example.appgroup")
userDefaults?.set("sharedValue", forKey: "key")
let value = userDefaults?.string(forKey: "key")
3. Creating Share Extensions
Share extensions allow users to share content from one app to another using the system's share sheet.
| Property | Description | Value |
|---|---|---|
| NSExtensionPointIdentifier | Extension point identifier for share extensions | com.apple.share-services |
Example: Basic Share Extension Configuration
import UIKit
import Social
class ShareViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Configure the UI for sharing
}
@IBAction func shareButtonTapped(_ sender: UIButton) {
// Handle sharing logic here
}
}
Info.plist.Steps to Create a Share Extension
- Create a new target in Xcode: File > New > Target
- Select Share Extension under the iOS section
- Name your extension (e.g.,
MyShareExtension) - Configure the extension in
Info.plist - Implement the sharing logic in
ShareViewController
4. Building Today Extensions
| Property | Value | Description |
|---|---|---|
| NSExtensionPointIdentifier | com.apple.widget-extension |
Extension point identifier for Today extensions |
| NSExtensionPointName | Today Extension |
Name of the extension point |
Example: Today Extension Info.plist Configuration
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSExtensionPointIdentifier</key>
<string>com.apple.widget-extension</string>
<key>NSExtensionPointName</key>
<string>Today Extension</string>
</dict>
</plist>
Example: Widget Data Provider
import WidgetKit
import SwiftUI
struct Provider: TimelineProvider {
func placeholder(in context: Context) -> SimpleEntry {
SimpleEntry(date: Date(), configuration: ConfigurationIntent())
}
func getSnapshot(in context: Context, completion: @escaping (SimpleEntry) -> ()) {
let entry = SimpleEntry(date: Date(), configuration: ConfigurationIntent())
completion(entry)
}
func getTimeline(in context: Context, completion: @escaping (Timeline<SimpleEntry>) -> ()) {
let entries: [SimpleEntry] = [
SimpleEntry(date: Date(), configuration: ConfigurationIntent())
]
let timeline = Timeline(entries: entries, policy: .after(Date().addingTimeInterval(60 * 60)))
completion(timeline)
}
}
struct SimpleEntry: TimelineEntry {
let date: Date
let configuration: ConfigurationIntent
}
Example: Widget View
import SwiftUI
import WidgetKit
struct SimpleWidgetEntryView : View {
var entry: Provider.Entry
var body: some View {
VStack(alignment: .leading) {
Text("Hello, World!")
.font(.headline)
Text(entry.date, style: .time)
}
.padding()
}
}
struct SimpleWidgetEntryView_Previews: PreviewProvider {
static var previews: some View {
SimpleWidgetEntryView(entry: SimpleEntry(date: Date(), configuration: ConfigurationIntent()))
}
}
5. Implementing Custom Keyboard Extensions
Custom keyboard extensions in iOS allow developers to create alternative input methods for users. These extensions are built using the UIInputViewController class and must be declared in a separate target within the app.
| Property | Description | Notes |
|---|---|---|
| inputViewController | Instance of UIInputViewController |
Base class for custom keyboards |
| textInputMode | Current text input mode (e.g., .keyboard, .dictation) |
Used to determine input context |
Example: Basic Custom Keyboard Setup
import UIKit
class CustomKeyboardViewController: UIInputViewController {
override func viewDidLoad() {
super.viewDidLoad()
setupKeyboard()
}
private func setupKeyboard() {
let button = UIButton(type: .system)
button.setTitle("Hello", for: .normal)
button.addTarget(self, action: #selector(handleButtonTap), for: .touchUpInside)
view.addSubview(button)
}
@objc private func handleButtonTap() {
textDocumentProxy.insertText("Hello")
}
}
Info.plist under NSExtensionPointIdentifier with value com.apple.keyboard-service.
6. Creating Action Extensions
Action extensions allow users to perform quick actions on content from other apps. They appear in the share sheet and can process selected text, images, or files.
| Property | Value | Description |
|---|---|---|
| NSExtensionPointIdentifier | com.apple.ui-services |
Extension point identifier for action extensions |
| NSExtension | NSExtensionPointIdentifier |
Defines the extension point and its attributes |
Example: Basic Action Extension Configuration
import UIKit
class ExtensionViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Configure UI for action extension
}
@IBAction func performAction(_ sender: Any) {
// Handle the action here
}
}
Example: Info.plist Configuration
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSExtensionPointIdentifier</key>
<string>com.apple.ui-services</string>
<key>NSExtension</key>
<dict>
<key>NSExtensionPointIdentifier</key>
<string>com.apple.ui-services</string>
<key>NSExtensionPointIdentifier</key>
<string>com.apple.ui-services</string>
</dict>
</dict>
</plist>
7. Using File Provider Extensions
File Provider extensions in iOS allow apps to provide and manage files from a custom file system. They are particularly useful for apps that need to integrate with external storage systems or provide a structured file system within the app.
| Property | Value | Description |
|---|---|---|
| NSFileProviderDomainIdentifier | com.example.domain |
Unique identifier for the file provider domain. |
| NSFileProviderDomainName | Example Domain |
Name of the file provider domain displayed to the user. |
| NSFileProviderDomainIcon | file://path/to/icon.png |
Icon used to represent the file provider domain. |
Example: Implementing a File Provider Extension
import FileProvider
class ExampleFileProvider: NSFileProviderExtension {
override func providePlaceholder(at url: URL, completionHandler: @escaping (Error?) -> Void) {
// Create a placeholder file at the specified URL
let placeholder = url.appendingPathExtension("placeholder")
try? "Placeholder content".write(to: placeholder, atomically: true, encoding: .utf8)
completionHandler(nil)
}
override func itemChanged(at url: URL) {
// Handle changes to items in the file system
print("Item changed at: \(url)")
}
}
NSFileProviderExtension key.
Key Features of File Provider Extensions
- Custom File Systems: Allow apps to present custom file systems to the user.
- Placeholder Files: Provide placeholders for files that are not yet downloaded.
- Asynchronous Operations: All file system operations must be performed asynchronously.
8. Communicating Between App and Extensions
Apps and their extensions can communicate using shared containers within App Groups. This allows data sharing, user defaults, and file access between the app and its extensions.
| Method | Description | Notes |
|---|---|---|
| UserDefaults(suiteName:) | Access shared user defaults using a suite name. | Must be configured in App Groups entitlements. |
| FileManager.containerURL(forSecurityApplicationGroupIdentifier:) | Get the URL for a shared container directory. | Useful for reading/writing files across app and extension. |
Example: Reading Shared UserDefaults
let suiteName = "group.com.example.shared"
let userDefaults = UserDefaults(suiteName: suiteName)
let value = userDefaults?.string(forKey: "sharedKey")
Example: Writing to Shared Container
let groupIdentifier = "group.com.example.shared"
guard let containerURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: groupIdentifier) else { return }
let fileURL = containerURL.appendingPathComponent("sharedData.json")
let data = "shared content".data(using: .utf8)
try? data?.write(to: fileURL)
9. Sharing Keychain Access
| Property | Value | Description |
|---|---|---|
| accessGroup | string |
Identifier for the keychain access group |
| service | string |
Name of the service using the keychain item |
| account | string |
Account name for the keychain item |
Example: Sharing Keychain Data
import Security
let keychainQuery: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: "userAccount",
kSecAttrService as String: "com.example.app",
kSecAttrAccessGroup as String: "com.example.sharedKeychain"
]
let status = SecItemAdd(keychainQuery as CFDictionary, nil)
if status == errSecSuccess {
print("Keychain item added successfully")
} else {
print("Failed to add keychain item: \(status)")
}
accessGroup must be the same across all apps and extensions in the app group to share keychain data.
10. Managing Extension Lifecycle
| Method | Description | Notes |
|---|---|---|
| extensionPointIdentifier | String |
Identifier for the extension point (e.g., com.apple.widgetkit-extension) |
| extensionPointIdentifier | String |
Used to determine the type of extension (e.g., com.apple.applescript) |
Example: App Extension Lifecycle
import UIKit
class ExtensionDelegate: NSObject {
func applicationDidFinishLaunching(_ application: UIApplication) {
// Handle app launch for extension
}
func applicationWillResignActive(_ application: UIApplication) {
// Handle app resigning active for extension
}
func applicationDidEnterBackground(_ application: UIApplication) {
// Handle app entering background for extension
}
func applicationWillEnterForeground(_ application: UIApplication) {
// Handle app entering foreground for extension
}
func applicationWillTerminate(_ application: UIApplication) {
// Handle app termination for extension
}
}