Calling delegate method from Swift's Extension class - swift

All
I am implementing a UIAlertView extension for callback implementation like UIAlertView+Block in Objective C but the delegate method is not calling from this class any help.
Thanks in advance.

The callback from the alert view runs in a background thread and the code receiving it is probably running in the main thread, as per apple documentation:
From the Apple iOS Documentation on Notification Centers:
In a multithreaded application, notifications are always delivered in
the thread in which the notification was posted, which may not be the
same thread in which an observer registered itself.
You need to call the notification back in the main thread otherwise strange things can happen
dispatch_async(dispatch_get_main_queue(), {
//Call your notification from here
NSNotificationCenter.defaultCenter().postNotificationName(mySpecialNotificationKey, object: self)
})

This is the updated answer after solving the issue.
typealias SelectBlock = (buttonIndex : Int) -> Void
typealias CancelBlock = () -> Void
private var blockSelect : SelectBlock?
private var blockCancel : CancelBlock?
extension UIAlertView {
//Initilization
func initWithTitle(
title : String ,
message : String,
onSelect : SelectBlock?,
onCancel : CancelBlock?,
otherButtonTitles : [String]){
//Initialize
self.title = title
self.delegate = self
self.message = message
//Block
if let onSelectObj = onSelect? {
blockSelect = onSelectObj
}
if let onCancelObj = onCancel? {
blockCancel = onCancelObj
}
//Other buttons
for buttons in otherButtonTitles {
self.addButtonWithTitle(buttons)
}
//Cancel index
// println("buttons \(self.numberOfButtons)")
self.cancelButtonIndex = 0
self.show()
}
func alertView(alertView: UIAlertView, didDismissWithButtonIndex buttonIndex: Int) {
println("Index \(buttonIndex)")
if buttonIndex == alertView.cancelButtonIndex {
blockCancel!()
// println("Cancel")
}else{
blockSelect!(buttonIndex: buttonIndex)
// println("Retry")
}
}
}

Related

NSTouchBar integration not calling

I am integrating TouchBar support to my App. I used the how to from Rey Wenderlich and implemented everything as follows:
If self.touchBarArraygot filled the makeTouchBar() Method returns the NSTouchBar object. If I print out some tests the identifiers object is filled and works.
What not work is that the makeItemForIdentifier method not get triggered. So the items do not get created and the TouchBar is still empty.
Strange behavior: If I add print(touchBar) and a Breakpoint before returning the NSTouchBar object it works and the TouchBar get presented as it should (also the makeItemForIdentifier function gets triggered). Even if it disappears after some seconds... also strange.
#available(OSX 10.12.2, *)
extension ViewController: NSTouchBarDelegate {
override func makeTouchBar() -> NSTouchBar? {
if(self.touchBarArray.count != 0) {
let touchBar = NSTouchBar()
touchBar.delegate = self
touchBar.customizationIdentifier = NSTouchBarCustomizationIdentifier("com.TaskControl.ViewController.WorkspaceBar")
var identifiers: [NSTouchBarItemIdentifier] = []
for (workspaceId, _) in self.touchBarArray {
identifiers.append(NSTouchBarItemIdentifier("com.TaskControl.ViewController.WorkspaceBar.\(workspaceId)"))
}
touchBar.defaultItemIdentifiers = identifiers
touchBar.customizationAllowedItemIdentifiers = identifiers
return touchBar
}
return nil
}
func touchBar(_ touchBar: NSTouchBar, makeItemForIdentifier identifier: NSTouchBarItemIdentifier) -> NSTouchBarItem? {
if(self.touchBarArray.count != 0) {
for (workspaceId, data) in self.touchBarArray {
if(identifier == NSTouchBarItemIdentifier("com.TaskControl.ViewController.WorkspaceBar.\(workspaceId)")) {
let saveItem = NSCustomTouchBarItem(identifier: identifier)
let button = NSButton(title: data["name"] as! String, target: self, action: #selector(self.touchBarPressed))
button.bezelColor = NSColor(red:0.35, green:0.61, blue:0.35, alpha:1.00)
saveItem.view = button
return saveItem
}
}
}
return nil
}
}
self.view.window?.makeFirstResponder(self) in viewDidLoad() did solve the problem.

Keyboard overlaying action sheet in iOS 13.1 on CNContactViewController

This seems to be specific to iOS 13.1, as it works as expected on iOS 13.0 and earlier versions to add a contact in CNContactViewController, if I 'Cancel', the action sheet is overlapping by keyboard. No actions getting performed and keyboard is not dismissing.
Kudos to #GxocT for the the great workaround! Helped my users immensely.
But I wanted to share my code based on #GxocT solution hoping it will help others in this scenario.
I needed my CNContactViewControllerDelegate contactViewController(_:didCompleteWith:) to be called on cancel (as well as done).
Also my code was not in a UIViewController so there is no self.navigationController
I also dont like using force unwraps when I can help it. I have been bitten in the past so I chained if lets in the setup
Here's what I did:
Extend CNContactViewController and place the swizzle function in
there.
In my case in the swizzle function just call the
CNContactViewControllerDelegate delegate
contactViewController(_:didCompleteWith:) with self and
self.contact object from the contact controller
In the setup code, make sure the swizzleMethod call to
class_getInstanceMethod specifies the CNContactViewController
class instead of self
And the Swift code:
class MyClass: CNContactViewControllerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
self.changeImplementation()
}
func changeCancelImplementation() {
let originalSelector = Selector(("editCancel:"))
let swizzledSelector = #selector(CNContactViewController.cancelHack)
if let originalMethod = class_getInstanceMethod(object_getClass(CNContactViewController()), originalSelector),
let swizzledMethod = class_getInstanceMethod(object_getClass(CNContactViewController()), swizzledSelector) {
method_exchangeImplementations(originalMethod, swizzledMethod)
}
}
func contactViewController(_ viewController: CNContactViewController, didCompleteWith contact: CNContact?) {
// dismiss the contacts controller as usual
viewController.dismiss(animated: true, completion: nil)
// do other stuff when your contact is canceled or saved
...
}
}
extension CNContactViewController {
#objc func cancelHack() {
self.delegate?.contactViewController?(self, didCompleteWith: self.contact)
}
}
The keyboard still shows momentarily but drops just after the Contacts controller dismisses.
Lets hope apple fixes this
I couldn't find a way to dismiss keyboard. But at least you can pop ViewController using my method.
Don't know why but it's impossible to dismiss keyboard in CNContactViewController. I tried endEditing:, make new UITextField firstResponder and so on. Nothing worked.
I tried to alter action for "Cancel" button. You can find this button in NavigationController stack, But it's action is changed every time you type something.
Finally I used method swizzling. I couldn't find a way to dismiss keyboard as I mentioned earlier, but at least you can dismiss CNContactViewController when "Cancel" button is pressed.
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
changeImplementation()
}
#IBAction func userPressedButton(_ sender: Any) {
let controller = CNContactViewController(forNewContact: nil)
controller.delegate = self
navigationController?.pushViewController(controller, animated: true)
}
#objc func popController() {
self.navigationController?.popViewController(animated: true)
}
func changeImplementation() {
let originalSelector = Selector("editCancel:")
let swizzledSelector = #selector(self.popController)
if let originalMethod = class_getInstanceMethod(object_getClass(CNContactViewController()), originalSelector),
let swizzledMethod = class_getInstanceMethod(object_getClass(CNContactViewController()), swizzledSelector) {
method_exchangeImplementations(originalMethod, swizzledMethod)
}
}
}
PS: You can find additional info on reddit topic: https://www.reddit.com/r/swift/comments/dc9n3a/bug_with_cnviewcontroller_ios_131/
Fixed in iOS 13.4
Tested in Xcode Simulator
NOTE: This bug is now fixed. This question and answer were applicable only to some particular versions of iOS (a limited range of iOS 13 versions).
The user can in fact swipe down to dismiss the keyboard and then tap Cancel and see the action sheet. So this issue is regrettable and definitely a bug (and I have filed a bug report) but not fatal (though, to be sure, the workaround is not trivial for the user to discover).
Thanks, #GxocT for your workaround, however, the solution posted here is different from the one you posted on Reddit.
The one on Reddit works for me, this one doesn't so I want to repost it here.
The difference is on the line with swizzledMethod which should be:
let swizzledMethod = class_getInstanceMethod(object_getClass(self), swizzledSelector) {
The whole updated code is:
class MyClass: CNContactViewControllerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
self.changeImplementation()
}
func changeCancelImplementation() {
let originalSelector = Selector(("editCancel:"))
let swizzledSelector = #selector(CNContactViewController.cancelHack)
if let originalMethod = class_getInstanceMethod(object_getClass(CNContactViewController()), originalSelector),
let swizzledMethod = class_getInstanceMethod(object_getClass(self), swizzledSelector) {
method_exchangeImplementations(originalMethod, swizzledMethod)
}
}
func contactViewController(_ viewController: CNContactViewController, didCompleteWith contact: CNContact?) {
// dismiss the contacts controller as usual
viewController.dismiss(animated: true, completion: nil)
// do other stuff when your contact is canceled or saved
...
}
}
extension CNContactViewController {
#objc func cancelHack() {
self.delegate?.contactViewController?(self, didCompleteWith: self.contact)
}
}
Thanks #Gxoct for his excellent work around. I think this is very useful question & post for those who are working with CNContactViewController. I also had this problem (till now) but in objective c. I interpret the above Swift code into objective c.
- (void)viewDidLoad {
[super viewDidLoad];
Class class = [CNContactViewController class];
SEL originalSelector = #selector(editCancel:);
SEL swizzledSelector = #selector(dismiss); // we will gonna access this method & redirect the delegate via this method
Method originalMethod = class_getInstanceMethod(class, originalSelector);
Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
BOOL didAddMethod =
class_addMethod(class,
originalSelector,
method_getImplementation(swizzledMethod),
method_getTypeEncoding(swizzledMethod));
if (didAddMethod) {
class_replaceMethod(class,
swizzledSelector,
method_getImplementation(originalMethod),
method_getTypeEncoding(originalMethod));
} else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
}
Creating a CNContactViewController category for accessing dismiss;
#implementation CNContactViewController (Test)
- (void) dismiss{
[self.delegate contactViewController:self didCompleteWithContact:self.contact];
}
#end
Guys who are not so familiar with Swizzling you may try this post by matt
One thing to always take into account is that swizzler method is executed only once. Make sure that you implement changeCancelImplementation() in dispatch_once queue so that it is executed only once.
Check this link for description
Also this bug is found only in iOS 13.1, 13.2 and 13.3

Dynamic NSTooltips: Event Key Modifier(s) Affecting Tooltips

Is there a notification mechanism for tooltips, so then key modifiers can be used to make them dynamic? I do not know of one so went about this path to capture the tooltip view being created and trying to trigger a redraw when a key event occurs.
I monitor key modifier(s) (in my app delegate); focusing on SHIFT here but any key modifier will do:
var localKeyDownMonitor : Any? = nil
var globalKeyDownMonitor : Any? = nil
var shiftKeyDown : Bool = false {
didSet {
let notif = Notification(name: Notification.Name(rawValue: "shiftKeyDown"),
object: NSNumber(booleanLiteral: shiftKeyDown));
NotificationCenter.default.post(notif)
}
}
which the app's startup process will install ...
// Local/Global Monitor
_ /*accessEnabled*/ = AXIsProcessTrustedWithOptions([kAXTrustedCheckOptionPrompt.takeUnretainedValue() as String: true] as CFDictionary)
globalKeyDownMonitor = NSEvent.addGlobalMonitorForEvents(matching: NSEventMask.flagsChanged) { (event) -> Void in
_ = self.keyDownMonitor(event: event)
}
localKeyDownMonitor = NSEvent.addLocalMonitorForEvents(matching: NSEventMask.flagsChanged) { (event) -> NSEvent? in
return self.keyDownMonitor(event: event) ? nil : event
}
and then intercept to post notifications via app delegate didSet above (so when the value changes a notification is sent!):
func keyDownMonitor(event: NSEvent) -> Bool {
switch event.modifierFlags.intersection(.deviceIndependentFlagsMask) {
case [.shift]:
self.shiftKeyDown = true
return true
default:
// Only clear when true
if shiftKeyDown { self.shiftKeyDown = false }
return false
}
}
For singleton tooltip bindings, this can be dynamic by having the notification inform (KVO of some key) I'd like a view delegate routine to act on this:
func tableView(_ tableView: NSTableView, toolTipFor cell: NSCell, rect: NSRectPointer, tableColumn: NSTableColumn?, row: Int, mouseLocation: NSPoint) -> String {
if tableView == playlistTableView
{
let play = (playlistArrayController.arrangedObjects as! [PlayList])[row]
if shiftKeyDown {
return String(format: "%ld play(s)", play.plays)
}
else
{
return String(format: "%ld item(s)", play.list.count)
}
}
...
So I'd like my notification handler to do something like
internal func shiftKeyDown(_ note: Notification) {
let keyPaths = ["cornerImage","cornerTooltip","<table-view>.<column-view>"]
for keyPath in (keyPaths)
{
self.willChangeValue(forKey: keyPath)
}
if subview.className == "NSCustomToolTipDrawView" {
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "CustomToolTipView"), object: subview)
}
for keyPath in (keyPaths)
{
self.didChangeValue(forKey: keyPath)
}
}
where I would like to force the tooltip to redraw, but nothing happens.
What I did to obtain the tooltip view, was to watch any notification (use nil name to do this) that looked promising and found one - by monitoring all notifications and so I post to it for interested view controller which is the delegate of the tableView. The view controller is observing for the "CustomToolTipView" and remembers the object send - tooltipView; this is the new tooltip view created.
But nothing happens - shiftKeyDown(), to redraw the view.
I suspect it won't update in the current event loop but I do see the debug output.
With Willeke's suggestion I migrated a tableView to be cell based, added a get property for the tooltip and had the relevant object's init() routine register notifications for the shift key changes. This resulted in less code. A win-win :-)

Xcode "po" command retains value

I've been debugging my code and found that my manager was deinitialised (that was cause of my bug - not calling delegate methods).
What's strange, that during debugging process I've used "po" command after setting the manager's delegate (weak) and it prevented it from being deinitialised (delegate methods were called).
Why is that? Is it proper behaviour?
Xcode 8.3, swift 3.1
EDIT:
//a tap starts everything :)
#IBAction func shareButtonPressed(_ sender: Any) {
let requestManager = FacebookPostRouteRequest() //bug fixed by changing to instance variable
requestManager.delegate = self
requestManager.showShareBadgeDialog(self.badge!, onViewController: self)
}
//in FacebookPostRouteRequest
final weak var delegate: FacebookPostRouteRequestDelegate?
func showShareBadgeDialog(_ badge: Badge, onViewController viewController: UIViewController) {
let dialog = self.initDialog(onViewController: viewController)
guard let imageURL = badge.imageURL else {
self.delegate?.facebookPostRouteRequest(self, didCompleteWithResult: false)
return
}
dialog.shareContent = self.generateImageShareContent(imageURL)
self.show(dialog)
}
private func show(_ dialog: FBSDKShareDialog) {
OperationQueue.main.addOperation {
dialog.delegate = self //when printed out dialog.delegate delegate methods were called! Deinit of FacebookPostRouteRequest is not called.
let showResult = dialog.show()
...
}
}
extension FacebookPostRouteRequest: FBSDKSharingDelegate {
func sharer(_ sharer: FBSDKSharing!, didCompleteWithResults results: [AnyHashable : Any]!) {
...
}
//other delegate methods implemented as well
}
Your problem is here:
#IBAction func shareButtonPressed(_ sender: Any) {
let requestManager = FacebookPostRouteRequest()
requestManager.delegate = self
requestManager.showShareBadgeDialog(self.badge!, onViewController: self)
}
After the last line, the requestManager object will be disposed because it's no longer referenced and will not call any of the delegate methods.
Make requestManager an instance variable:
let requestManager = FacebookPostRouteRequest()
#IBAction func shareButtonPressed(_ sender: Any) {
requestManager.delegate = self
requestManager.showShareBadgeDialog(self.badge!, onViewController: self)
}
Your issues with the debugger are probably race conditions for stopping the main thread.

NSNotificationCenter Notification Not Being Received When Posted in a Closure

What I am trying to accomplish is posting a notification through NSNotificationCenter's default center. This is being done within a closure block after making a network call using Alamofire. The problem I am having is that a class that should be responding to a posted notification isn't receiving such notification.
My ViewController simply creates a First object that get's things moving:
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let first = First()
}
}
My First class creates and instance of a Second class and adds itself as an observer to my NSNotificationCenter. This is the class that can't seem to get the notification when the notification is posted.
class First : NSObject {
let second = Second()
override init(){
super.init()
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(First.gotDownloadNotification(_:)), name: "test", object: nil)
second.sendRequest()
}
// NOT REACHING THIS CODE
func gotDownloadNotification(notification: NSNotification){
print("Successfully received download notification from Second")
}
}
My Second class is what makes the network call through my NetworkService class and posts a notification in a closure once the request is successful and complete.
class Second : NSObject {
func sendRequest(){
let networkService = NetworkService()
networkService.downloadFile() { statusCode in
if let statusCode = statusCode {
print("Successfully got a status code")
// Post notification
NSNotificationCenter.defaultCenter().postNotificationName("test", object: nil)
}
}
}
}
Finally, my NetworkService class is what makes a network call using Alamofire and returns the status code from the response through a closure.
class NetworkService : NSObject {
func downloadFile(completionHandler: (Int?) -> ()){
Alamofire.download(.GET, "https://www.google.com") { temporaryURL, response in
let fileManager = NSFileManager.defaultManager()
let directoryURL = fileManager.URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask)[0]
let pathComponent = response.suggestedFilename
return directoryURL.URLByAppendingPathComponent(pathComponent!)
}
.response { (request, response, _, error) in
if let error = error {
print("File download failed with error: \(error.localizedDescription)")
completionHandler(nil)
} else if let response = response{
print("File downloaded successfully")
// Pass status code through completionHandler to Second
completionHandler(response.statusCode)
}
}
}
}
The output after execution is:
File downloaded successfully
Successfully got a status code
From this output I know the download was successful and Second got the status code from the closure and posted a notification right after.
I believe that I have tried resolving most other suggestions on Stack Overflow related to not receiving notifications such as objects not being instantiated before notification is posted or syntax of either adding an observer or posting a notification.
Does anyone have any idea why the posted notification is not being received in the First class?
Since there is a direct relationship between First and Second the protocol/delegate pattern is the better way to notify. Even better with this pattern and you don't have to take care of unregistering the observer. NSNotificationCenter is supposed to be used only if there is no relationship between sender and receiver.
And basically the thread doesn't matter either.
protocol SecondDelegate {
func gotDownloadNotification()
}
class Second : NSObject {
var delegate : SecondDelegate?
init(delegate : SecondDelegate?) {
self.delegate = delegate
}
func sendRequest(){
let networkService = NetworkService()
networkService.downloadFile() { statusCode in
if let statusCode = statusCode {
print("Successfully got a status code")
// Post notification
self.delegate?.gotDownloadNotification()
}
}
}
}
class First : NSObject, SecondDelegate {
let second : Second
override init(){
super.init()
second = Second(delegate:self)
second.sendRequest()
}
func gotDownloadNotification(){
print("Successfully received download notification from Second")
}
}