Initial commit: add all skills files
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
401
ios-application-dev/references/system-integration.md
Normal file
401
ios-application-dev/references/system-integration.md
Normal file
@@ -0,0 +1,401 @@
|
||||
# System Integration
|
||||
|
||||
iOS system integration guide covering permissions, location, sharing, app lifecycle, and haptic feedback.
|
||||
|
||||
## Permission Requests
|
||||
|
||||
Request permissions contextually, not at launch:
|
||||
|
||||
```swift
|
||||
import AVFoundation
|
||||
|
||||
@objc func openCamera() {
|
||||
AVCaptureDevice.requestAccess(for: .video) { [weak self] granted in
|
||||
DispatchQueue.main.async {
|
||||
if granted {
|
||||
self?.showCameraInterface()
|
||||
} else {
|
||||
self?.showPermissionDeniedAlert()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Photo Library
|
||||
|
||||
```swift
|
||||
import Photos
|
||||
|
||||
func requestPhotoAccess() {
|
||||
PHPhotoLibrary.requestAuthorization(for: .readWrite) { status in
|
||||
DispatchQueue.main.async {
|
||||
switch status {
|
||||
case .authorized, .limited:
|
||||
self.showPhotoPicker()
|
||||
case .denied, .restricted:
|
||||
self.showSettingsAlert()
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Microphone
|
||||
|
||||
```swift
|
||||
func requestMicrophoneAccess() {
|
||||
AVAudioSession.sharedInstance().requestRecordPermission { granted in
|
||||
DispatchQueue.main.async {
|
||||
if granted {
|
||||
self.startRecording()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Notifications
|
||||
|
||||
```swift
|
||||
import UserNotifications
|
||||
|
||||
func requestNotificationPermission() {
|
||||
UNUserNotificationCenter.current().requestAuthorization(
|
||||
options: [.alert, .badge, .sound]
|
||||
) { granted, error in
|
||||
DispatchQueue.main.async {
|
||||
if granted {
|
||||
self.registerForRemoteNotifications()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Location Button
|
||||
|
||||
For one-time location access without persistent permission:
|
||||
|
||||
```swift
|
||||
import CoreLocationUI
|
||||
|
||||
class StoreFinderVC: UIViewController {
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
let locationBtn = CLLocationButton()
|
||||
locationBtn.icon = .arrowFilled
|
||||
locationBtn.label = .currentLocation
|
||||
locationBtn.cornerRadius = 20
|
||||
locationBtn.addTarget(self, action: #selector(findNearby), for: .touchUpInside)
|
||||
|
||||
view.addSubview(locationBtn)
|
||||
locationBtn.snp.makeConstraints { make in
|
||||
make.centerX.equalToSuperview()
|
||||
make.bottom.equalTo(view.safeAreaLayoutGuide).offset(-24)
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Core Location
|
||||
|
||||
```swift
|
||||
import CoreLocation
|
||||
|
||||
class LocationManager: NSObject, CLLocationManagerDelegate {
|
||||
private let manager = CLLocationManager()
|
||||
|
||||
func requestLocation() {
|
||||
manager.delegate = self
|
||||
manager.desiredAccuracy = kCLLocationAccuracyBest
|
||||
manager.requestWhenInUseAuthorization()
|
||||
}
|
||||
|
||||
func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
|
||||
switch manager.authorizationStatus {
|
||||
case .authorizedWhenInUse, .authorizedAlways:
|
||||
manager.requestLocation()
|
||||
case .denied:
|
||||
showLocationDeniedAlert()
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
|
||||
guard let location = locations.last else { return }
|
||||
handleLocation(location)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Share Sheet
|
||||
|
||||
```swift
|
||||
@objc func shareContent() {
|
||||
let items: [Any] = [contentURL, contentImage].compactMap { $0 }
|
||||
let activityVC = UIActivityViewController(activityItems: items, applicationActivities: nil)
|
||||
|
||||
if let popover = activityVC.popoverPresentationController {
|
||||
popover.sourceView = shareButton
|
||||
popover.sourceRect = shareButton.bounds
|
||||
}
|
||||
|
||||
present(activityVC, animated: true)
|
||||
}
|
||||
```
|
||||
|
||||
### Custom Share Items
|
||||
|
||||
```swift
|
||||
class ShareItem: NSObject, UIActivityItemSource {
|
||||
let title: String
|
||||
let url: URL
|
||||
|
||||
init(title: String, url: URL) {
|
||||
self.title = title
|
||||
self.url = url
|
||||
}
|
||||
|
||||
func activityViewControllerPlaceholderItem(_ activityViewController: UIActivityViewController) -> Any {
|
||||
return url
|
||||
}
|
||||
|
||||
func activityViewController(_ activityViewController: UIActivityViewController, itemForActivityType activityType: UIActivity.ActivityType?) -> Any? {
|
||||
return url
|
||||
}
|
||||
|
||||
func activityViewController(_ activityViewController: UIActivityViewController, subjectForActivityType activityType: UIActivity.ActivityType?) -> String {
|
||||
return title
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Excluding Activities
|
||||
|
||||
```swift
|
||||
let activityVC = UIActivityViewController(activityItems: items, applicationActivities: nil)
|
||||
activityVC.excludedActivityTypes = [
|
||||
.addToReadingList,
|
||||
.assignToContact,
|
||||
.print
|
||||
]
|
||||
```
|
||||
|
||||
## App Lifecycle
|
||||
|
||||
```swift
|
||||
class PlayerViewController: UIViewController {
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
NotificationCenter.default.addObserver(
|
||||
self, selector: #selector(onBackground),
|
||||
name: UIApplication.didEnterBackgroundNotification, object: nil
|
||||
)
|
||||
NotificationCenter.default.addObserver(
|
||||
self, selector: #selector(onForeground),
|
||||
name: UIApplication.willEnterForegroundNotification, object: nil
|
||||
)
|
||||
NotificationCenter.default.addObserver(
|
||||
self, selector: #selector(onTerminate),
|
||||
name: UIApplication.willTerminateNotification, object: nil
|
||||
)
|
||||
}
|
||||
|
||||
@objc private func onBackground() {
|
||||
saveState()
|
||||
pausePlayback()
|
||||
}
|
||||
|
||||
@objc private func onForeground() {
|
||||
restoreState()
|
||||
resumePlayback()
|
||||
}
|
||||
|
||||
@objc private func onTerminate() {
|
||||
saveState()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Scene Lifecycle (iOS 13+)
|
||||
|
||||
```swift
|
||||
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
|
||||
func sceneDidBecomeActive(_ scene: UIScene) {
|
||||
// Resume tasks
|
||||
}
|
||||
|
||||
func sceneWillResignActive(_ scene: UIScene) {
|
||||
// Pause tasks
|
||||
}
|
||||
|
||||
func sceneDidEnterBackground(_ scene: UIScene) {
|
||||
// Save state
|
||||
}
|
||||
|
||||
func sceneWillEnterForeground(_ scene: UIScene) {
|
||||
// Prepare UI
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### State Preservation
|
||||
|
||||
```swift
|
||||
class ViewController: UIViewController {
|
||||
override func encodeRestorableState(with coder: NSCoder) {
|
||||
super.encodeRestorableState(with: coder)
|
||||
coder.encode(currentItemID, forKey: "currentItemID")
|
||||
}
|
||||
|
||||
override func decodeRestorableState(with coder: NSCoder) {
|
||||
super.decodeRestorableState(with: coder)
|
||||
if let itemID = coder.decodeObject(forKey: "currentItemID") as? String {
|
||||
loadItem(itemID)
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Haptic Feedback
|
||||
|
||||
```swift
|
||||
func onTaskComplete() {
|
||||
UINotificationFeedbackGenerator().notificationOccurred(.success)
|
||||
}
|
||||
|
||||
func onError() {
|
||||
UINotificationFeedbackGenerator().notificationOccurred(.error)
|
||||
}
|
||||
|
||||
func onWarning() {
|
||||
UINotificationFeedbackGenerator().notificationOccurred(.warning)
|
||||
}
|
||||
|
||||
func onSelection() {
|
||||
UISelectionFeedbackGenerator().selectionChanged()
|
||||
}
|
||||
|
||||
func onImpact() {
|
||||
UIImpactFeedbackGenerator(style: .medium).impactOccurred()
|
||||
}
|
||||
```
|
||||
|
||||
### Impact Styles
|
||||
|
||||
| Style | Usage |
|
||||
|-------|-------|
|
||||
| `.light` | Subtle feedback, small UI changes |
|
||||
| `.medium` | Standard feedback, button presses |
|
||||
| `.heavy` | Strong feedback, significant actions |
|
||||
| `.soft` | Gentle feedback, background changes |
|
||||
| `.rigid` | Sharp feedback, collisions |
|
||||
|
||||
### Prepared Feedback
|
||||
|
||||
For time-critical haptics, prepare the generator in advance:
|
||||
|
||||
```swift
|
||||
class DraggableView: UIView {
|
||||
private let impactGenerator = UIImpactFeedbackGenerator(style: .medium)
|
||||
|
||||
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
|
||||
super.touchesBegan(touches, with: event)
|
||||
impactGenerator.prepare()
|
||||
}
|
||||
|
||||
func didSnapToPosition() {
|
||||
impactGenerator.impactOccurred()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Deep Linking
|
||||
|
||||
### URL Schemes
|
||||
|
||||
```swift
|
||||
// In AppDelegate or SceneDelegate
|
||||
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool {
|
||||
guard let components = URLComponents(url: url, resolvingAgainstBaseURL: true) else {
|
||||
return false
|
||||
}
|
||||
|
||||
switch components.host {
|
||||
case "item":
|
||||
if let itemID = components.queryItems?.first(where: { $0.name == "id" })?.value {
|
||||
navigateToItem(itemID)
|
||||
return true
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
```
|
||||
|
||||
### Universal Links
|
||||
|
||||
```swift
|
||||
func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
|
||||
guard userActivity.activityType == NSUserActivityTypeBrowsingWeb,
|
||||
let url = userActivity.webpageURL else {
|
||||
return false
|
||||
}
|
||||
|
||||
return handleUniversalLink(url)
|
||||
}
|
||||
```
|
||||
|
||||
## Background Tasks
|
||||
|
||||
```swift
|
||||
import BackgroundTasks
|
||||
|
||||
func registerBackgroundTasks() {
|
||||
BGTaskScheduler.shared.register(
|
||||
forTaskWithIdentifier: "com.app.refresh",
|
||||
using: nil
|
||||
) { task in
|
||||
self.handleAppRefresh(task: task as! BGAppRefreshTask)
|
||||
}
|
||||
}
|
||||
|
||||
func scheduleAppRefresh() {
|
||||
let request = BGAppRefreshTaskRequest(identifier: "com.app.refresh")
|
||||
request.earliestBeginDate = Date(timeIntervalSinceNow: 15 * 60)
|
||||
|
||||
do {
|
||||
try BGTaskScheduler.shared.submit(request)
|
||||
} catch {
|
||||
print("Could not schedule app refresh: \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
func handleAppRefresh(task: BGAppRefreshTask) {
|
||||
scheduleAppRefresh()
|
||||
|
||||
let operation = RefreshOperation()
|
||||
|
||||
task.expirationHandler = {
|
||||
operation.cancel()
|
||||
}
|
||||
|
||||
operation.completionBlock = {
|
||||
task.setTaskCompleted(success: !operation.isCancelled)
|
||||
}
|
||||
|
||||
OperationQueue.main.addOperation(operation)
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
*UIKit, Core Location, and Apple are trademarks of Apple Inc.*
|
||||
Reference in New Issue
Block a user