After spending all day (>12 hours) trying to isolate a bug in 13 lines of mind-bogglingly generic code, I have come to the dubious conclusion that there must be a bug in the current iteration of CNContactPickerViewController, in iOS 9.2.
Simply copy+paste this ViewController and link the invite action to a button.
The bug is that MFMessageComposeViewController dismisses itself immediately.
If anybody knows what to do with this, do share?
import UIKit
import MessageUI
import ContactsUI
class ViewController: UIViewController, MFMessageComposeViewControllerDelegate, CNContactPickerDelegate {
let contactPickerVC = CNContactPickerViewController()
let messageVC = MFMessageComposeViewController()
override func viewDidLoad() {
super.viewDidLoad()
contactPickerVC.delegate = self
}
func contactPicker(picker: CNContactPickerViewController, didSelectContact contact: CNContact) {
if let phoneNumberValue = contact.phoneNumbers.first?.value as? CNPhoneNumber {
if let phoneNumber = phoneNumberValue.valueForKey("digits") as? String {
// Configure message ViewController
messageVC.messageComposeDelegate = self
messageVC.recipients = [phoneNumber]
messageVC.body = "Yoyoyo"
picker.presentViewController(messageVC, animated: true, completion: nil)
}
}
}
func messageComposeViewController(controller: MFMessageComposeViewController, didFinishWithResult result: MessageComposeResult) {
controller.dismissViewControllerAnimated(true, completion: nil)
}
#IBAction func invite(sender: AnyObject) {
presentViewController(contactPickerVC, animated: true, completion: nil)
}
}
I got it working by dismissing the pickerVC and changing the controller which presents the messageVC!
Insert (before the messageVC config lines):
picker.dismissViewControllerAnimated(true, completion: nil)
Replace
picker.presentViewController(messageVC, animated: true, completion: nil)
with
presentViewController(messageVC, animated: true, completion: nil)
Related
I have small problem. I'm using MFMailComposeViewController to send email after work. So I have var allRuns, and I've printed that var in few places, and it always show value = 3. But in my MailViewController into MessageBody it's equal 0/nil (I've set 0). If it is important- I'm taking var from Firebase, but earlier in the app. Where should I seek problem? That's some code below.
MailViewController:
import UIKit
import MessageUI
class MailViewController: UIViewController, MFMailComposeViewControllerDelegate {
let av = ActualValues()
override func viewDidLoad() {
super.viewDidLoad()
}
//MARK: - Buttons
#IBAction func emailButtonTapped(_ sender: UIButton) {
showMailComposer()
}
#IBAction func backButtonTapped(_ sender: UIButton) {
self.dismiss(animated: true, completion: nil)
}
//MARK: -Func
func showMailComposer() {
if MFMailComposeViewController.canSendMail() {
let mailComposer = MFMailComposeViewController()
mailComposer.mailComposeDelegate = self
mailComposer.setToRecipients(["paereson#gmail.com"])
mailComposer.setSubject("Work of \(av.currentDate())")
mailComposer.setMessageBody("All runs: \(av.allRuns)", isHTML: false)
present(mailComposer, animated: true, completion: nil)
}
}
func mailComposeController(_ controller: MFMailComposeViewController,
didFinishWith result: MFMailComposeResult, error: Error?) {
if let _ = error {
//show alert
controller.dismiss(animated: true, completion: nil)
return
}
switch result {
case .cancelled:
print("Cancelled")
case .failed:
print("Failed")
case .saved:
print("saved")
case .sent:
print("Email sent")
#unknown default:
print("default")
}
controller.dismiss(animated: true, completion: nil)
}
}
Few lines in av = ActualValuse:
var allRuns: Int = 0
var runs: Array? = []
//All runs
func allRunsFunc() {
let ref1 = ref.child("\(currentYear())/\(currentMonth())/\(currentDay())")
ref1.observeSingleEvent(of: .value) { (snapshot) in
if let snapshots = snapshot.children.allObjects as? [DataSnapshot] {
self.allRuns = snapshots.count as Int
}
}
}
EDIT:
Should I use segue before MFMAilComposeViewController? If yes, how should I configure it?
import UIKit
import MessageUI
class MailViewController: UIViewController, MFMailComposeViewControllerDelegate {
let av = ActualValues()
override func viewDidLoad() {
super.viewDidLoad()
}
//MARK: - Buttons
#IBAction func emailButtonTapped(_ sender: UIButton) {
showMailComposer()
}
#IBAction func backButtonTapped(_ sender: UIButton) {
self.dismiss(animated: true, completion: nil)
}
//MARK: -Func
func showMailComposer() {
if MFMailComposeViewController.canSendMail() {
let mailComposer = MFMailComposeViewController()
mailComposer.mailComposeDelegate = self
mailComposer.setToRecipients(["paereson#gmail.com"])
mailComposer.setSubject("Work of \(av.currentDate())")
mailComposer.setMessageBody("\(av.exportText())", isHTML: true)
present(mailComposer, animated: true, completion: nil)
}
}
func mailComposeController(_ controller: MFMailComposeViewController,
didFinishWith result: MFMailComposeResult, error: Error?) {
if let _ = error {
//show alert
controller.dismiss(animated: true, completion: nil)
return
}
switch result {
case .cancelled:
print("Cancelled")
case .failed:
print("Failed")
case .saved:
print("saved")
case .sent:
print("Email sent")
#unknown default:
print("default")
}
controller.dismiss(animated: true, completion: nil)
}
func configureMail(model: ActualValues) {
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.destination is MFMailComposeViewController {
let vc = segue.destination as? MFMailComposeViewController
vc?.configureMail(model: av)
}
}
}
You seem to be missing the execution of allRunsFunc function. Maybe call it in the ActualValue init method.
UIActivityViewController dismisses the presenting view controller after sharing files.
this is happening in iOS 13+ only. Is there any permanent solution for this?
Others apps seem to have this issue too after updating to iOS 13.
class VC : UIViewController {
#IBAction func moveFiles(_ sender: UIButton) {
let alertController = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
alertController.addAction(UIAlertAction(title: "Move", style: .default, handler: { action in
let activityController = UIActivityViewController(activityItems: urls, applicationActivities: nil)
if (UIDevice.current.userInterfaceIdiom == UIUserInterfaceIdiom.pad) {
activityController.popoverPresentationController?.sourceRect = sender.frame
activityController.popoverPresentationController?.sourceView = sender.superview
}
self.present(activityController, animated: true, completion: nil)
}))
}
}
Here is the work around for your issue.
let tempController = TransparentViewController()
tempController.modalPresentationStyle = .overFullScreen
activityViewController.completionWithItemsHandler = { [weak tempController] _, _, _, _ in
if let presentingViewController = tempController?.presentingViewController {
presentingViewController.dismiss(animated: false, completion: nil)
} else {
tempController?.dismiss(animated: false, completion: nil)
}
}
present(tempController, animated: true) { [weak tempController] in
tempController?.present(activityViewController, animated: true, completion: nil)
}
Found similar question with solution which helps me. For iOS 13 show UIActivityViewController in another UIWindow
Stackoverflow answer
Seems it's fixed in iOS 14.4
For older iOS versions I found easier workaround. Override dismiss(animated:completion:) with empty implementation so it won't be dismissing itself automatically.
However, you can still dismiss this VC using super.dismiss(animated:completion).
E.g
override func dismiss(animated flag: Bool, completion: (() -> Void)? = nil) {
// do nothing to workaround bug - automatically dimisses this VC after saveToCameraRoll activity was performed
// call super.dismiss(animated:completion:) in order to really dismiss this VC
// seems fixed in iOS 14.4
}
...
#objc private func didTapCloseButton(_ sender: UIButton) {
super.dismiss(animated: true) // calling parent class implementation
}
I have same issue right now on iOS target 14.1.
I made my solution based on answers I found.
final class ShareViewController: UIViewController {
private let activityItems: [Any]
private let applicationActivities: [UIActivity]?
// Same looking initializer as UIActivityViewController has
init(activityItems: [Any], applicationActivities: [UIActivity]? = nil) {
self.activityItems = activityItems
self.applicationActivities = applicationActivities
super.init(nibName: nil, bundle: nil)
// Make transparent and covering entire screen
view.backgroundColor = .clear
modalPresentationStyle = .overCurrentContext
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// Present UIActivityViewController here
presentShareSheet()
}
fileprivate func presentShareSheet() {
let shareSheet = UIActivityViewController(activityItems: activityItems,
applicationActivities: applicationActivities)
shareSheet.completionWithItemsHandler = { [weak self] _, _, _, _ in
// This is necessary to dismiss parent VC
self?.dismiss(animated: false)
}
self.present(shareSheet, animated: true)
}
}
And use it like UIActivityViewController but presenting it without animations
#objc private func shareImage() {
guard let image = imageView.image else { return }
let shareVC = ShareViewController(activityItems: [image])
present(shareVC, animated: false)
}
Im my GMSAUtocompleteController I have two textfield but they display same location when clicked
extension RideAddDetailsViewController: GMSAutocompleteViewControllerDelegate {
func viewController(_ viewController: GMSAutocompleteViewController, didAutocompleteWith place: GMSPlace) {
locationTextField.text = place.name
destinationTextField.text = place.name
dismiss(animated: true, completion: nil)
}
func viewController(_ viewController: GMSAutocompleteViewController, didFailAutocompleteWithError error: Error) {
// Handle the error
print("Error: ", error.localizedDescription)
}
func wasCancelled(_ viewController: GMSAutocompleteViewController) {
// Dismiss when the user canceled the action
dismiss(animated: true, completion: nil)
}
You can use tag to separate them.
if textField.isEqual(locationTextField)
{
let autocompleteController = GMSAutocompleteViewController()
autocompleteController.view.tag = 1 // assign the tag you want
autocompleteController.delegate = self
present(autocompleteController, animated: true, completion: nil)
}
else if textField.isEqual(destinationTextField)
{
let autocompleteController = GMSAutocompleteViewController()
autocompleteController.view.tag = 2 // assign the tag you want
autocompleteController.delegate = self
present(autocompleteController, animated: true, completion: nil)
}
Now you can separate the value from delegate method like this and assign to the textfield.
func viewController(_ viewController: GMSAutocompleteViewController, didAutocompleteWith place: GMSPlace) {
if viewController.view.tag == 1
{
locationTextField.text = place.name
}
else viewController.view.tag == 2
{
destinationTextField.text = place.name
}
dismiss(animated: true, completion: nil)
}
I set the reference of clicked textField to selectedTextField in both my IBActions.
var selectedTextField = UITextField()
#IBAction func locationTextFieldTapped(_ sender: Any) {
selectedTextField = locationTextField
//locationTextField.resignFirstResponder()
let autoCompleteController = GMSAutocompleteViewController()
locationTextField.tag = 0
autoCompleteController.delegate = self
present(autoCompleteController, animated: true, completion: nil)
}
#IBAction func destinationTextField(_ sender: Any) {
selectedTextField = destinationTextField
//destinationTextField.resignFirstResponder()
let autoCompleteController = GMSAutocompleteViewController()
destinationTextField.tag = 1
autoCompleteController.delegate = self
present(autoCompleteController, animated: true, completion: nil)
}
Then in the GMSAutocompleteViewControllerDelegate
I did this:
self.selectedTextField.text = place.name
I'm implementing a photopicker in my project and it works or not, with the same code, depending the way I implement it. In my first approach I was using a custom UIAlertAction class and doing all the stuff in there, to have my main controller lighter, but the picker delegate was never called, instead it prints an error message in the console ([discovery] errors encountered while discovering extensions: Error Domain=PlugInKit Code=13 "query cancelled" UserInfo={NSLocalizedDescription=query cancelled}) and I would like to discuss if the way I were implementing the picker was right or wrong and why? Or if it's a bug from apple. I've been googling for a while and checking all the related questions in stack overflow, and anything has worked for me except to put the picker code in my main controller.
Here is the code of my main controller, when it was not calling the delegate:
First approach that give me error and don't call delegates
import UIKit
import MobileCoreServices
import Photos
class ViewController: UIViewController {
#IBOutlet weak var lblMain: UILabel!
#IBOutlet weak var buttonsBottom: NSLayoutConstraint!
#IBOutlet weak var imgFromUser: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
hideButtons()
}
override func viewDidAppear(_ animated: Bool) {
showButtons()
checkPermission()
}
func checkPermission() {
let photoAuthorizationStatus = PHPhotoLibrary.authorizationStatus()
switch photoAuthorizationStatus {
case .authorized: print("Access is granted by user")
case .notDetermined: PHPhotoLibrary.requestAuthorization({
(newStatus) in
print("status is \(newStatus)")
if newStatus == PHAuthorizationStatus.authorized { print("success") }
})
case .restricted: print("User do not have access to photo album.")
case .denied: print("User has denied the permission.")
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func hideButtons()
{
buttonsBottom.constant += self.view.frame.size.height * 0.15
}
func showButtons(){
UIView.animate(withDuration: 0.5) {
}
UIView.animate(withDuration: 0.5, animations: {
self.buttonsBottom.constant = 0
self.view.layoutIfNeeded()
}) { (completed) in
self.lblMain.text = firstController.lblMain
}
}
#IBAction func addImagePressed(_ sender: Any) {
let alertViewController : AlertAction = AlertAction.init(controller: self, type: .photoGallery)
self.present((alertViewController.setType(alert: .photoGallery)), animated: true, completion: nil)
}
}
And this is my custom AlertAction class, it implements the picker & navigation delegate, creates the UIImagePickerController, sets the delegate to it, creates the alert action and set it to present the main view controller, adds it to a UIAlertController and gets returned to the main view controller which present it:
import UIKit
import Photos
class AlertAction: UIAlertAction {
var destinationController : ViewController?
var imagePicker = UIImagePickerController()
convenience init(controller : ViewController, type : type) {
self.init()
destinationController = controller
//Init picker
imagePicker.delegate = self
imagePicker.sourceType = type == .photoGallery ? UIImagePickerControllerSourceType.photoLibrary : UIImagePickerControllerSourceType.camera
imagePicker.allowsEditing = false
}
enum type {
case camera
case photoGallery
}
var alertType : type = .camera
func setType(alert : type) -> UIAlertController {
alertType = alert
return alertType == .camera ? newAlert() : newAlert()
}
func newAlert() -> UIAlertController{
//set alert text
let alertText = alertType == .camera ? AlertText.typeCamera : AlertText.typeGalery
let myAlert = UIAlertController(title: AlertText.title, message: "", preferredStyle: .actionSheet)
if alertType == .camera {
myAlert.addAction(getCameraAction(alertText: alertText))
return myAlert
}
else{
myAlert.addAction(getGalleryAction(alertText: alertText))
return myAlert
}
}
func getCameraAction(alertText : String) -> UIAlertAction{
let cameraAction = UIAlertAction(title : alertText, style : .default) { (action) in
if UIImagePickerController.isSourceTypeAvailable(.camera) {
self.destinationController!.present(self.imagePicker, animated: true, completion: nil)
}
}
return cameraAction
}
func getGalleryAction(alertText : String) -> UIAlertAction{
let photoLibraryAction = UIAlertAction(title: alertText, style: .default) { (action) in
if UIImagePickerController.isSourceTypeAvailable(.photoLibrary) {
self.destinationController!.present(self.imagePicker, animated: true, completion: nil)
}
}
return photoLibraryAction
}
}
extension AlertAction : UIImagePickerControllerDelegate {
#objc func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {
let mediaType = info[UIImagePickerControllerMediaType] as! NSString
if mediaType.isEqual(to: kCIAttributeTypeImage as String){
destinationController!.imgFromUser.image = info[UIImagePickerControllerOriginalImage] as? UIImage
}
destinationController!.dismiss(animated: true, completion: nil)
}
#objc func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
destinationController!.dismiss(animated: true, completion: nil)
}
}
extension AlertAction : UINavigationControllerDelegate {
}
After reading a while, I've been trying all the posible solutions in this custom class but nothing worked.
Then I tried to implement a picker creation method in my main view controller and it worked.
So my question is very simple, why the delegate methods get called only if I do all the coding stuff in the main view controller but don't work in a custom class?
Here is the code that I'm currently using and works:
Second approach that works, all the code in the same ViewController
import UIKit
import MobileCoreServices
import Photos
class ViewController: UIViewController {
#IBOutlet weak var lblMain: UILabel!
#IBOutlet weak var buttonsBottom: NSLayoutConstraint!
#IBOutlet weak var imgFromUser: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
hideButtons()
}
override func viewDidAppear(_ animated: Bool) {
showButtons()
checkPermission()
}
func checkPermission() {
let photoAuthorizationStatus = PHPhotoLibrary.authorizationStatus()
switch photoAuthorizationStatus {
case .authorized: print("Access is granted by user")
case .notDetermined: PHPhotoLibrary.requestAuthorization({
(newStatus) in
print("status is \(newStatus)")
if newStatus == PHAuthorizationStatus.authorized { print("success") }
})
case .restricted: print("User do not have access to photo album.")
case .denied: print("User has denied the permission.")
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func hideButtons()
{
buttonsBottom.constant += self.view.frame.size.height * 0.15
}
func showButtons(){
UIView.animate(withDuration: 0.5) {
}
UIView.animate(withDuration: 0.5, animations: {
self.buttonsBottom.constant = 0
self.view.layoutIfNeeded()
}) { (completed) in
self.lblMain.text = firstController.lblMain
}
}
#IBAction func addImagePressed(_ sender: Any) {
self.present(addPicker(), animated: true, completion: nil)
}
//MARK: Picker methods
func addPicker()->UIAlertController{
let alertText = AlertText.typeGalery
let myAlert = UIAlertController(title: AlertText.title, message: "", preferredStyle: .actionSheet)
let cameraAction = UIAlertAction(title : alertText, style : .default) { (action) in
if UIImagePickerController.isSourceTypeAvailable(.photoLibrary) {
let imagePicker = UIImagePickerController()
imagePicker.delegate = self
imagePicker.sourceType = .photoLibrary
imagePicker.allowsEditing = true
self.present(imagePicker, animated: true, completion: nil)
}
}
myAlert.addAction(cameraAction)
return myAlert
}
}
extension ViewController : UIImagePickerControllerDelegate {
#objc func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {
let mediaType = info[UIImagePickerControllerMediaType] as! NSString
if let image = info[UIImagePickerControllerOriginalImage] as? UIImage{
self.imgFromUser.image = image
}
if mediaType.isEqual(to: kCIAttributeTypeImage as String){
self.imgFromUser.image = info[UIImagePickerControllerOriginalImage] as? UIImage
}
self.dismiss(animated: true, completion: nil)
}
#objc func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
self.dismiss(animated: true, completion: nil)
}
}
extension ViewController : UINavigationControllerDelegate {
}
Update: the error [discovery] errors encountered while discovering extensions: Error Domain=PlugInKit Code=13 "query cancelled" UserInfo={NSLocalizedDescription=query cancelled} still appears in my second implementation, but only when I remove #objc before the picker delegate method and environement varibable OS_ACTIVITY_MODE = disable is not set, but the code still works anda the delegates are called correctly (in the second implementation) so basically this error is not related with the code functionality and don't describe anything useful
I have presented MFMessageViewController with following code.
extension TransactionResultViewController {
func showMessageComposeController() {
if MFMessageComposeViewController.canSendText() {
let messageComposeVc = MFMessageComposeViewController()
messageComposeVc.body = viewModel.offlinePayload
messageComposeVc.recipients = ["37245"]
messageComposeVc.messageComposeDelegate = self
self.present(messageComposeVc, animated: true, completion: nil)
}
}
}
and dismissed with following code.
func messageComposeViewController(_ controller: MFMessageComposeViewController, didFinishWith result: MessageComposeResult) {
controller.dismiss(animated: true, completion: nil)
}
Sometimes the MFMessageviewcontroller is not presented and sometimes it shows blank white/black screen. Please help. Thanks