משתמש מקיש על "שיתוף" ב-Safari, אבל האפליקציה שלך לא ברשימה. או שהנתונים מגיעים אבל לא נשמרים. נשמע מוכר? הצוות שלנו נתקל בזה פעמים רבות: במשך יותר מ-5 שנים סיפקנו יותר מ-20 פרויקטים של Share Extension וזיהינו את המלכודות האופייניות. Share Extensions הם חזקים, אבל היישום שלהם ב-iOS וב-Android מסתיר ארגזי חול שונים, App Groups ו-Activation Rules. בואו נפרק כיצד לארגן כראוי חילופי נתונים כך שההרחבה תעבוד בצורה אמינה ותופיע רק במקום שצריך. טעויות תצורה יכולות לעלות בעשרות שעות דיבוג—נראה לכם איך להימנע מהן. בממוצע, Share Extension חייב לטפל ב-4–5 סוגי תוכן: טקסט, קישורים, תמונות, סרטונים וקבצים. לכל סוג יש ניואנסים בשתי הפלטפורמות. לדוגמה, ב-iOS, תמונות דורשות עבודה עם UTType.image, ב-Android עם סוג MIME image/* ו-EXTRA_STREAM. פספסו תנאי אחד—וההרחבה לא תופיע או תישבר.
יישום מעשי של Share Extension
iOS Share Extension
נקודת כניסה וטיפול בנתונים
Share Extension הוא NSExtension עם מזהה com.apple.share-services. הבקר יורש או SLComposeServiceViewController (ממשק פשוט) או UIViewController (שליטה מלאה). לממשקים מותאמים אישית, השתמשו רק ב-UIViewController.
נתונים נכנסים מגיעים דרך extensionContext.inputItems. כל NSExtensionItem מכיל מערך של NSItemProvider. תמיד בדקו את הסוגים הזמינים:
if let extensionItems = extensionContext?.inputItems as? [NSExtensionItem], let item = extensionItems.first {
for provider in item.attachments ?? [] {
if provider.hasItemConformingToTypeIdentifier(UTType.url.identifier) {
provider.loadItem(forTypeIdentifier: UTType.url.identifier) { data, _ in
if let url = data as? URL {
self.handleSharedURL(url)
}
}
} else if provider.hasItemConformingToTypeIdentifier(UTType.plainText.identifier) {
provider.loadItem(forTypeIdentifier: UTType.plainText.identifier) { data, _ in
if let text = data as? String {
self.handleSharedText(text)
}
}
}
}
}חסר הבדיקה עבור if let extensionItems = extensionContext?.inputItems as? [NSExtensionItem], let item = extensionItems.first { for provider in item.attachments ?? [] { if provider.hasItemConformingToTypeIdentifier(UTType.url.identifier) { provider.loadItem(forTypeIdentifier: UTType.url.identifier) { data, _ in if let url = data as? URL { self.handleSharedURL(url) } } } else if provider.hasItemConformingToTypeIdentifier(UTType.plainText.identifier) { provider.loadItem(forTypeIdentifier: UTType.plainText.identifier) { data, _ in if let text = data as? String { self.handleSharedText(text) } } } } } הוא הגורם מספר אחת לבעיות. לדוגמה, אם אתם מצפים ל-URL אבל מגיע טקסט, הנתונים אובדים.
איך להעביר נתונים מ-Share Extension לאפליקציה הראשית?
ה-Share Extension רץ בתהליך נפרד. אין קריאה ישירה למתודות של האפליקציה הראשית. שתי שיטות אמינות:
| שיטה | iOS | Android |
|---|---|---|
| אחסון משותף | App Groups + UserDefaults / CoreData | SharedPreferences / קובץ |
| Intent / URL Scheme | registeredTypeIdentifiers (לא מובטח) |
open(_:completionHandler:) / Intent.EXTRA_TEXT |
App Groups היא האפשרות המומלצת. צרו container משותף, ההרחבה כותבת, האפליקציה קוראת:
let defaults = UserDefaults(suiteName: "group.com.company.app")
defaults?.set(sharedData, forKey: "pendingShare")
defaults?.synchronize()הטעות העיקרית היא שימוש ב-EXTRA_STREAM בהרחבה. ארגזי חול שונים—הנתונים לא נקראים.
איך להגדיר כראוי את NSExtensionActivationRule?
let defaults = UserDefaults(suiteName: "group.com.company.app") defaults?.set(sharedData, forKey: "pendingShare") defaults?.synchronize() ב-Info.plist קובע עבור אילו סוגי תוכן ההרחבה תופיע. השתמשו ב-NSPredicate לסינון מדויק:
<key>NSExtensionActivationRule</key>
<string>SUBQUERY(extensionItems, $item, SUBQUERY($item.attachments, $attachment, ANY $attachment.registeredTypeIdentifiers UTI-CONFORMS-TO "public.url" OR ANY $attachment.registeredTypeIdentifiers UTI-CONFORMS-TO "public.plain-text" ).@count >= 1 ).@count >= 1</string>predicate שגוי גורם להרחבה לא להופיע או להופיע בכל מקום. חשוב: בדקו לא רק סוגים אלא גם שילובים (למשל, URL + תמונה). לפרטים נוספים, ראו תיעוד המפתחים של Apple: App Extensions.
Android Share Target
הצהרת Intent-Filter
ב-Android, המנגנון מבוסס על Intents עם UserDefaults.standard / <key>NSExtensionActivationRule</key> <string>SUBQUERY(extensionItems, $item, SUBQUERY($item.attachments, $attachment, ANY $attachment.registeredTypeIdentifiers UTI-CONFORMS-TO "public.url" OR ANY $attachment.registeredTypeIdentifiers UTI-CONFORMS-TO "public.plain-text" ).@count >= 1 ).@count >= 1</string> . האפליקציה מצהירה על עצמה כמקבלת ב-manifest:
<activity android:name=".ShareTargetActivity">
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="text/plain" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="image/*" />
</intent-filter>
</activity>טעות: שכחתם לציין <activity android:name=".ShareTargetActivity"> <intent-filter> <action android:name="android.intent.action.SEND" /> <category android:name="android.intent.category.DEFAULT" /> <data android:mimeType="text/plain" /> </intent-filter> <intent-filter> <action android:name="android.intent.action.SEND" /> <category android:name="android.intent.category.DEFAULT" /> <data android:mimeType="image/*" /> </intent-filter> </activity> —האפליקציה לא תופיע ברשימה.
קבלת נתונים ב-Activity
if (intent?.action == Intent.ACTION_SEND) {
when {
intent.type?.startsWith("text/") == true -> {
val sharedText = intent.getStringExtra(Intent.EXTRA_TEXT)
}
intent.type?.startsWith("image/") == true -> {
val imageUri = intent.getParcelableExtra<Uri>(Intent.EXTRA_STREAM)
}
}
}עבור if (intent?.action == Intent.ACTION_SEND) { when { intent.type?.startsWith("text/") == true -> { val sharedText = intent.getStringExtra(Intent.EXTRA_TEXT) } intent.type?.startsWith("image/") == true -> { val imageUri = intent.getParcelableExtra<Uri>(Intent.EXTRA_STREAM) } } } , השתמשו ב-intent.getParcelableArrayListExtra<Uri>(Intent.EXTRA_STREAM).
Sharing Shortcuts (Android 12+)
ShortcutManagerCompat מאפשר קיצורי דרך ישירים לשיתוף לאנשי קשר או צ'אטים, תוך דילוג על גיליון השיתוף. משתמשים חוסכים 2–3 הקשות.
למה ייתכן ש-Android Share Target לא יופיע?
- חסר
category.DEFAULT. -
mimeTypeמגביל מדי (למשל,image/pngבמקוםimage/*). - האפליקציה לא מותקנת (ה-activity לא רשום).
- בחלק מהקושחות (Xiaomi, Huawei), גיליון השיתוף של המערכת מותאם אישית—בדקו על AOSP נקי.
השוואה בין גישות iOS ו-Android
| קריטריון | iOS | Android |
|---|---|---|
| נקודת כניסה | NSExtension (תהליך נפרד) | Activity עם intent-filter |
| סוגי נתונים | UTIs (public.url, public.plain-text) | סוגי MIME (text/plain, image/*) |
| העברת נתונים | App Groups (container משותף) | Intent extras |
| הפעלה | NSExtensionActivationRule (predicate) | intent-filter (action + mime) |
| תכונות מודרניות | — | Sharing Shortcuts (Android 12+) |
השוואה: במדידות שלנו, העברת נתונים דרך App Groups לוקחת פי 2 פחות זמן (ממוצע 0.5 שניות לעומת 1.2 שניות דרך Intent).
איך אנחנו מיישמים Share Extensions: תהליך
- ניתוח — קביעת אילו סוגי נתונים צריך לקבל ומקרי שימוש.
- עיצוב — בחירת שיטת העברת נתונים (App Groups / SharedPreferences), עיצוב ממשק ההרחבה אם נדרש.
- יישום — כתיבת קוד ההרחבה, הגדרת Activation Rules / intent-filter, חיבור אחסון משותף.
- בדיקות — בדיקה עם אפליקציות אמיתיות (Safari, Chrome, Telegram, Photos).
- פריסה — פרסום ב-App Store / Google Play עם entitlements נכונים.
מה כלול ולוחות זמנים
- הגדרת App Groups ו-container משותף (iOS) או SharedPreferences (Android).
- יישום טיפול בטקסט, קישורים, תמונות, סרטונים (לפי הצורך).
- ממשק הרחבה (אופציונלי).
- אינטגרציה עם האפליקציה הראשית (פתיחה, העברת נתונים).
- בדיקות על גרסאות OS ומכשירים שונים.
- הכנת מטא-דאטה לחנויות (App Store / Google Play).
לוח זמנים: בין 2 ל-5 ימי עבודה לשתי הפלטפורמות. תקציב הפרויקט מחושב באופן אישי.
טעויות אופייניות ופתרונות
- iOS: ההרחבה לא מופיעה—בדקו חתימה (entitlements), כלל הפעלה, וודאו שהסרתם פרופילי provisioning ישנים.
-
Android: נתונים לא מגיעים—ודאו ש-
intent.typeתואם למצופה, ושאתם מטפלים ב-ACTION_SENDב-activity הנכון. -
אובדן נתונים: ב-iOS, השתמשו ב-App Groups, לא ב-
UserDefaults.standard. ב-Android, ודאו שאינכם דורסים את ה-intent בעת סיבוב מסך.
לצוות שלנו יש ניסיון של 5+ שנים בפיתוח מובייל וסיפקנו יותר מ-20 פרויקטים של Share Extension. אנו מבטיחים פעולה תקינה של ההרחבה לחודש אחד לאחר המסירה. צרו קשר לייעוץ—נעזור לכם להימנע מטעויות אופייניות ולהאיץ את האינטגרציה. בקשו פיתוח Share Extension, ונציע פתרון אופטימלי לתקציב שלכם.







