diff --git a/GIFCollector MessagesExtension/GIFCollector MessagesExtension.entitlements b/GIFCollector MessagesExtension/GIFCollector MessagesExtension.entitlements new file mode 100644 index 0000000..9620b3f --- /dev/null +++ b/GIFCollector MessagesExtension/GIFCollector MessagesExtension.entitlements @@ -0,0 +1,10 @@ + + + + + com.apple.security.application-groups + + group.com.abunchofknowitalls.gif + + + diff --git a/GIFCollector MessagesExtension/MessagesViewController.swift b/GIFCollector MessagesExtension/MessagesViewController.swift index 74cbfde..e039b01 100644 --- a/GIFCollector MessagesExtension/MessagesViewController.swift +++ b/GIFCollector MessagesExtension/MessagesViewController.swift @@ -15,6 +15,12 @@ class MessagesViewController: MSMessagesAppViewController { override func viewDidLoad() { super.viewDidLoad() setupChildViewController() + + // Register for notifications when app becomes active + NotificationCenter.default.addObserver(self, + selector: #selector(appDidBecomeActive), + name: UIApplication.didBecomeActiveNotification, + object: nil) } private func setupChildViewController() { @@ -90,11 +96,22 @@ class MessagesViewController: MSMessagesAppViewController { // Called when the extension is about to move from the inactive to active state. // This will happen when the extension is about to present UI. + // Check for GIFs shared from the Share Extension + GIFStorageService.shared.checkForSharedGIFs() + // Refresh GIFs list when becoming active gifCollectionVC?.viewWillAppear(true) } - override func didResignActive(with conversation: MSConversation) {} + override func didResignActive(with conversation: MSConversation) { + // No action needed when the extension becomes inactive + } + + @objc private func appDidBecomeActive() { + // Check for GIFs shared through the Share Extension + GIFStorageService.shared.checkForSharedGIFs() + gifCollectionVC?.loadGIFs() + } override func didReceive(_ message: MSMessage, conversation: MSConversation) {} override func didStartSending(_ message: MSMessage, conversation: MSConversation) {} override func didCancelSending(_ message: MSMessage, conversation: MSConversation) {} diff --git a/GIFCollector MessagesExtension/Services/GIFFileManager.swift b/GIFCollector MessagesExtension/Services/GIFFileManager.swift index 2d15cc5..f1dc734 100644 --- a/GIFCollector MessagesExtension/Services/GIFFileManager.swift +++ b/GIFCollector MessagesExtension/Services/GIFFileManager.swift @@ -11,6 +11,12 @@ class GIFFileManager { // MARK: - File Storage private var documentsDirectory: URL { + // First try to get the App Group container + if let containerURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.gifcollector") { + return containerURL + } + + // Fall back to the app's documents directory if App Group is not available let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask) return paths[0] } @@ -32,6 +38,11 @@ class GIFFileManager { } func storeGIF(data: Data, fromURL urlString: String) -> String? { + // Check if this is a shared GIF from the share extension + if let sharedGIFPath = checkForSharedGIF(withURL: urlString), FileManager.default.fileExists(atPath: sharedGIFPath) { + return sharedGIFPath + } + // Create a unique filename based on the URL hash and timestamp let urlHash = urlString.hashValue let timestamp = Int(Date().timeIntervalSince1970) @@ -129,4 +140,38 @@ class GIFFileManager { print("Error cleaning up old GIFs: \(error)") } } + + // Check if we already have this GIF in the shared container + private func checkForSharedGIF(withURL urlString: String) -> String? { + guard let containerURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.gifcollector") else { + return nil + } + + let sharedGIFsFolder = containerURL.appendingPathComponent("GIFs", isDirectory: true) + + guard FileManager.default.fileExists(atPath: sharedGIFsFolder.path) else { + return nil + } + + do { + let fileURLs = try FileManager.default.contentsOfDirectory(at: sharedGIFsFolder, includingPropertiesForKeys: nil) + + // Find any shared GIF that matches our URL (usually won't find any, but helps avoid duplicates) + let userDefaults = UserDefaults(suiteName: "group.gifcollector") + if let pendingGIFs = userDefaults?.array(forKey: "pendingGIFs") as? [[String: Any]] { + for gifInfo in pendingGIFs { + if let originURL = gifInfo["originalURL"] as? String, + let path = gifInfo["localFilePath"] as? String, + originURL == urlString { + return path + } + } + } + + return nil + } catch { + print("Error checking for shared GIFs: \(error)") + return nil + } + } } \ No newline at end of file diff --git a/GIFCollector MessagesExtension/Services/GIFStorageService.swift b/GIFCollector MessagesExtension/Services/GIFStorageService.swift index 5bc561b..fc1c835 100644 --- a/GIFCollector MessagesExtension/Services/GIFStorageService.swift +++ b/GIFCollector MessagesExtension/Services/GIFStorageService.swift @@ -5,12 +5,16 @@ class GIFStorageService { private let userDefaults = UserDefaults(suiteName: "group.gifcollector") private let savedGIFsKey = "savedGIFs" + private let pendingGIFsKey = "pendingGIFs" private init() { // Make sure the shared UserDefaults exists if userDefaults == nil { print("Error: Could not create UserDefaults with app group") } + + // Process any pending GIFs from the Share Extension + checkForSharedGIFs() } func saveGIF(data: Data, fromURL urlString: String, completion: @escaping (GIF?) -> Void) { @@ -86,4 +90,46 @@ class GIFStorageService { func getGIFData(for gif: GIF) -> Data? { return GIFFileManager.shared.loadGIFData(from: gif.localFilePath) } + + // MARK: - Share Extension Integration + + func checkForSharedGIFs() { + guard let pendingGIFsData = userDefaults?.array(forKey: pendingGIFsKey) as? [[String: Any]] else { + return + } + + guard !pendingGIFsData.isEmpty else { + return + } + + var savedGIFs = fetchGIFs() + var newGIFsAdded = false + + for gifInfo in pendingGIFsData { + if let localFilePath = gifInfo["localFilePath"] as? String, + let originalURL = gifInfo["originalURL"] as? String, + let createdAt = gifInfo["createdAt"] as? TimeInterval { + + // Create a GIF object + let gif = GIF( + localFilePath: localFilePath, + originalURL: originalURL + ) + + // Don't add duplicates + if !savedGIFs.contains(where: { $0.localFilePath == localFilePath }) { + savedGIFs.append(gif) + newGIFsAdded = true + } + } + } + + // Save updated GIFs list and clear pending ones + if newGIFsAdded { + saveToUserDefaults(gifs: savedGIFs) + } + + // Clear pending GIFs + userDefaults?.removeObject(forKey: pendingGIFsKey) + } } \ No newline at end of file diff --git a/GIFCollector MessagesExtension/Views/GIFCollectionViewController.swift b/GIFCollector MessagesExtension/Views/GIFCollectionViewController.swift index 3077654..3e8debe 100644 --- a/GIFCollector MessagesExtension/Views/GIFCollectionViewController.swift +++ b/GIFCollector MessagesExtension/Views/GIFCollectionViewController.swift @@ -93,7 +93,7 @@ class GIFCollectionViewController: UIViewController { updateEmptyState() } - private func loadGIFs() { + func loadGIFs() { gifs = GIFStorageService.shared.fetchGIFs() collectionView.reloadData() updateEmptyState() diff --git a/GIFCollector ShareExtension/Base.lproj/MainInterface.storyboard b/GIFCollector ShareExtension/Base.lproj/MainInterface.storyboard new file mode 100644 index 0000000..286a508 --- /dev/null +++ b/GIFCollector ShareExtension/Base.lproj/MainInterface.storyboard @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/GIFCollector ShareExtension/GIFCollector ShareExtension.entitlements b/GIFCollector ShareExtension/GIFCollector ShareExtension.entitlements new file mode 100644 index 0000000..9620b3f --- /dev/null +++ b/GIFCollector ShareExtension/GIFCollector ShareExtension.entitlements @@ -0,0 +1,10 @@ + + + + + com.apple.security.application-groups + + group.com.abunchofknowitalls.gif + + + diff --git a/GIFCollector ShareExtension/Info.plist b/GIFCollector ShareExtension/Info.plist new file mode 100644 index 0000000..71550c3 --- /dev/null +++ b/GIFCollector ShareExtension/Info.plist @@ -0,0 +1,25 @@ + + + + + NSExtension + + NSExtensionAttributes + + NSExtensionActivationRule + + NSExtensionActivationSupportsFileWithMaxCount + 1 + NSExtensionActivationSupportsImageWithMaxCount + 1 + NSExtensionActivationSupportsWebURLWithMaxCount + 1 + + + NSExtensionPrincipalClass + $(PRODUCT_MODULE_NAME).ShareViewController + NSExtensionPointIdentifier + com.apple.share-services + + + diff --git a/GIFCollector ShareExtension/ShareViewController.swift b/GIFCollector ShareExtension/ShareViewController.swift new file mode 100644 index 0000000..edb7182 --- /dev/null +++ b/GIFCollector ShareExtension/ShareViewController.swift @@ -0,0 +1,30 @@ +// +// ShareViewController.swift +// GIFCollector ShareExtension +// +// Created by Joshua Higgins on 6/3/25. +// + +import UIKit +import Social + +class ShareViewController: SLComposeServiceViewController { + + override func isContentValid() -> Bool { + // Do validation of contentText and/or NSExtensionContext attachments here + return true + } + + override func didSelectPost() { + // This is called after the user selects Post. Do the upload of contentText and/or NSExtensionContext attachments. + + // Inform the host that we're done, so it un-blocks its UI. Note: Alternatively you could call super's -didSelectPost, which will similarly complete the extension context. + self.extensionContext!.completeRequest(returningItems: [], completionHandler: nil) + } + + override func configurationItems() -> [Any]! { + // To add configuration options via table cells at the bottom of the sheet, return an array of SLComposeSheetConfigurationItem here. + return [] + } + +} diff --git a/GIFCollector.xcodeproj/project.pbxproj b/GIFCollector.xcodeproj/project.pbxproj index d51dc94..94b19e2 100644 --- a/GIFCollector.xcodeproj/project.pbxproj +++ b/GIFCollector.xcodeproj/project.pbxproj @@ -9,6 +9,9 @@ /* Begin PBXBuildFile section */ 1B3192822DEDCF86007850B9 /* GIFCollector MessagesExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 1B3192812DEDCF86007850B9 /* GIFCollector MessagesExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 1B3192872DEDCF86007850B9 /* Messages.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1B3192862DEDCF86007850B9 /* Messages.framework */; }; + 1BDF21552DEFEF6B00128C3C /* GIFCollector ShareExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 1BDF21462DEFE9A500128C3C /* GIFCollector ShareExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; + 1BDF21722DEFF72800128C3C /* GIFCollector MessagesExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 1B3192812DEDCF86007850B9 /* GIFCollector MessagesExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; + 1BDF21752DEFF72800128C3C /* GIFCollector ShareExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 1BDF21462DEFE9A500128C3C /* GIFCollector ShareExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -19,6 +22,41 @@ remoteGlobalIDString = 1B3192802DEDCF86007850B9; remoteInfo = "GIFCollector MessagesExtension"; }; + 1BDF21562DEFEF6B00128C3C /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 1B3192722DEDCF83007850B9 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 1BDF21452DEFE9A500128C3C; + remoteInfo = "GIFCollector ShareExtension"; + }; + 1BDF21732DEFF72800128C3C /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 1B3192722DEDCF83007850B9 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 1B3192802DEDCF86007850B9; + remoteInfo = "GIFCollector MessagesExtension"; + }; + 1BDF21762DEFF72800128C3C /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 1B3192722DEDCF83007850B9 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 1BDF21452DEFE9A500128C3C; + remoteInfo = "GIFCollector ShareExtension"; + }; + 1BDF21792DEFF72D00128C3C /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 1B3192722DEDCF83007850B9 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 1BDF21452DEFE9A500128C3C; + remoteInfo = "GIFCollector ShareExtension"; + }; + 1BDF217B2DEFF73000128C3C /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 1B3192722DEDCF83007850B9 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 1B3192802DEDCF86007850B9; + remoteInfo = "GIFCollector MessagesExtension"; + }; /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -28,17 +66,32 @@ dstPath = ""; dstSubfolderSpec = 13; files = ( + 1BDF21552DEFEF6B00128C3C /* GIFCollector ShareExtension.appex in Embed Foundation Extensions */, 1B3192822DEDCF86007850B9 /* GIFCollector MessagesExtension.appex in Embed Foundation Extensions */, ); name = "Embed Foundation Extensions"; runOnlyForDeploymentPostprocessing = 0; }; + 1BDF21782DEFF72800128C3C /* Embed Foundation Extensions */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 13; + files = ( + 1BDF21752DEFF72800128C3C /* GIFCollector ShareExtension.appex in Embed Foundation Extensions */, + 1BDF21722DEFF72800128C3C /* GIFCollector MessagesExtension.appex in Embed Foundation Extensions */, + ); + name = "Embed Foundation Extensions"; + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ 1B3192782DEDCF83007850B9 /* GIFCollector.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = GIFCollector.app; sourceTree = BUILT_PRODUCTS_DIR; }; 1B3192812DEDCF86007850B9 /* GIFCollector MessagesExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "GIFCollector MessagesExtension.appex"; sourceTree = BUILT_PRODUCTS_DIR; }; 1B3192862DEDCF86007850B9 /* Messages.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Messages.framework; path = System/Library/Frameworks/Messages.framework; sourceTree = SDKROOT; }; + 1BDF21462DEFE9A500128C3C /* GIFCollector ShareExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "GIFCollector ShareExtension.appex"; sourceTree = BUILT_PRODUCTS_DIR; }; + 1BDF21652DEFF71200128C3C /* GIFCollector App.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "GIFCollector App.app"; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ /* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */ @@ -49,6 +102,13 @@ ); target = 1B3192802DEDCF86007850B9 /* GIFCollector MessagesExtension */; }; + 1BDF214E2DEFE9A500128C3C /* Exceptions for "GIFCollector ShareExtension" folder in "GIFCollector ShareExtension" target */ = { + isa = PBXFileSystemSynchronizedBuildFileExceptionSet; + membershipExceptions = ( + Info.plist, + ); + target = 1BDF21452DEFE9A500128C3C /* GIFCollector ShareExtension */; + }; /* End PBXFileSystemSynchronizedBuildFileExceptionSet section */ /* Begin PBXFileSystemSynchronizedRootGroup section */ @@ -65,6 +125,14 @@ path = "GIFCollector MessagesExtension"; sourceTree = ""; }; + 1BDF21472DEFE9A500128C3C /* GIFCollector ShareExtension */ = { + isa = PBXFileSystemSynchronizedRootGroup; + exceptions = ( + 1BDF214E2DEFE9A500128C3C /* Exceptions for "GIFCollector ShareExtension" folder in "GIFCollector ShareExtension" target */, + ); + path = "GIFCollector ShareExtension"; + sourceTree = ""; + }; /* End PBXFileSystemSynchronizedRootGroup section */ /* Begin PBXFrameworksBuildPhase section */ @@ -76,6 +144,20 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 1BDF21432DEFE9A500128C3C /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 1BDF21622DEFF71200128C3C /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ @@ -84,6 +166,7 @@ children = ( 1B31927A2DEDCF83007850B9 /* GIFCollector */, 1B3192882DEDCF86007850B9 /* GIFCollector MessagesExtension */, + 1BDF21472DEFE9A500128C3C /* GIFCollector ShareExtension */, 1B3192852DEDCF86007850B9 /* Frameworks */, 1B3192792DEDCF83007850B9 /* Products */, ); @@ -94,6 +177,8 @@ children = ( 1B3192782DEDCF83007850B9 /* GIFCollector.app */, 1B3192812DEDCF86007850B9 /* GIFCollector MessagesExtension.appex */, + 1BDF21462DEFE9A500128C3C /* GIFCollector ShareExtension.appex */, + 1BDF21652DEFF71200128C3C /* GIFCollector App.app */, ); name = Products; sourceTree = ""; @@ -120,6 +205,7 @@ ); dependencies = ( 1B3192842DEDCF86007850B9 /* PBXTargetDependency */, + 1BDF21572DEFEF6B00128C3C /* PBXTargetDependency */, ); fileSystemSynchronizedGroups = ( 1B31927A2DEDCF83007850B9 /* GIFCollector */, @@ -153,6 +239,52 @@ productReference = 1B3192812DEDCF86007850B9 /* GIFCollector MessagesExtension.appex */; productType = "com.apple.product-type.app-extension.messages"; }; + 1BDF21452DEFE9A500128C3C /* GIFCollector ShareExtension */ = { + isa = PBXNativeTarget; + buildConfigurationList = 1BDF214F2DEFE9A500128C3C /* Build configuration list for PBXNativeTarget "GIFCollector ShareExtension" */; + buildPhases = ( + 1BDF21422DEFE9A500128C3C /* Sources */, + 1BDF21432DEFE9A500128C3C /* Frameworks */, + 1BDF21442DEFE9A500128C3C /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + fileSystemSynchronizedGroups = ( + 1BDF21472DEFE9A500128C3C /* GIFCollector ShareExtension */, + ); + name = "GIFCollector ShareExtension"; + packageProductDependencies = ( + ); + productName = "GIFCollector ShareExtension"; + productReference = 1BDF21462DEFE9A500128C3C /* GIFCollector ShareExtension.appex */; + productType = "com.apple.product-type.app-extension"; + }; + 1BDF21642DEFF71200128C3C /* GIFCollector App */ = { + isa = PBXNativeTarget; + buildConfigurationList = 1BDF216F2DEFF71300128C3C /* Build configuration list for PBXNativeTarget "GIFCollector App" */; + buildPhases = ( + 1BDF21612DEFF71200128C3C /* Sources */, + 1BDF21622DEFF71200128C3C /* Frameworks */, + 1BDF21632DEFF71200128C3C /* Resources */, + 1BDF21782DEFF72800128C3C /* Embed Foundation Extensions */, + ); + buildRules = ( + ); + dependencies = ( + 1BDF21742DEFF72800128C3C /* PBXTargetDependency */, + 1BDF21772DEFF72800128C3C /* PBXTargetDependency */, + 1BDF217A2DEFF72D00128C3C /* PBXTargetDependency */, + 1BDF217C2DEFF73000128C3C /* PBXTargetDependency */, + ); + name = "GIFCollector App"; + packageProductDependencies = ( + ); + productName = "GIFCollector App"; + productReference = 1BDF21652DEFF71200128C3C /* GIFCollector App.app */; + productType = "com.apple.product-type.application"; + }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ @@ -169,6 +301,12 @@ 1B3192802DEDCF86007850B9 = { CreatedOnToolsVersion = 16.4; }; + 1BDF21452DEFE9A500128C3C = { + CreatedOnToolsVersion = 16.4; + }; + 1BDF21642DEFF71200128C3C = { + CreatedOnToolsVersion = 16.4; + }; }; }; buildConfigurationList = 1B3192752DEDCF83007850B9 /* Build configuration list for PBXProject "GIFCollector" */; @@ -187,6 +325,8 @@ targets = ( 1B3192772DEDCF83007850B9 /* GIFCollector */, 1B3192802DEDCF86007850B9 /* GIFCollector MessagesExtension */, + 1BDF21452DEFE9A500128C3C /* GIFCollector ShareExtension */, + 1BDF21642DEFF71200128C3C /* GIFCollector App */, ); }; /* End PBXProject section */ @@ -206,6 +346,20 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 1BDF21442DEFE9A500128C3C /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 1BDF21632DEFF71200128C3C /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -216,6 +370,20 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 1BDF21422DEFE9A500128C3C /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 1BDF21612DEFF71200128C3C /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ @@ -224,6 +392,31 @@ target = 1B3192802DEDCF86007850B9 /* GIFCollector MessagesExtension */; targetProxy = 1B3192832DEDCF86007850B9 /* PBXContainerItemProxy */; }; + 1BDF21572DEFEF6B00128C3C /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 1BDF21452DEFE9A500128C3C /* GIFCollector ShareExtension */; + targetProxy = 1BDF21562DEFEF6B00128C3C /* PBXContainerItemProxy */; + }; + 1BDF21742DEFF72800128C3C /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 1B3192802DEDCF86007850B9 /* GIFCollector MessagesExtension */; + targetProxy = 1BDF21732DEFF72800128C3C /* PBXContainerItemProxy */; + }; + 1BDF21772DEFF72800128C3C /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 1BDF21452DEFE9A500128C3C /* GIFCollector ShareExtension */; + targetProxy = 1BDF21762DEFF72800128C3C /* PBXContainerItemProxy */; + }; + 1BDF217A2DEFF72D00128C3C /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 1BDF21452DEFE9A500128C3C /* GIFCollector ShareExtension */; + targetProxy = 1BDF21792DEFF72D00128C3C /* PBXContainerItemProxy */; + }; + 1BDF217C2DEFF73000128C3C /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 1B3192802DEDCF86007850B9 /* GIFCollector MessagesExtension */; + targetProxy = 1BDF217B2DEFF73000128C3C /* PBXContainerItemProxy */; + }; /* End PBXTargetDependency section */ /* Begin XCBuildConfiguration section */ @@ -231,8 +424,9 @@ isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = "iMessage App Icon"; + CODE_SIGN_ENTITLEMENTS = "GIFCollector MessagesExtension/GIFCollector MessagesExtension.entitlements"; CODE_SIGN_IDENTITY = ""; - CODE_SIGN_INJECT_BASE_ENTITLEMENTS = NO; + CODE_SIGN_INJECT_BASE_ENTITLEMENTS = YES; CODE_SIGN_STYLE = Manual; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = ""; @@ -245,7 +439,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 1.0; + MARKETING_VERSION = 100.2.0; PRODUCT_BUNDLE_IDENTIFIER = com.abunchofknowitalls.GIFCollector.MessagesExtension; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -260,8 +454,9 @@ isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = "iMessage App Icon"; + CODE_SIGN_ENTITLEMENTS = "GIFCollector MessagesExtension/GIFCollector MessagesExtension.entitlements"; CODE_SIGN_IDENTITY = ""; - CODE_SIGN_INJECT_BASE_ENTITLEMENTS = NO; + CODE_SIGN_INJECT_BASE_ENTITLEMENTS = YES; CODE_SIGN_STYLE = Manual; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = ""; @@ -274,7 +469,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 1.0; + MARKETING_VERSION = 100.2.0; PRODUCT_BUNDLE_IDENTIFIER = com.abunchofknowitalls.GIFCollector.MessagesExtension; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -337,7 +532,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 18.5; + IPHONEOS_DEPLOYMENT_TARGET = 18.0; LOCALIZATION_PREFERS_STRING_CATALOGS = YES; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; @@ -394,7 +589,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 18.5; + IPHONEOS_DEPLOYMENT_TARGET = 18.0; LOCALIZATION_PREFERS_STRING_CATALOGS = YES; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; @@ -409,7 +604,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_IDENTITY = ""; - CODE_SIGN_INJECT_BASE_ENTITLEMENTS = NO; + CODE_SIGN_INJECT_BASE_ENTITLEMENTS = YES; CODE_SIGN_STYLE = Manual; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = ""; @@ -418,7 +613,7 @@ INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; - MARKETING_VERSION = 1.0; + MARKETING_VERSION = 100.2.0; PRODUCT_BUNDLE_IDENTIFIER = com.abunchofknowitalls.GIFCollector; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; @@ -432,7 +627,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_IDENTITY = ""; - CODE_SIGN_INJECT_BASE_ENTITLEMENTS = NO; + CODE_SIGN_INJECT_BASE_ENTITLEMENTS = YES; CODE_SIGN_STYLE = Manual; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = ""; @@ -441,7 +636,7 @@ INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; - MARKETING_VERSION = 1.0; + MARKETING_VERSION = 100.2.0; PRODUCT_BUNDLE_IDENTIFIER = com.abunchofknowitalls.GIFCollector; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; @@ -450,6 +645,120 @@ }; name = Release; }; + 1BDF21502DEFE9A500128C3C /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_ENTITLEMENTS = "GIFCollector ShareExtension/GIFCollector ShareExtension.entitlements"; + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_INJECT_BASE_ENTITLEMENTS = YES; + CODE_SIGN_STYLE = Manual; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = ""; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = "GIFCollector ShareExtension/Info.plist"; + INFOPLIST_KEY_CFBundleDisplayName = "GIFCollector ShareExtension"; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + MARKETING_VERSION = 100.2.0; + PRODUCT_BUNDLE_IDENTIFIER = com.abunchofknowitalls.GIFCollector.ShareExtension; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SKIP_INSTALL = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 1BDF21512DEFE9A500128C3C /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_ENTITLEMENTS = "GIFCollector ShareExtension/GIFCollector ShareExtension.entitlements"; + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_INJECT_BASE_ENTITLEMENTS = YES; + CODE_SIGN_STYLE = Manual; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = ""; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = "GIFCollector ShareExtension/Info.plist"; + INFOPLIST_KEY_CFBundleDisplayName = "GIFCollector ShareExtension"; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + MARKETING_VERSION = 100.2.0; + PRODUCT_BUNDLE_IDENTIFIER = com.abunchofknowitalls.GIFCollector.ShareExtension; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SKIP_INSTALL = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; + 1BDF21702DEFF71300128C3C /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchScreen_Generation = YES; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + IPHONEOS_DEPLOYMENT_TARGET = 18.5; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = "com.abunchofknowitalls.GIFCollector-App"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 1BDF21712DEFF71300128C3C /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchScreen_Generation = YES; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + IPHONEOS_DEPLOYMENT_TARGET = 18.5; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = "com.abunchofknowitalls.GIFCollector-App"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ @@ -480,6 +789,24 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + 1BDF214F2DEFE9A500128C3C /* Build configuration list for PBXNativeTarget "GIFCollector ShareExtension" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 1BDF21502DEFE9A500128C3C /* Debug */, + 1BDF21512DEFE9A500128C3C /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 1BDF216F2DEFF71300128C3C /* Build configuration list for PBXNativeTarget "GIFCollector App" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 1BDF21702DEFF71300128C3C /* Debug */, + 1BDF21712DEFF71300128C3C /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; /* End XCConfigurationList section */ }; rootObject = 1B3192722DEDCF83007850B9 /* Project object */; diff --git a/GIFCollector.xcodeproj/xcshareddata/xcschemes/GIFCollector MessagesExtension.xcscheme b/GIFCollector.xcodeproj/xcshareddata/xcschemes/GIFCollector MessagesExtension.xcscheme new file mode 100644 index 0000000..bc31b9d --- /dev/null +++ b/GIFCollector.xcodeproj/xcshareddata/xcschemes/GIFCollector MessagesExtension.xcscheme @@ -0,0 +1,107 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/GIFCollector.xcodeproj/xcshareddata/xcschemes/GIFCollector.xcscheme b/GIFCollector.xcodeproj/xcshareddata/xcschemes/GIFCollector.xcscheme new file mode 100644 index 0000000..79f6210 --- /dev/null +++ b/GIFCollector.xcodeproj/xcshareddata/xcschemes/GIFCollector.xcscheme @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/GIFCollector/AppDelegate.swift b/GIFCollector/AppDelegate.swift new file mode 100644 index 0000000..4dc6cef --- /dev/null +++ b/GIFCollector/AppDelegate.swift @@ -0,0 +1,21 @@ +import UIKit + +@main +class AppDelegate: UIResponder, UIApplicationDelegate { + + var window: UIWindow? + + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + // Create window + window = UIWindow(frame: UIScreen.main.bounds) + + // Create and set the root view controller + let viewController = ViewController() + window?.rootViewController = viewController + + // Make the window visible + window?.makeKeyAndVisible() + + return true + } +} \ No newline at end of file diff --git a/GIFCollector/ViewController.swift b/GIFCollector/ViewController.swift new file mode 100644 index 0000000..430e0b2 --- /dev/null +++ b/GIFCollector/ViewController.swift @@ -0,0 +1,29 @@ +import UIKit + +class ViewController: UIViewController { + + private let helloLabel: UILabel = { + let label = UILabel() + label.text = "Hello World!" + label.font = UIFont.systemFont(ofSize: 24, weight: .bold) + label.textAlignment = .center + label.translatesAutoresizingMaskIntoConstraints = false + return label + }() + + override func viewDidLoad() { + super.viewDidLoad() + setupUI() + } + + private func setupUI() { + view.backgroundColor = .white + + view.addSubview(helloLabel) + + NSLayoutConstraint.activate([ + helloLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor), + helloLabel.centerYAnchor.constraint(equalTo: view.centerYAnchor) + ]) + } +} \ No newline at end of file