UIActivityViewController вылетает на iPad в iOS 8: краш при показе
Проблема: iPad требует popover с источником
Вы абсолютно правы: на iPad (в отличие от iPhone) UIActivityViewController отображается не модально, а как popover. iOS 8 ввела UIPopoverPresentationController, и если не указать sourceView (или barButtonItem), система выбрасывает исключение. Ваш код почти правильный, но есть важный нюанс: sourceView нужно устанавливать до вызова presentViewController, а также обязательно задать sourceRect — область, откуда появится popover.
Правильное решение для iOS 8+ (Swift)
let textToShare = "Поделиться этим текстом"
let activityVC = UIActivityViewController(activityItems: [textToShare], applicationActivities: nil)
// Для iPad — настраиваем popover
if let popoverController = activityVC.popoverPresentationController {
popoverController.sourceView = self.view // Ваша вьюха, откуда показываем
popoverController.sourceRect = CGRect(x: self.view.bounds.midX, y: self.view.bounds.midY, width: 0, height: 0) // Центр экрана
popoverController.permittedArrowDirections = .any // Разрешаем любую стрелку
}
presentViewController(activityVC, animated: true, completion: nil)
Почему это работает?
- sourceView — указывает, к какой вьюхе привязан popover. Без него iOS не знает, откуда показывать.
- sourceRect — координаты внутри sourceView, откуда вылетает popover. Если не задать, по умолчанию используется CGRect.zero, что может привести к странному поведению или крашу на некоторых устройствах.
- permittedArrowDirections — направление стрелки. На iPad лучше оставить
.any, чтобы система сама выбрала оптимальное.
Особенности iOS 8 и универсальный код
На iOS 8 popoverPresentationController доступен не у всех контроллеров, но у UIActivityViewController он есть. Чтобы код работал и на iPhone (где popover не нужен), проверяйте устройство:
if let popoverController = activityVC.popoverPresentationController,
UIDevice.current.userInterfaceIdiom == .pad {
popoverController.sourceView = self.view
popoverController.sourceRect = CGRect(x: self.view.bounds.midX, y: self.view.bounds.midY, width: 0, height: 0)
}
Так вы избежите лишней настройки на iPhone, где UIActivityViewController всё равно покажется модально.
Что делать, если краш всё равно происходит?
- Убедитесь, что
self.viewне nil и уже добавлена в иерархию (например, вызывайте код вviewDidAppear, а неviewDidLoad). - Если показываете из кнопки, используйте
sourceView = buttonиsourceRect = button.bounds— popover появится прямо от кнопки. - Проверьте, что не вызываете
presentViewControllerдважды или из фонового потока.
Надеюсь, это поможет. Удачи с приложением!
<<Альтернатива: использование barButtonItem для UIBarButtonItem
Если ваш UIActivityViewController вызывается из кнопки на панели навигации или тулбара, лучше использовать barButtonItem вместо sourceView. Это более естественно для iPad — popover привязывается к кнопке, а не к произвольной области экрана.
@IBAction func shareButtonTapped(_ sender: UIBarButtonItem) {
let textToShare = "Поделиться"
let activityVC = UIActivityViewController(activityItems: [textToShare], applicationActivities: nil)
// На iPad привязываем к barButtonItem
if let popoverController = activityVC.popoverPresentationController,
UIDevice.current.userInterfaceIdiom == .pad {
popoverController.barButtonItem = sender
}
present(activityVC, animated: true, completion: nil)
}
Преимущества этого подхода
- Не нужно вычислять
sourceRect— iOS сама определит, откуда показывать popover. - Код чище и короче.
- Работает на всех версиях iOS, начиная с 8.0.
Этот способ особенно удобен, когда кнопка находится в навигационной панели или тулбаре. Попробуйте — он часто решает проблему с крашем на iPad без лишних телодвижений.