CNContactViewController Cancel Button Not Working - ios

I'm trying to use the built-in new contact UI and am getting unexpected behavior with the cancel button. The code below works and calls up the new contact screen but the cancel button will only clear the screen entries not cancel out of the new contact screen. In the built in contacts app hitting cancel returns to the contact list screen. I would like the cancel button to close out the window.
#IBAction func newTwo(sender: AnyObject) {
AppDelegate.getAppDelegate().requestForAccess { (accessGranted) -> Void in
if accessGranted {
let npvc = CNContactViewController(forNewContact: nil)
npvc.delegate = self
self.navigationController?.pushViewController(npvc, animated: true)
}
}
}

did you implement CNContactViewControllerDelegate methods?
Here's a link to documentation
for example:
func contactViewController(viewController: CNContactViewController, didCompleteWithContact contact: CNContact?) {
self.dismissViewControllerAnimated(true, completion: nil)
}

It worked for me using the following code:
Swift 3
func contactViewController(_ vc: CNContactViewController, didCompleteWith con: CNContact?) {
vc.dismiss(animated: true)
}
Also I changed the way I was calling the controller:
Instead of:
self.navigationController?.pushViewController(contactViewController, animated: true)
the solution was:
self.present(UINavigationController(rootViewController: contactViewController), animated:true)
I found the solution using the example code Programming-iOS-Book-Examples written by Matt Neuburg:

Better way to do the dismissing would be to check if the contact is nil and then dismiss it. The dismiss doesn't work if you've pushed the view controller from a navigation controller. You might have to do the following:
func contactViewController(viewController: CNContactViewController, didCompleteWithContact contact: CNContact?) {
if let contactCreated = contact
{
}
else
{
_ = self.navigationController?.popViewController(animated: true)
}
}

Related

Reloading TableView when a UIViewController is being dismissed?

The problem here is that I'm presenting EditCommentVC modally, over the current context of the CommentVC because I want to set the background of the UIView to semi-transparent. Now, on the EditCommentVC I have a UITextView that allows the user to edit their comment, along with 2 buttons - cancel (dismisses the EditCommentVC) and update that updates the new comment and push it to the database.
In term of code, everything is working, except that once the new comment is being pushed and EditCommentVC is being dismissed, the UITableView on CommentsVC with all the comments is not being reloaded to show the updated comments. Tried calling it from viewWillAppear() but it doesn't work.
How can I reload the data in the UITableView in this case?
#IBAction func updateTapped(_ sender: UIButton) {
guard let id = commentId else { return }
Api.Comment.updateComment(forCommentId: id, updatedComment: editTextView.text!, onSuccess: {
DispatchQueue.main.async {
let commentVC = CommentVC()
commentVC.tableView.reloadData()
self.dismiss(animated: true, completion: nil)
}
}, onError: { error in
SVProgressHUD.showError(withStatus: error)
})
}
The code in the CommentVC where it transitions (and passes the id of the comment). CommentVC conforms to a CommentActionProtocol that passes the id of that comment:
extension CommentVC: CommentActionProtocol {
func presentActionSheet(for commentId: String) {
let actionSheet = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
let editAction = UIAlertAction(title: "Edit", style: .default) { _ in
self.performSegue(withIdentifier: "CommentVCToEditComment", sender: commentId)
}
actionSheet.addAction(editAction)
present(actionSheet, animated: true, completion: nil)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "CommentVCToEditComment" {
let editCommentVC = segue.destination as! EditCommentVC
let commentId = sender as! String
editCommentVC.commentId = commentId
}
}
}
I see atleast 2 problems here:
You are creating a new CommentVC which you should not do if you want to update the tableView in the existing view controller.
Since you have mentioned that Api.Comment.updateComment is a an asynchronous call, you need to write the UI code to run on the main thread.
So first you need to have the instance of the commentVC in a variable inside this viewController. You can store the instance of the view controller from where you are presenting this view controller.
class EditCommentVC {
var commentVCdelegate: CommentVC!
// Rest of your code
}
Now you need to pass the reference commentVC in this variable when you are presenting the edit view controller.
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "CommentVCToEditComment" {
let editCommentVC = segue.destination as! EditCommentVC
let commentId = sender as! String
editCommentVC.commentId = commentId
editCommentVC.commentVCdelegate = self
}
}
Now you need to use this reference to reload your tableView.
Api.Comment.updateComment(forCommentId: id, updatedComment: editTextView.text!, onSuccess: {
DispatchQueue.main.async {
commentVCdelegate.tableView.reloadData() // - this commentVC must be an instance that you store of the your commentVC that you created the first time
self.dismiss(animated: true, completion: nil)
}
}, onError: { error in
SVProgressHUD.showError(withStatus: error)
})
Well, i had this problem too, and the solution i found was to use Protocol. I would recommend you to search how to send data back to previous ViewController. That way, when you dismiss the EditCommentVC, you then send back a value(in my case i send true) to the previous ViewController(in your case, CommentVC), and then you'll have a function in CommentVC checking if the value is true and if it is, reload the TableView.
Here, let me show you an example of how i used (those are the names of my ViewControllers, functions and protocols, you can use whatever you want and send whatever data you want back):
In your CommentVC, you'll have something like this:
protocol esconderBlurProtocol {
func isEsconder(value: Bool)
}
class PalestranteVC: UIViewController,esconderBlurProtocol {
func isEsconder(value: Bool) {
if(value){
//here is where you can call your api again if you want and reload the data
tableView.reloadData()
}
}
}
Also, dont forget that you have to set the delegate of EditCommentVC, so do it when you're presenting EditCommentVC, like this:
let viewController = (self.storyboard?.instantiateViewController(withIdentifier: "DetalhePalestranteVC")) as! DetalhePalestranteVC
viewController.modalPresentationStyle = .overFullScreen
viewController.delegate = self
self.present(viewController, animated: true, completion: nil)
//replace **DetalhePalestranteVC** with your **EditCommentVC**
And in your EditCommentVC you'll have something like this:
class DetalhePalestranteVC: UIViewController {
var delegate: esconderBlurProtocol?
override func viewWillDisappear(_ animated: Bool) {
delegate?.isEsconder(value: true)
}
}
That way, everything you dismiss EditCommentVC, you'll send back True and reload the tableView.

Page created by MFMailComposeViewController could not be shown because of hierarchy - Swift 4

I attempt to add a function, that is a mail page would pop up after the user touched a row in a table. Namely, it means that the user could activate a "function" (here the name of that function is "orderOfSendAnEmailToReportTheProblem") when the row is tapped. All of my codes were shown below. (This kind of code has been proposed by several genii on Stackoverflow...)
import Foundation
import UIKit
import MessageUI
class ReportProblem : UIViewController, MFMailComposeViewControllerDelegate {
func orderOfSendAnEmailToReportTheProblem() {
let mailComposeViewController = configureMailController()
if MFMailComposeViewController.canSendMail() {
self.present(mailComposeViewController, animated: true, completion: nil)
} else {
showMailError()
}
}
//Activate the series of the commands of sending the email.
func configureMailController() -> MFMailComposeViewController {
let mailComposeVC = MFMailComposeViewController()
mailComposeVC.mailComposeDelegate = self
mailComposeVC.setToRecipients(["my email"])
mailComposeVC.setSubject("Yo")
return mailComposeVC
}
//Set the recipient and the title of this email automatically.
func showMailError() {
let sendMailErrorAlert = UIAlertController(title: "Could not sned the email.", message: "Oops, something was wrong, please check your internet connection once again.", preferredStyle: .alert)
let dismiss = UIAlertAction(title: "Ok", style: .default, handler: nil)
sendMailErrorAlert.addAction(dismiss)
self.present(sendMailErrorAlert, animated: true, completion: nil) //If you conform the protocol of NSObject instead of UIViewController, you could not finish this line successfully.
}
//Set a alert window so that it would remind the user when the device could not send the email successfully.
func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) {
controller.dismiss(animated: true, completion: nil)
}
//Set this final step so that the device would go to the previous window when you finish sending the email.
}
However, a problem occurred. When I test it on my real device, and after I tapped that particular row, nothing happened, no any new page pop up... The Xcode only showed that "Warning: Attempt to present on whose view is not in the window hierarchy!" I have tried several ways, such as "view.bringSubview(toFront: mailComposeVC)" or adding the codes shown below at the end of my codes, but nothing worked.
func topMostController() -> UIViewController {
var topController: UIViewController = UIApplication.shared.keyWindow!.rootViewController!
while (topController.presentedViewController != nil) {
topController = topController.presentedViewController!
}
return topController
}
I noticed that some other people also would face similar problems when they want to create the alert window, and the solution of that is to create an independent UIWindow, but I want to use mailComposeController to present the email page instead. Some others also faced some problems about MFMailComposeViewController, but their problems are not concerning to hierarchy. I was a novice of swift, and I was haunted by this problem for a whole day... I used swift 4 to develop my App, is anyone know how to solve this problem here?...
So now I'm writing another way to present which I'm using for generic views.
Have Some code in another class for presentation of view so that you can reuse them throughout the app with these two methods.
func slideInFromRight(parentView:UIView,childView:UIView) {
childView.transform = CGAffineTransform(translationX: parentView.frame.maxX, y: 0)
parentView.addSubview(childView)
UIView.animate(withDuration: 0.25, animations: {
childView.transform = CGAffineTransform(translationX: 0, y: 0)
})
}
func slideOutToRight(view:UIView) {
UIView.animate(withDuration: 0.25, animations: {
view.transform = CGAffineTransform(translationX: view.frame.maxX, y: 0)
},completion:{(completed:Bool) in
view.removeFromSuperview()
})
}
Now use these methods to present and remove custom view controller as follows
let window = UIApplication.shared.keyWindow
let vc = YourViewController().instantiate()
self.addChildViewController(vc)
let view = vc.view
view.frame = CGRect(x: 0, y: 20, width: window!.frame.width, height: window!.frame.height-20)
//Here Animation is my custom presenter class and shared is it's shared instance.
Animation.shared.slideInFromRight(parentView: window!, childView: view)
//Or you can use current View controller's view
Animation.shared.slideInFromRight(parentView: self.view!, childView: view)
Genius Vivek Singh, your way looks good, but it's a little bit tedious. Moreover, it still did not work in my project... (It seems that you used some codes about UIView, such as parentView, childView, and view. However, I used MFMailComposeViewController which seems is a little bit different from original view...I am not sure whether this theory is correct or not...)
However, I have found the solution. I presume that the problem is that after the user clicked the row in another tableViewController (here is SettingTVController), it would activate the function "orderOfSendAnEmailToReportTheProblem( )" which is in "another " viewController (here is ReportProblem). Because there are two different viewController, some kind of conflict occurred.
Therefore, I move my whole codes I posted in the above question to my original tableViewController, so that the user would not go into another viewController when they activate the function, and there's no hierarchy problem anymore.
import UIKit
import StoreKit
import MessageUI
class SettingTVController: UITableViewController, MFMailComposeViewControllerDelegate {
var settingTitleConnection = showData()
override func viewDidLoad() {
//skip
}
override func didReceiveMemoryWarning() {
//skip
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
//skip
}
override func numberOfSections(in tableView: UITableView) -> Int {
//skip
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
//skip
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if tableView.indexPathForSelectedRow?.row == 2 && tableView.indexPathForSelectedRow?.section == 1 {
orderOfSendAnEmailToReportTheProblem()
} else {
//skip
}
tableView.deselectRow(at: indexPath, animated: true)
}
//-----<The codes below is used to construct the function of reporting problem with email>-----
func orderOfSendAnEmailToReportTheProblem() {
let mailComposeViewController = configureMailController()
self.present(mailComposeViewController, animated: true, completion: nil)
if MFMailComposeViewController.canSendMail() {
self.present(mailComposeViewController, animated: false, completion: nil)
} else {
showMailError()
}
}
//Activate the series of the commands of sending the email.
func configureMailController() -> MFMailComposeViewController {
let mailComposeVC = MFMailComposeViewController()
mailComposeVC.mailComposeDelegate = self
mailComposeVC.setToRecipients(["datototest#icloud.com"])
mailComposeVC.setSubject("Reporting of Problems of Rolling")
return mailComposeVC
}
//Set the recipient and the title of this email automatically.
func showMailError() {
let sendMailErrorAlert = UIAlertController(title: "Could not send the email.", message: "Oops, something was wrong, please check your internet connection once again.", preferredStyle: .alert)
let dismiss = UIAlertAction(title: "Ok", style: .default, handler: nil)
sendMailErrorAlert.addAction(dismiss)
self.present(sendMailErrorAlert, animated: true, completion: nil) //If you conform the protocol of NSObject instead of UIViewController, you could not finish this line successfully.
}
//Set a alert window so that it would remind the user when the device could not send the email successfully.
func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) {
controller.dismiss(animated: true, completion: nil)
//UIApplication.shared.keyWindow?.rootViewController?.dismiss(animated: true, completion: nil)
}
//Set this final step so that the device would go to the previous window when you finish sending the email.
//-----<The codes above is used to construct the function of reporting problem with email>-----
}
I posted my codes above so that it may help others who face similar problem someday. Once again, thanks for your help!!
I don't know why you are facing a view hierarchy issue. But I am able to achieve the mail share option in swift 4. I followed exactly same steps.
Check if mail can be sent:
MFMailComposeViewController.canSendMail()
Configure mail body:
private func configureMailController() -> MFMailComposeViewController {
let mailComposeViewController = MFMailComposeViewController()
mailComposeViewController.mailComposeDelegate = self
mailComposeViewController.setMessageBody("MESSAGE BODY", isHTML: true)
return mailComposeViewController
}
Present mail VC:
present(mailComposeViewController, animated: true)
confirm optional protocol and dismiss the view explicitly:
func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) {
controller.dismiss(animated: true)
}

MessageComposeViewController not calling delegate

I've been searching for solutions to my problem without any success...
The app I'm developing lets the user play a small quiz game and send the result as a text message. Everything works fine except when the MessageComposeViewController is suppose to dismiss (on send/cancel).
It seems like the MessageComposeViewController doesn't call the delegate since I don't get the print from the delegate function...
I have a separate class called SendMessage which handles the MessageComposeViewController, when the user click a button "Send" in a ViewController I create an instance of this class and present it.
Part of my ViewController with the send button:
#IBAction func Send(_ sender: Any) {
let sendResult = SendMessage()
if sendResult.canSend() {
let meddelande = sendResult.createMessage(result: 8, name: "Steve Jobs")
present(meddelande, animated: true, completion: nil)
} else {
alert.addAction(alertButton)
self.present(alert, animated: true, completion: nil)
}
}
The class which handles the MessageComposeViewController called SendMessage (I left some irrelevant code out)
func createMessage(result: Int, name: String) -> MFMessageComposeViewController {
let meddelande = MFMessageComposeViewController()
meddelande.messageComposeDelegate = self
meddelande.recipients = ["PhoneNumber"]
meddelande.body = name + ": " + String(result)
return meddelande
}
func messageComposeViewController(_ controller: MFMessageComposeViewController, didFinishWith result: MessageComposeResult) {
print ("F*ck")
controller.dismiss(animated: true, completion: nil)
}
Grateful for any help!
I think you should hold a strong reference to it instead of a local variable
let sendResult = SendMessage()
declare it as instance variable
var sendResult:SendMessage?

How do I prevent a navigationController from returning to root when dismissing MFMailComposeViewController

When I dismiss an instance of MFMailComposeViewController or MFMessageComposeViewController that is presented modally from the third viewController in a navigation stack, the navigation stack is reset, and the root VC is reloaded. How can I prevent this behavior and remain on the original presenting viewController (third VC in the stack)? I get the same behavior whether I call dismiss from the presenting VC, the presented VC, or the navigationController.
This has been asked before, but I have not seen a solution.
App Structure looks like this:
TabBarController
Tab 1 - TripsNavController
-> Trips IntroductionVC (root VC) segue to:
-> TripsTableViewController segue to:
-> TripEditorContainerVC
- TripEditorVC (child of ContainerVC)
- HelpVC (child of ContainerVC)
Tab 2...
Tab 3...
Tab 4...
In the TripEditorVC I present the MFMailComposeViewController. The functions below are declared in an extension to UIViewController that adopts the MFMailComposeViewControllerDelegate protocol
func shareWithEmail(message: NSAttributedString) {
guard MFMailComposeViewController.canSendMail() else {
showServiceError(message: "Email Services are not available")
return
}
let composeVC = MFMailComposeViewController()
composeVC.setSubject("My Trip Plan")
composeVC.setMessageBody(getHTMLforAttributedString(attrStr: message), isHTML: true)
composeVC.mailComposeDelegate = self
present(composeVC, animated: true, completion: nil)
}
Then in the delegate method I dismiss the MFMailComposeVC:
public func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) {
switch result {
case .sent:
print("Mail sent")
case .saved:
print("Mail saved")
case .cancelled:
print("Mail cancelled")
case .failed:
print("Send mail failed")
}
if error != nil {
showServiceError(message: "Error: \(error!.localizedDescription)")
}
dismiss(animated: true, completion: nil)
}
I have tried the following to present and dismiss and get the same behavior, i.e.: the TripsNavController clears the nav stack and reloads the TripsIntroductionVC as its root VC:
self.present(composeVC, animated: true, completion: nil)
self.parent?.present(composeVC, animated: true, completion: nil)
self.parent?.navigationController?.present(composeVC, animated: true, completion: nil)
self.navigationController?.present(composeVC, animated: true, completion: nil)
You can also check with presentingViewController?.dismiss method to get the solution.
I have tried with following navigation stack.
I am able to send email successfully from Container VC's Send Email button using your code only.
Can you please check and verify navigation flow?
Please let me know if you still face any issue.
dismiss(animated: true, completion: nil)
to
self.dismiss(animated: true, completion: nil)
Try this
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let secondViewController = storyboard.instantiateViewControllerWithIdentifier("secondViewControllerId") as! SecondViewController
self.presentViewController(secondViewController, animated: true, completion: nil)
or
https://stackoverflow.com/a/37740006/8196100
Just Use Unwind Segue its Pretty simple and perfect in your case
I have used many times..
just look at this Unwind segue's example
. If u still don't able to find answer or not able to understand the Unwind segue then just reply to me.
Hope this will solve your problem.
I found the problem with my navigation stack today. I built a simple
project that duplicated the tabBarController/NavigationControler architecture of my problem project, component by component, until dismissing a MFMailComposeViewController caused my navigation stack to reset as described in my original post.
That immediately pointed to the source of the bug. In my subclassed UINavigationCotroller I was instantiating the root viewController in code so that I could skip an introductory view if the user had set a switch in the apps settings. In order to pick up changes in that switch setting I was calling my instantiation code in viewDidAppear of the navigationController. That worked fine EXCEPT when dismissing the mailComposeVC. The fix was to add a guard statement in viewDidAppear to return if the navControllers viewController collection was not empty, and send and respond to an NSNotification when the switch was changed.
class TopNavigationController: UINavigationController {
var sectionType: SectionType?
var defaults = UserDefaults.standard
var showIntroFlag: Bool = true
override func viewDidLoad() {
super.viewDidLoad()
// Handle initial load of the tab bar controller where we are not sent a sectionType
if sectionType == nil {
sectionType = .groups
}
setShowIntroFlag()
NotificationCenter.default.addObserver(self, selector: #selector(resetControllers), name: NSNotification.Name(rawValue: "kUserDidChangeShowIntros"), object: nil)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(true)
guard self.viewControllers.isEmpty else {
return
}
loadControllers()
}
func setShowIntroFlag() {
showIntroFlag = true
// Check NSUserDefaults to see if we should hide the Intro Views for all sections
if defaults.bool(forKey: "SHOW_SECTION_INTROS") == false {
showIntroFlag = false
}
}
func loadControllers() {
if showIntroFlag == true {
showIntro()
} else {
skipIntro()
}
}
func resetControllers() {
setShowIntroFlag()
loadControllers()
}

Dismiss parseUI login view from a SKScene

I am using ParseUI inside a SpriteKit Swift app.
This is my LoginScene
class LoginScene: SKScene, PFLogInViewControllerDelegate, PFSignUpViewControllerDelegate {
func showLoginView() {
let nativeLogin = NativeLoginViewController()
nativeLogin.fields = PFLogInFields.Default
nativeLogin.delegate = self
self.view?.window?.rootViewController?.presentViewController(nativeLogin, animated: true, completion: nil)
}
func logInViewController(logInController: PFLogInViewController!, didLogInUser user: PFUser!) {
println("login succeeded")
self.view?.window?.rootViewController?.dismissViewControllerAnimated(true, completion: {() in
println("dismissed loginviewcontroller suceeded")
self.view?.presentScene(ModeMenuScene(size: self.view!.bounds.size))
})
}
}
}
It does print login succeeded but not dismissed loginviewcontroller succeeded. How can I dismiss the loginview upon login and go back to my LoginScene ?
This question isn't related to Parse. Instead I'd look into better handling the dismissViewControllerAnimated() method.
Try switching:
self.view?.window?.rootViewController?.dismissViewControllerAnimated...
to
self.dismissViewControllerAnimated...
Found the solution : I need to make use of the first parameter instead of relying on the delegate controller.
func logInViewController(logInController: PFLogInViewController!, didLogInUser user: PFUser!) {
logInController.dismissViewControllerAnimated(true, completion: {() in
self.view?.presentScene(GameScene(size: self.view!.bounds.size)) //Go to Game Scene
return
})
}

Resources