privacy-compliance/SKILL.md
iOS/macOS privacy compliance including Privacy Manifests, App Tracking Transparency (ATT), Required Reason APIs, GDPR, and App Store privacy requirements. Essential for App Store approval.
npx skillsauth add abanoub-ashraf/manus-skills-import privacy-complianceInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Apple has significantly increased privacy requirements. Non-compliance can result in App Store rejection. This skill covers everything you need for privacy compliance.
Starting Spring 2024, apps must declare privacy practices in a Privacy Manifest file.
PrivacyInfo.xcprivacy in your project<?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>
<!-- Privacy Tracking -->
<key>NSPrivacyTracking</key>
<false/>
<!-- Tracking Domains (if tracking is true) -->
<key>NSPrivacyTrackingDomains</key>
<array>
<string>analytics.example.com</string>
</array>
<!-- Required Reason APIs -->
<key>NSPrivacyAccessedAPITypes</key>
<array>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategoryUserDefaults</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>CA92.1</string>
</array>
</dict>
</array>
<!-- Data Collection -->
<key>NSPrivacyCollectedDataTypes</key>
<array>
<dict>
<key>NSPrivacyCollectedDataType</key>
<string>NSPrivacyCollectedDataTypeEmailAddress</string>
<key>NSPrivacyCollectedDataTypeLinked</key>
<true/>
<key>NSPrivacyCollectedDataTypeTracking</key>
<false/>
<key>NSPrivacyCollectedDataTypePurposes</key>
<array>
<string>NSPrivacyCollectedDataTypePurposeAppFunctionality</string>
</array>
</dict>
</array>
</dict>
</plist>
These APIs now require justification in your Privacy Manifest:
// Requires reason: DDA9.1, C617.1, 3B52.1, or 0A2A.1
let attributes = try FileManager.default.attributesOfItem(atPath: path)
let modDate = attributes[.modificationDate]
Approved Reasons:
DDA9.1: Display to userC617.1: Access timestamps inside app container3B52.1: Access timestamps from user-granted access0A2A.1: Third-party SDK for above purposes// Requires reason: 35F9.1, 8FFB.1, 3D61.1
let bootTime = ProcessInfo.processInfo.systemUptime
Approved Reasons:
35F9.1: Measure elapsed time (not absolute time)8FFB.1: Calculate absolute timestamps for user display3D61.1: Include in bug reports// Requires reason: 85F4.1, E174.1, 7D9E.1, B728.1
let freeSpace = try URL(fileURLWithPath: "/")
.resourceValues(forKeys: [.volumeAvailableCapacityKey])
.volumeAvailableCapacity
Approved Reasons:
85F4.1: Display to userE174.1: Check before writing files7D9E.1: Submit with bug reportsB728.1: Check disk space for app functionality// Requires reason: CA92.1, 1C8F.1, C56D.1
UserDefaults.standard.set(value, forKey: key)
Approved Reasons:
CA92.1: Access user defaults within your app1C8F.1: Access managed app configurationC56D.1: Third-party SDK for above purposes// Requires reason: 3EC4.1, 54BD.1
let keyboards = UITextInputMode.activeInputModes
Approved Reasons:
3EC4.1: Customize UI based on keyboard54BD.1: Implement custom keyboardRequired when tracking users across apps or websites owned by other companies.
import AppTrackingTransparency
import AdSupport
class TrackingManager {
static let shared = TrackingManager()
func requestPermission() async -> ATTrackingManager.AuthorizationStatus {
// Wait for app to be active
await MainActor.run {
if UIApplication.shared.applicationState != .active {
// Schedule for later
}
}
return await ATTrackingManager.requestTrackingAuthorization()
}
var canTrack: Bool {
ATTrackingManager.trackingAuthorizationStatus == .authorized
}
var advertisingIdentifier: String? {
guard canTrack else { return nil }
let idfa = ASIdentifierManager.shared().advertisingIdentifier
return idfa.uuidString
}
}
// Usage
Task {
let status = await TrackingManager.shared.requestPermission()
switch status {
case .authorized:
// Can use IDFA for tracking
analytics.enableTracking()
case .denied, .restricted:
// Use privacy-preserving alternatives
analytics.usePrivateMode()
case .notDetermined:
// User hasn't seen prompt yet
break
@unknown default:
break
}
}
<key>NSUserTrackingUsageDescription</key>
<string>We use this identifier to show you personalized ads and measure ad effectiveness.</string>
Required:
NOT Required:
import StoreKit
// Register for attribution
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Register for ad network attribution
SKAdNetwork.registerAppForAdNetworkAttribution()
return true
}
}
// Update conversion value (0-63)
func updateConversionValue(_ value: Int) {
if #available(iOS 16.1, *) {
SKAdNetwork.updatePostbackConversionValue(value) { error in
if let error = error {
print("Failed to update conversion value: \(error)")
}
}
} else {
SKAdNetwork.updateConversionValue(value)
}
}
<key>SKAdNetworkItems</key>
<array>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>cstr6suwn9.skadnetwork</string>
</dict>
<!-- Add all ad network IDs -->
</array>
Required information for App Store Connect:
import UIKit
class ConsentManager: ObservableObject {
@Published var hasConsent: Bool = false
@Published var analyticsConsent: Bool = false
@Published var marketingConsent: Bool = false
static let shared = ConsentManager()
private let defaults = UserDefaults.standard
init() {
loadConsent()
}
func showConsentDialog() {
// Present consent UI
}
func updateConsent(analytics: Bool, marketing: Bool) {
analyticsConsent = analytics
marketingConsent = marketing
hasConsent = true
saveConsent()
// Update SDK configurations
configureAnalytics(enabled: analytics)
configureMarketing(enabled: marketing)
}
func withdrawConsent() {
analyticsConsent = false
marketingConsent = false
saveConsent()
// Disable all tracking
disableAllTracking()
}
private func saveConsent() {
defaults.set(hasConsent, forKey: "hasConsent")
defaults.set(analyticsConsent, forKey: "analyticsConsent")
defaults.set(marketingConsent, forKey: "marketingConsent")
}
private func loadConsent() {
hasConsent = defaults.bool(forKey: "hasConsent")
analyticsConsent = defaults.bool(forKey: "analyticsConsent")
marketingConsent = defaults.bool(forKey: "marketingConsent")
}
}
struct GDPRService {
func requestDataDeletion(userId: String) async throws {
// 1. Delete from local storage
deleteLocalData(userId: userId)
// 2. Request deletion from server
try await api.requestDeletion(userId: userId)
// 3. Clear keychain
KeychainService.deleteAll(for: userId)
// 4. Reset analytics
Analytics.reset()
// 5. Log out user
AuthService.shared.logout()
}
func exportUserData(userId: String) async throws -> Data {
// Export all user data in machine-readable format
let userData = try await api.exportUserData(userId: userId)
return try JSONEncoder().encode(userData)
}
}
// Bad: Collect everything "just in case"
struct Analytics {
func track(event: String,
userId: String,
deviceId: String,
location: CLLocation,
contacts: [Contact]) { }
}
// Good: Collect only what's needed
struct Analytics {
func track(event: String,
anonymousId: String) { }
}
// Process on device when possible
class RecommendationEngine {
func getRecommendations(for items: [Item]) -> [Item] {
// Use Core ML for on-device ML
// No data sent to server
return model.predict(items)
}
}
import CryptoKit
struct SecureStorage {
static func encrypt(_ data: Data, key: SymmetricKey) throws -> Data {
try AES.GCM.seal(data, using: key).combined!
}
static func decrypt(_ data: Data, key: SymmetricKey) throws -> Data {
let box = try AES.GCM.SealedBox(combined: data)
return try AES.GCM.open(box, using: key)
}
}
// Always use HTTPS
// Pin certificates for sensitive data
class NetworkSecurity {
func createSession() -> URLSession {
let config = URLSessionConfiguration.default
config.tlsMinimumSupportedProtocolVersion = .TLSv12
return URLSession(configuration: config, delegate: PinningDelegate(), delegateQueue: nil)
}
}
class PinningDelegate: NSObject, URLSessionDelegate {
func urlSession(_ session: URLSession,
didReceive challenge: URLAuthenticationChallenge,
completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
// Implement certificate pinning
}
}
PrivacyInfo.xcprivacy exists in projectdevelopment
Design principles for building polished, native-feeling SwiftUI apps and widgets. Use this skill when creating or modifying SwiftUI views, iOS widgets (WidgetKit), or any native Apple UI. Ensures proper spacing, typography, colors, and widget implementations that look and feel like quality apps rather than AI-generated slop.
data-ai
Design and implement SwiftUI views, components, and app architecture. Use when creating new SwiftUI views, implementing MVVM/TCA patterns, managing state with @Observable, @State, @Binding, or @Environment, designing navigation flows, or structuring iOS app architecture. Triggers on SwiftUI, view model, state management, navigation, coordinator pattern.
development
Implement, review, or improve SwiftUI animations and transitions. Use when adding implicit or explicit animations with withAnimation, configuring spring animations (.smooth, .snappy, .bouncy), building phase or keyframe animations with PhaseAnimator/KeyframeAnimator, creating hero transitions with matchedGeometryEffect or matchedTransitionSource, adding SF Symbol effects (bounce, pulse, variableColor, breathe, rotate, wiggle), implementing custom Transition or CustomAnimation types, or ensuring animations respect accessibilityReduceMotion.
testing
Audit SwiftUI views for accessibility (iOS + macOS) with patch-ready fixes