CNContactViewControllerDelegate not called when contact property selected/edited on iOS - ios

The delegate for CNContactviewController is not called when properties get edited or selected.
When editing a new contact, the contactViewController(_ viewController: CNContactViewController, shouldPerformDefaultActionFor property: CNContactProperty) function is supposed to be called, but it's not.
How do you get notified when the user edits/selects a contact property?
Steps to reproduce:
Copy the view controller below.
Edit/select a contact
property.
Expected behavior:
"yo" is printed every time you edit/select a property.
Actual behavior:
Nothing.
import Foundation
import Contacts
import ContactsUI
class ContactViewController: UIViewController, CNContactViewControllerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
createContact()
}
func createContact() {
let contactController = CNContactViewController(forNewContact: nil)
contactController.delegate = self
contactController.allowsEditing = true
contactController.allowsActions = true
contactController.displayedPropertyKeys = [CNContactPostalAddressesKey, CNContactPhoneNumbersKey, CNContactGivenNameKey]
contactController.view.layoutIfNeeded()
present(contactController, animated:true)
}
// =============================================================================================================
// MARK: CNContactViewControllerDelegate Functions
// =============================================================================================================
func contactViewController(_ viewController: CNContactViewController, didCompleteWith contact: CNContact?) {
viewController.dismiss(animated: true, completion: nil)
print("hi")
}
func contactViewController(_ viewController: CNContactViewController, shouldPerformDefaultActionFor property: CNContactProperty) -> Bool {
print("yo")
return true
}
// =============================================================================================================
// MARK: UIViewController Functions
// =============================================================================================================
override var prefersStatusBarHidden: Bool {
return true
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}

There are three initializers for making a CNContactViewController:
Existing contact: init(for:)
New contact: init(forNewContact:)
Unknown contact: init(forUnknownContact:)
The first and third forms call the delegate method contactViewController(_:shouldPerformDefaultActionFor:). The second form does not. That's the one you are using.
With the second flavor, the only event you get is contactViewController(_:didCompleteWith:), and at that point the new contact has already been saved into the database.
When editing a new contact, the contactViewController(_ viewController: CNContactViewController, shouldPerformDefaultActionFor property: CNContactProperty) function is supposed to be called
No, it isn't. That's just an idea you made up.
Expected behavior: "yo" is printed every time you edit/select a property.
Then stop expecting that.
How do you get notified when the user edits/selects a contact property?
You don't.
When you use a framework like Cocoa, you don't get to make up any expectations you like. Your expectations need to be based on what the framework actually does. You might wish that CNContactViewController and its delegate messages worked as you describe, and that might make a very good enhancement request to Apple. But it is not how it works in fact, so expecting it to do so won't do you any good.

Related

How to notify a change from a class to multiple ViewControllers?

I'm writing because I'd like to know what's the best method to notify a change from a class to multiple ViewControllers. At the moment I'm using delegate method but I'm sure It's not the best one for this purpose. I created a class where I receive data and after a bit of processing I need to send the processed message to some ViewControllers (any one shows a piece of that message.). At the moment I have a singleton for the class and I assign its delegate to a different ViewController when I load it through a menu. What's your suggestion to do this job?
Here is an example of my actual code:
import Foundation
protocol MyClassDelegate {
func receivedData(_ sender: MyClass)
}
class MyClass: NSObject {
// create the var for delegate
var delegate: MyClassDelegate?
// save the single instance
static private var instance: MyClass {
return sharedInstance
}
private let sharedInstance = MyClass()
static func getInstance() -> MyClass {
return instance
}
func processData() {
// at the end of the process
delegate?.receivedData(self)
}
}
class Menu: UIViewController {
private var containerView: UIView!
private let myClass = Myclass.getInstance()
private var vcOne = VcOne()
private var vcTwo = VcTwo()
override func viewDidLoad() {
super.viewDidLoad()
containerView = UIVIew()
// set containerView position and dimensions
}
func selectViewController(previous: UIViewController, next: UIViewController) {
// remove actual loaded ViewController
previous.willMove(toParent: nil)
previous.view.removeFromSuperView()
previous.removeFromParent()
// assign the delegate
myClass.delegate = next
// add the new ViewController
self.addChild(next)
slef.addSubView(next.view)
next.didMove(toParent: self)
}
}
class VcOne: UIViewController, MyClassDelegate {
func receivedData(_ sender: MyClass) {
// data received
}
}
class VcTwo: UIViewController, MyClassDelegate {
func receivedData(_ sender: MyClass) {
// data received
}
}
You can use NotificationCenter https://developer.apple.com/documentation/foundation/notificationcenter to broadcast a message. or use KVO. Generally I consider notification center much easier.
simple example:
https://www.hackingwithswift.com/example-code/system/how-to-post-messages-using-notificationcenter

Function Overriding in iOS

I have created BaseClassviewController and all my controllers are derived from this controller. I am doing the following steps:
Set custom delegate in BaseClassViewController.
Implement all function of protocol in BaseClassViewController.
Then I am pushing HomeController derived from BaseClassViewController.
Again I am pushing DetailController also derived from BaseClassViewController.
Now when delegate function is called I should get control in DetailController but I am getting control in HomeController.
So my question is why its not calling top controller at navigation i.e DetailController and is it possible to call delegate functions in both controllers?
P.S I am overriding delegate functions in all child controllers.
EDIT: After reading answers and comments I think I have not been clear that much so adding following code snippet.
In Helper Class:
#objc protocol SampleDelegate: class
{
#objc optional func shouldCallDelegateMethod()
}
class SampleHelper: NSObject
{
var sampleDelegate:SampleDelegate!
static var sharedInstance = SampleHelper()
//It is triggered
func triggerDelegateMethod()
{
sampleDelegate!.shouldCallDelegateMethod()
}
func apiCall()
{
let urlString = URL(string: "https://google.com")
if let url = urlString {
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
if error != nil {
print(error)
} else {
if let usableData = data {
self. triggerDelegateMethod()
}
}
}
task.resume()
}
}
}
In BaseClass
class BaseClassViewController: UIViewController,SampleDelegate{
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewWillAppear(_ animated: Bool)
{
super.viewWillAppear(true)
SampleHelper.sharedInstance.delegate = self;
}
func shouldCallDelegateMethod()
{
//Override
}
}
In HomeController i.e 1st controller to be pushed
class HomeViewController: BaseClassViewController{
override func shouldCallDelegateMethod()
{
//
}
}
In DetailController i.e 2nd controller is pushed after HomeController from HomeController.
class DetailViewController: BaseClassViewController{
override func viewDidLoad()
{
super.viewDidLoad()
SampleHelper.sharedInstance.apiCall()
}
override func shouldCallDelegateMethod()
{
//
}
}
Now my question is when delegate is triggered from helper class it calls shouldCallDelegateMethod in HomeViewController but not in DetailViewController. But DetailViewController is at top of navigation array.
Also is there any possibility I can trigger same function in both controller at a time with delegate only?
In BaseClassviewController you should have a delegate variable/property.
In HomeController and DetailController you need to set that delegate variable/property to self if you want that class to be listening to the delegate callbacks.
The basic problem is that you are using delegate with a singleton.
Setting the delegate in viewWillAppear is not a good solution either. In short, when view controllers are being shown and hidden, the delegate on your singleton will changed all the time.
Don't use delegates with singletons. Use a completion callback. Otherwise you will keep running into problems.
func apiCall(onCompletion: (() -> Void)?) {
let urlString = URL(string: "https://google.com")
if let url = urlString {
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
if error != nil {
print(error)
} else if let usableData = data {
onCompletion?()
}
}
task.resume()
}
}
called as
SampleHelper.apiCall {
// do something
}
Edit 2
After you posted your code, i realize that you have used the singleton class for delegation.
Delegates allows an object to send a message to another object.
Answer for your query is "No". You can not trigger same function in both controller at a time with delegate.
If you really want to listen an event in both class at a time, i would suggest you to use NSNotificationCenter instead of delegate.
Thats not the correct way to achieve this. I think proper way to set delegate only in respective UIViewController rather than implementing that protocol on BaseViewController and then overriding in child classes. So your implementation should be like.
In HomeViewController
class HomeViewController: BaseClassViewController {
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(true)
SampleHelper.sharedInstance.delegate = self;
}
func shouldCallDelegateMethod() {
// Provide implementation
}
}
In DetailViewController
class DetailViewController: BaseClassViewController {
override func viewWillAppear(_ animated: Bool) {
super.viewDidLoad()
SampleHelper.sharedInstance.delegate = self;
}
func shouldCallDelegateMethod() {
// Provide implementation
}
}
Using this imeplementation you will be having only one-to-one communication design pattern, ensuring right UIViewController to be called.

Trigger function in VC when modal view is dismissed

I’m trying to trigger a function after dismissing a modal VC (FirstStartVC) back to the main VC. I know that I have to use delegation but it doesn’t work and my debug area stays empty.
In other question topics there were people who had it work exact the same way like below. So I have no idea what I’m doing wrong. Does anyone know what I need to change to the code?
// FirstStartVC.swift
//
import UIKit
import CoreData
import JSSAlertView
protocol NewUser: class {
func newUserAction()
}
class FirstStartVC: UITableViewController, UITextFieldDelegate {
var delegation : NewUser?
func saveNewUser(){
self.delegation?.newUserAction()
self.dismiss(animated: true, completion: nil)
}
}
#IBAction func saveSettings(_ sender: Any) {
self.saveNewUser()
}
override func viewDidLoad() {
super.viewDidLoad()
print (delegation)
}
}
//
// ViewController.swift
//
import UIKit
import UserNotifications
import GoogleMobileAds
import CoreData
import JSSAlertView
class ViewController: UIViewController, UNUserNotificationCenterDelegate, NewUser {
func newUserAction() {
print("Reload some labels")
}
override func viewDidLoad() {
super.viewDidLoad()
var firstStart = FirstStartVC()
firstStart.delegation = self
}
}
Swift 3
In your main VC viewDidLoad add:
NotificationCenter.default.addObserver(self, selector: #selector(mainVc.functionName), name:"NotificationID", object: nil)
and add a function in main VC
func functionName() {
// Do stuff
}
in FirstStartVC call the method with
NotificationCenter.default.postNotificationName("NotificationID", object: nil)
Hope this helps!
A simple edit on Swift 4
NotificationCenter.default.addObserver(self, selector: #selector(self.funcName), name: NSNotification.Name(rawValue: "NotificationID"), object: nil)
Put #objc before the function definition.
#objc func functionName() {
// Do stuff
}
In your code, you have:
func saveNewUser(){
self.delegation?.newUserAction()
self.dismiss(animated: true, completion: nil)
}
}
Simply write the code you want to run after dismissing in completion::
func saveNewUser() {
self.delegation?.newUserAction()
self.dismiss(animated: true, completion: { finished in
// on completion
})
}
}
(You might not even need to say finished in or anything like that.)
If the code you need to execute within newUserAction() is a part of FirstStartVC, you should just call it inside the completion handler of the dismiss(_:animated:) method. However, if you need the code to execute on the VC that presented FirstStartVC, make sure that it conforms to the NewUser protocol. You could do something like this (assuming the presenting VC was named something like PresentingViewController - change it to whatever is the case for your project):
class PresentingViewController: UIViewController {
// However you instantiate the FirstStartVC
let firstStart = FirstStartVC()
// set the delegation property to self
firstStart.delegation = self
}
Then at the bottom of the screen create an extension so it conforms to the protocol:
extension PresentingViewController: NewUser {
func newUserAction() {
// Here you can do whatever you want when the delegation calls this method
}
}
EDIT: - Further recommendation...
I always find it best practice with delegates to use a weak reference to prevent memory problems. To do so you have to make sure to set the protocol as :class, which you've already completed: protocol NewUser: class. So then when you create the property at the top of FirstStartVC you would just say
weak var delegation: NewUser?
Your code will still run the same, I just recommend doing it this way as it's helped me avoid memory issues in numerous instances.

Class protocols not working after updating from Swift 2.3 to Swift 3.0

I have a very basic user class, who os responsible for get user data from Firebase and update the currently screen if needed, everything was working until a decided to update my project to Swift 3.0
This is the User Class
#objc protocol userClassProtocol {
func updateScreen()
#objc optional func sucessUnlockedCategory()
}
class User {
static let sharedInstance = User()
internal var delegate : userClassProtocol?
func fakeClassThatGetsDataFromFirebase() {
//Got data
print("Has new data")
self.delegate?.updateScreen()
}
}
And here is the ViewController:
class FirstViewController: UIViewController, userClassProtocol {
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
print("View will load")
User.sharedInstance.delegate = self
}
func updateScreen() {
print("Screen is going to update")
//Do stuff here
}
}
The logs i get from this are:
Has new Data
View Will Appear
But the function updateScreen() in the view controller never gets called. No errors are being pointed out by Xcode.
Looks like it's just an issue with the timing of your method calls.
Your fakeClassThatGetsDataFromFirebase method is called first, and at that point, your delegate hasn't been set. So self.delegate is nil when calling:
self.delegate?.updateScreen()
This will only work if your viewWillAppear gets called before fakeClassThatGetsDataFromFirebase
It shouldn't get called, according to your code. Nothing calls fakeClassThatGetsDataFromFirebase(), which is what should call updateScreen() on your delegate method.

Call view controller method from custom class

I have a custom class and a view controller.
My custom class:
class ChatManager:NSObject {
func messageArrived() {
//When Message arrives I am handling it from here
//I need something like that: Viewcontroller.updateTable()
}
}
When message arrives from internet I need to update tableview in view controller. So I mean I have to call a view controller method from messageArrived method. How can I do this ?
Here is a simple example of using delegate:
declare the delegate before your chat manager class
protocol ChatManagerDelegate {
func manageMessage()
}
when the message arrived, call the delegate method to handle it.
class ChatManager: NSObject {
var delegate: ChatManagerDelegate?
func messageArrived() {
self.delegate!.manageMessage()
}
}
in your view controller, remember to set the delegate of the chat manager to self.
class ViewController: ChatManagerDelegate {
var manager = ChatManager()
manager.delegate = self
func manageMessage() {
self.updateTable()
}
}
This would be a possible implementation:
ViewController:
class ViewController: UIViewController,ChatManagerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
let myChatManager = ChatManager()
myChatManager.delegate = self
}
func messageDidArrive() {
// Do Things here.
}
}
Chatmanager:
class ChatManager:NSObject {
var delegate:ChatManagerDelegate?
func messageArrived() {
//When Message arrives I am handling it from here
//I need something like that: Viewcontroller.updateTable()
}
}
Delegate-Protocol:
protocol ChatManagerDelegate{
func messageDidArrive()
}

Resources