Closed AimanKyo97 closed 3 weeks ago
By making these changes I am able to get data in IOS:
In Runner -> Build Phase reorder the list also work to get IOS app in share tray.
By making these changes I am able to get data in IOS:
- In ShareHandlerIosViewController -> loadIds() -> Replace ShareHandlerIosViewController.hostAppBundleIdentifier with your app bundle identifier and ShareHandlerIosViewController.appGroupId with your appGroupId
In Runner -> Build Phase reorder the list also work to get IOS app in share tray.
@simranKa I have try it but the data i receive still null, below is example code i used in ShareHandlerIosViewController, and Build Phases in my project
// // ShareHandlerIosViewController.swift // Pods // // Created by Josh Juncker on 7/7/22. //
import UIKit import Social import MobileCoreServices import Photos import Intents import Contacts
@available(iOS 14.0, ) @available(iOSApplicationExtension 14.0, ) open class ShareHandlerIosViewController: UIViewController { static var hostAppBundleIdentifier = "com.xxx.xxx” static var appGroupId = "group.com.xxx.xxx” let sharedKey = "ShareKey" var sharedText: [String] = [] let imageContentType = UTType.image.identifier let movieContentType = UTType.movie.identifier let textContentType = UTType.text.identifier let urlContentType = UTType.url.identifier let fileURLType = UTType.fileURL.identifier let dataContentType = UTType.data.identifier var sharedAttachments: [SharedAttachment] = [] lazy var userDefaults: UserDefaults = { return UserDefaults(suiteName: ShareHandlerIosViewController.appGroupId)! }()
public func loadIds() {
// loading Share extension App Id
let shareExtensionAppBundleIdentifier = Bundle.main.bundleIdentifier!;
// convert ShareExtension id to host app id
// By default it is remove last part of id after last point
// For example: com.test.ShareExtension -> com.test
let lastIndexOfPoint = shareExtensionAppBundleIdentifier.lastIndex(of: ".");
ShareHandlerIosViewController.hostAppBundleIdentifier = "com.xxx.xxx";
// loading custom AppGroupId from Build Settings or use group.<hostAppBundleIdentifier>
ShareHandlerIosViewController.appGroupId = "group.com.xxx.xxx”;
}
public override func viewDidLoad() {
super.viewDidLoad();
// load group and app id from build info
loadIds();
Task {
await handleInputItems()
}
}
public override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
extensionContext!.completeRequest(returningItems: [], completionHandler: nil)
}
func handleInputItems() async {
if let content = extensionContext!.inputItems[0] as? NSExtensionItem {
if let contents = content.attachments {
for (index, attachment) in (contents).enumerated() {
do {
if attachment.hasItemConformingToTypeIdentifier(imageContentType) {
try await handleImages(content: content, attachment: attachment, index: index)
} else if attachment.hasItemConformingToTypeIdentifier(movieContentType) {
try await handleVideos(content: content, attachment: attachment, index: index)
} else if attachment.hasItemConformingToTypeIdentifier(fileURLType){
try await handleFiles(content: content, attachment: attachment, index: index)
} else if attachment.hasItemConformingToTypeIdentifier(urlContentType) {
try await handleUrl(content: content, attachment: attachment, index: index)
} else if attachment.hasItemConformingToTypeIdentifier(textContentType) {
try await handleText(content: content, attachment: attachment, index: index)
} else if attachment.hasItemConformingToTypeIdentifier(dataContentType) {
try await handleData(content: content, attachment: attachment, index: index)
} else {
print("Attachment not handled with registered type identifiers: \(attachment.registeredTypeIdentifiers)")
}
} catch {
self.dismissWithError()
}
}
}
redirectToHostApp()
}
}
public func getNewFileUrl(fileName: String) -> URL {
let newFileUrl = FileManager.default
.containerURL(forSecurityApplicationGroupIdentifier: ShareHandlerIosViewController.appGroupId)!
.appendingPathComponent(fileName)
return newFileUrl
}
public func handleText (content: NSExtensionItem, attachment: NSItemProvider, index: Int) async throws {
let data = try await attachment.loadItem(forTypeIdentifier: textContentType, options: nil)
if let item = data as? String {
sharedText.append(item)
} else {
if let d = data as? Data {
do{
let contacts = try CNContactVCardSerialization.contacts(with: d)
for contact in contacts {
let data = try CNContactVCardSerialization.data(with: [contact])
let str = String(data: data, encoding: .utf8)!
sharedText.append(str)
}
} catch {
dismissWithError()
}
} else {
dismissWithError()
}
}
}
public func handleUrl (content: NSExtensionItem, attachment: NSItemProvider, index: Int) async throws {
let data = try await attachment.loadItem(forTypeIdentifier: urlContentType, options: nil)
if let item = data as? URL {
sharedText.append(item.absoluteString)
} else {
dismissWithError()
}
}
public func handleImages (content: NSExtensionItem, attachment: NSItemProvider, index: Int) async throws {
let data = try await attachment.loadItem(forTypeIdentifier: imageContentType, options: nil)
var fileName: String?
var imageData: Data?
var sourceUrl: URL?
if let url = data as? URL {
fileName = getFileName(from: url, type: .image)
sourceUrl = url
} else if let iData = data as? Data {
fileName = UUID().uuidString + ".png"
imageData = iData
} else if let image = data as? UIImage {
fileName = UUID().uuidString + ".png"
imageData = image.pngData()
}
if let _fileName = fileName {
let newFileUrl = getNewFileUrl(fileName: _fileName)
do {
if FileManager.default.fileExists(atPath: newFileUrl.path) {
try FileManager.default.removeItem(at: newFileUrl)
}
} catch {
print("Error removing item")
}
var copied: Bool = false
if let _data = imageData {
copied = FileManager.default.createFile(atPath: newFileUrl.path, contents: _data)
} else if let _sourceUrl = sourceUrl {
copied = copyFile(at: _sourceUrl, to: newFileUrl)
}
if (copied) {
sharedAttachments.append(SharedAttachment.init(path: newFileUrl.absoluteString, type: .image))
} else {
dismissWithError()
return
}
} else {
dismissWithError()
return
}
}
public func handleVideos (content: NSExtensionItem, attachment: NSItemProvider, index: Int) async throws {
let data = try await attachment.loadItem(forTypeIdentifier: movieContentType, options: nil)
if let url = data as? URL {
// Always copy
let fileName = getFileName(from: url, type: .video)
let newFileUrl = getNewFileUrl(fileName: fileName)
let copied = copyFile(at: url, to: newFileUrl)
if(copied) {
sharedAttachments.append(SharedAttachment.init(path: newFileUrl.absoluteString, type: .video))
}
} else {
dismissWithError()
}
}
public func handleFiles (content: NSExtensionItem, attachment: NSItemProvider, index: Int) async throws {
let data = try await attachment.loadItem(forTypeIdentifier: fileURLType, options: nil)
if let url = data as? URL {
// Always copy
let fileName = getFileName(from :url, type: .file)
let newFileUrl = getNewFileUrl(fileName: fileName)
let copied = copyFile(at: url, to: newFileUrl)
if (copied) {
sharedAttachments.append(SharedAttachment.init(path: newFileUrl.absoluteString, type: .file))
}
} else {
dismissWithError()
}
}
public func handleData (content: NSExtensionItem, attachment: NSItemProvider, index: Int) async throws {
let data = try await attachment.loadItem(forTypeIdentifier: dataContentType, options: nil)
if let url = data as? URL {
// Always copy
let fileName = getFileName(from :url, type: .file)
let newFileUrl = getNewFileUrl(fileName: fileName)
let copied = copyFile(at: url, to: newFileUrl)
if (copied) {
sharedAttachments.append(SharedAttachment.init(path: newFileUrl.absoluteString, type: .file))
}
} else {
dismissWithError()
}
}
public func dismissWithError() {
print("[ERROR] Error loading data!")
let alert = UIAlertController(title: "Error", message: "Error loading data", preferredStyle: .alert)
let action = UIAlertAction(title: "Error", style: .cancel) { _ in
self.dismiss(animated: true, completion: nil)
}
alert.addAction(action)
present(alert, animated: true, completion: nil)
extensionContext!.completeRequest(returningItems: [], completionHandler: nil)
}
public func redirectToHostApp() {
// ids may not loaded yet so we need loadIds here too
loadIds();
let url = URL(string: "ShareMedia-com.xxx.xxx://com.xxx.xxx?key=\(sharedKey)")
var responder = self as UIResponder?
let selectorOpenURL = sel_registerName("openURL:")
let intent = self.extensionContext?.intent as? INSendMessageIntent
let conversationIdentifier = intent?.conversationIdentifier
let sender = intent?.sender
let serviceName = intent?.serviceName
let speakableGroupName = intent?.speakableGroupName
let sharedMedia = SharedMedia.init(attachments: sharedAttachments, conversationIdentifier: conversationIdentifier, content: sharedText.joined(separator: "\n"), speakableGroupName: speakableGroupName?.spokenPhrase, serviceName: serviceName, senderIdentifier: sender?.contactIdentifier ?? sender?.customIdentifier, imageFilePath: nil)
let json = sharedMedia.toJson()
userDefaults.set(json, forKey: sharedKey)
userDefaults.synchronize()
while (responder != nil) {
if (responder?.responds(to: selectorOpenURL))! {
let _ = responder?.perform(selectorOpenURL, with: url)
}
responder = responder!.next
}
}
enum RedirectType {
case media
case text
case file
}
func getExtension(from url: URL, type: SharedAttachmentType) -> String {
let parts = url.lastPathComponent.components(separatedBy: ".")
var ex: String? = nil
if (parts.count > 1) {
ex = parts.last
}
if (ex == nil) {
switch type {
case .image:
ex = "PNG"
case .video:
ex = "MP4"
case .file:
ex = "TXT"
default:
ex = ""
}
}
return ex ?? "Unknown"
}
func getFileName(from url: URL, type: SharedAttachmentType) -> String {
var name = url.lastPathComponent
if (name.isEmpty) {
name = UUID().uuidString + "." + getExtension(from: url, type: type)
}
return name
}
func copyFile(at srcURL: URL, to dstURL: URL) -> Bool {
do {
if FileManager.default.fileExists(atPath: dstURL.path) {
try FileManager.default.removeItem(at: dstURL)
}
try FileManager.default.copyItem(at: srcURL, to: dstURL)
} catch (let error) {
print("Cannot copy item at \(srcURL) to \(dstURL): \(error)")
return false
}
return true
}
}
Build phases :
Below is my record video:
https://github.com/ShoutSocial/share_handler/assets/67236402/ad4ea1d3-bb16-46cc-af5e-9b7b05ce7e66
Is there something i missing? Thank you @simranKa
@aniketcodebase It has nothing to do with the loadIds, The build phase order is crucial, I was getting circular dependency error, so follow @simranKa on that, strangely when I compared the ShareExtension entitlements file with my project and the example project(which works fine). I realized Xcode added Wifi capability as well. I removed that. and also on the URL Schemes, make sure it is "ShareMedia-$(PRODUCT_BUNDLE_IDENTIFIER)" Initially I made it something custom so that might be the issue as well. Happy hacking
in my case, actual problem was a mismatch (case sensitive) between the group-id (autogenerated after enabling the app-groups ) and bundle id
I had to correct my group-id to group.some.something.myTestApp
@AimanKyo97 I explained step by step how to setup fo Xcode 16 and IOS 18, the example app works right out the box by following instructions here
Closing this as it isn't a problem with the plugin, but a build phase ordering issue related specifically to Xcode build. If you think the readme needs additional info, please make a pull request with that info.
I did follow the documentation but its not working for me in iOS, can help me check what i doing wrong? The app can launch when being shared but no data is parse in the app.
iOS Device iPhone: Version: 17.2.1
Xcode Version: 15.1
In info.plis
`CFBundleURLTypes