Reusable solution for keyboard notifications handling in iOS - ios

I've recently realised that in my projects it's often necessary to react to keyboard events, usually adjusting some layout or making some other changes. In order to achieve this I have to complete 3 steps each time:
Register a VC as an observer for keyboard notifications;
Create a couple of corresponding methods to handle notifications;
Finally remove VC from observers list at appropriate time (dealloc,willDissappear, etc.);
I started to think, how can I make this steps into a simple reusable instrument.
The most obvious solution is to subclass a UIViewController, add all three steps to it, and then just override handling methods. This will work, but it's not very flexible. What if I want to stop observing at different point? Another argument against a superclass is that most of my controllers already have 2 parent classes before UIViewController. One for scrolling management and another one for tableView management. Is this a good practice to have such a subclass tree? Any disadvantages?
Second solution that comes to mind is a EventManager class that takes blocks to complete event handling. Here's the code for it:
.h
#import <Foundation/Foundation.h>
#import UIKit;
#interface NKKeyboardEventsManager : NSObject
#property (nonatomic, assign, getter=isObserving) BOOL observing;
+(instancetype)managerWithShowBlock:(void (^)(CGFloat kbHeight))showBlock hideBlock:(void (^)(void))hideBlock ;
- (void)startEventsObserving;
- (void)stopEventsObserving;
#end
.m
#interface NKKeyboardEventsManager ()
#property (nonatomic, copy) void (^showBlock)(CGFloat kbHeight);
#property (nonatomic, copy) void (^hideBlock)(void);
#end
#implementation NKKeyboardEventsManager
+ (instancetype)managerWithShowBlock:(void (^)(CGFloat kbHeight))showBlock hideBlock:(void (^)(void))hideBlock {
NKKeyboardEventsManager *manager = [super new];
if (manager) {
manager.showBlock = showBlock;
manager.hideBlock = hideBlock;
[manager startEventsObserving];
}
return manager;
}
- (void)startEventsObserving {
if (self.isObserving) return;
self.observing = YES;
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(_handleKeyboardWillShow:) name:UIKeyboardWillShowNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(_handleKeyboardWillHide:) name:UIKeyboardWillHideNotification object:nil];
}
- (void)stopEventsObserving {
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
#pragma mark - Private
- (void)_handleKeyboardWillShow:(NSNotification *)paramNotification
{
NSDictionary* info = [paramNotification userInfo];
CGFloat kbHeightNew = [[info objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue].size.height;
if (self.showBlock) {
self.showBlock(kbHeightNew);
}
}
- (void)_handleKeyboardWillHide:(NSNotification *)paramNotification
{
if (self.hideBlock) {
self.hideBlock();
}
}
#end
It works okish, not to much boilerplate code:
#import "NKKeyboardEventsManager.h"
#interface ViewController ()
#property (nonatomic, weak) IBOutlet NSLayoutConstraint *textViewBottomConstraint;
#property (nonatomic, strong) NKKeyboardEventsManager *keyboardManager;
#end
#implementation ViewController
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
__weak typeof(self) weakSelf = self;
self.keyboardManager = [NKKeyboardEventsManager managerWithShowBlock:^(CGFloat kbHeight) {
weakSelf.textViewBottomConstraint.constant = kbHeight + 20;
[weakSelf.view layoutIfNeeded];
} hideBlock:^{
weakSelf.textViewBottomConstraint.constant = 20;
[weakSelf.view layoutIfNeeded];
}];
}
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
[self.keyboardManager stopEventsObserving];
}
But this solution comes with some blocks problem. Everything within the block is captured by the block, and the block itself is captured by the manager which is captured by the VC. I'm walking on a thin ice of retain cycles. This is why I had to set manager to nil to break the cycle.
UPDATE:
I incorporated a tip from #Ben Pious to use weakSelf inside the block.
I'm looking for any suggestions to both solutions. Both do not seem great to me. Throw anything, some minor tweaks or a completely new approach, like some clever #define macro with block parameters or something.

Related

Updating ViewController UILabel from another class

I'm new to development and been trying to figure this out,but after trying various different solutions, I'm still unable to get the result I'm looking for.
I would like to update the UILabel in a ViewController from another class.
here is a little demo program that I cannot get to work.
I have a view controller which has 3 UILabels, one is updated from the viewDidLoad and the other two I would like to update from the other class called Hello which is called from ViewController, I can the see the class is being called correctly as the console is logging the NSLog entry but I cannot get the syntax for updating the UILabel.
Thanks in advance.
ViewController.h
#import <UIKit/UIKit.h>
#interface ViewController : UIViewController
#property (nonatomic, retain) IBOutlet UILabel *firstLabelBox;
#property (nonatomic, retain) IBOutlet UILabel *secondLabelBox;
#property (nonatomic, retain) IBOutlet UILabel *thirdLabelBox;
#end
ViewController.m
#import "ViewController.h"
#import "Hello.h"
#interface ViewController ()
#end
#implementation ViewController
#synthesize firstLabelBox;
#synthesize secondLabelBox;
#synthesize thirdLabelBox;
- (void)viewDidLoad {
[super viewDidLoad];
firstLabelBox.text=#"Hello";
[Hello updatedisplay];
[Hello getStringToDisplay];
}
#end
Hello.h
#import <Foundation/Foundation.h>
#class ViewController;
#interface Hello : NSObject
+(void)updatedisplay;
+(void) getStringToDisplay;
#end
Hello.m
#import "Hello.h"
#import "ViewController.h"
#implementation Hello
+ (void)updatedisplay
{
NSLog(#"NewClass - updatedisplay");
ViewController *labelupdate02 = [[ViewController alloc]init];
labelupdate02.secondLabelBox.text = #"Apple";
}
+ (void) getStringToDisplay
{
NSLog(#"Inside getString function - updatedisplay");
ViewController *labelupdate03 = [[ViewController alloc]init];
labelupdate03.thirdLabelBox.text = #"World";
}
#end
First update method of Hello class
+ (void)updatedisplay
{
NSLog(#"NewClass - updatedisplay");
[[NSNotificationCenter defaultCenter] postNotificationName:#"CHANGE_SECOND_LABEL" object:nil];
}
+ (void) getStringToDisplay
{
NSLog(#"Inside getString function - updatedisplay");
[[NSNotificationCenter defaultCenter] postNotificationName:#"CHANGE_THIRD_LABEL" object:nil];
}
And then update viewDidLoad()
- (void)viewDidLoad
{
[super viewDidLoad];
firstLabelBox.text=#"Hello";
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(secondLabel) name:#"CHANGE_SECOND_LABEL" object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(thirdLabel) name:#"CHANGE_THIRD_LABEL" object:nil];
[Hello updatedisplay];
[Hello getStringToDisplay];
}
Implement below methods in ViewController.m.
- (void)secondLabel
{
secondLabelBox.text = #"Apple";
}
- (void)thirdLabel
{
thirdLabelBox.text = #"World";
}

What is the proper way to implement MVC pattern in iOS app?

I'm trying to make a clean MVC project.
So is it good or bad idea to use NSNotificationCenter's observers for communication between UIViews and ViewControllers?
For example in the CustomView.m i make a button:
- (void) makeSomeButton{
....
[bt addTarget:self action:#(buttonWasClicked) forControlEvents:UIControlEventTouchDragInside];
...
}
- (void) buttonWasClicked {
[[NSNotificationCenter defaultCenter] postNotificationName:#"buttonWasClicked" object:nil];
}
And in the viewCotroller.m i'm adding observer in init section:
- (void)viewDidLoad { //
[self.view addSubview: [[CustomView alloc] initWithFrame ...]];
.....
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#(buttonWasClicked) name:#"buttonWasClicked" object:nil];
.....
}
then
- (void) buttonWasClicked{
// button was clicked in customView so do something
}
If it's not correct, please, explain what is the proper way to implement MVC pattern in iOS app?
No, Notification Center shouldn't be used in this scenario.
The pattern I would use here is delegation.
in your CustomView, declare a protocol, with some method,
at the top of your header:
#protocol CustomViewDelegate : NSObject
- (void)customViewDidSelectButton;
#end
in the interface.
#interface CustomView : NSObject
---
#property (nonatomic, weak) id <CustomViewDelegate> delegate;
---
#end
in the implementation:
- (void) buttonWasClicked {
[self.delegate customViewDidSelectButton];
}
In the View Controller Observing
in the implementation file add <CustomViewDelegate> (same place you put TableViewDelegate etc..)
and then when you create the CustomView set is delegate to self.
implement the delegate method:
- (void)customViewDidSelectButton {
// button was clicked in customView so do something
}

Objective-C how to release yourself in ARC code

I'm new to Objective-C (from java background), so apologies if this question is too trivial.
Suppose i have two classes, where one holds a reference to another, as such:
#interface PostOffice
#property (nonatomic, strong) MailGuy *mailGuy;
#end
#implementation PostOffice
-(void)getMailmanToSendMail {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
self.mailGuy = [[MailGuy alloc] init];
[self.mailGuy sendMail];
}
}
#end
and for MailGuy:
#interface MailGuy () <MFMailComposeViewControllerDelegate>
#end
#implementation MailGuy
-(void)sendMail {
NSLog(#"send mail");
[self.viewController presentViewController:mailViewController animated:YES completion:nil];
}
- (void)mailComposeController:(MFMailComposeViewController *)controller didFinishWithResult:(MFMailComposeResult)result error:(NSError *)error {
// upon dismissal, how do i get the PostOffice instance to release this MailGuy instance?
}
#end
How do i get the PostOffice to release the MailGuy?? i only know when it should be free based on the callback. but i don't want to store a reference to the PostOffice? or do i ? and does it matter that i'm instantiating the MailGuy from a background thread?
any help would be appreciated. thanks!
The usual way to do so is to use protocol and delegates.
So in your MailGuy.h you should add a protocol
#protocol MailGuyDelegate
- (void) didPostHisLetter;
#end
And still in the .h file but this time INSIDE the #interface you would add a delegate reference
#property (weak, nonatomic) id <MailGuyDelegate> delegate;
This adds a delegate property to your MailGuy and it says that the delegate must implement the given protocol (which has the method).
Then in your mail guy implementation code here's what you would do
- (void)mailComposeController:(MFMailComposeViewController *)controller didFinishWithResult:(MFMailComposeResult)result error:(NSError *)error {
if (self.delegate) {
[self.delegate didPostHisLetter];
}
}
This tells his delegate "Hey I finished my job". So all you have to do now is implement the delegate in your PostOffice class
In your .m file of PostOffice, add a private property
#interface PostOffice() <MailGuyDelegate>
#end
Then when you invoke your mail guy, you associate it's delegate to self. Notice that I remove the async dispatch as it is not used and may cause problems as mentioned in comments
-(void)getMailmanToSendMail {
self.mailGuy = [[MailGuy alloc] init];
self.mailGuy.delegate = self;
[self.mailGuy sendMail];
}
And all is left to do is implement the protocol's method (still in postoffice implementation)
- (void) didPostHisLetter {
self.mailGuy = nil;
}

NSNotificationCenter Not Working Between ViewControllers

Ive been hitting the wall for two days on this hard (but simple for you) problem.
The problem is as follows:
I am posting a notification in the appdelegate
I am attempting to receive that notification in a viewcontroller but I cannot receive it.
Here is the code for this.
In the appdelegate.h
#import <UIKit/UIKit.h>
#interface AppDelegate : UIResponder <UIApplicationDelegate>
#property (strong, nonatomic) UIWindow *window;
#property (strong, nonatomic) NSString * someString;
#end
In the appdelegate.m
#import "AppDelegate.h"
#import "SomeContextExample.h"
#implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// Override point for customization after application launch.
self.someString = #"Hello!";
NSDictionary * userInfo = #{SomeContextExampleRef : self.someString};
[[NSNotificationCenter defaultCenter] postNotificationName:#"SomeContextExample"
object:nil
userInfo:userInfo];
return YES;
}
The "SomeContextExampleRef" is coming from a .h file as follows:
#ifndef SampleNotWorking_SomeContextExample_h
#define SampleNotWorking_SomeContextExample_h
#define SomeContextExampleRef #"SomeContextExampleRef"
#endif
Finally, in the viewController.m:
#import "ViewController.h"
#import "SomeContextExample.h"
#interface ViewController ()
#end
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
NSOperationQueue *mainQueue = [NSOperationQueue mainQueue];
[[NSNotificationCenter defaultCenter] addObserverForName:#"SomeContextExample"
object:nil
queue:mainQueue
usingBlock:^(NSNotification *note)
{
NSLog(#"got the notification!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! %#", note.userInfo);
}];
}
My full code is attached here:
https://github.com/moomoo23/SampleNotWorking
Thank you for helping a beginner!
Try posting your notification when you are confident your "ViewController" object has instantiated and/or come into view. E.G. why not try it out by putting it into an "IBAction" method fired by some UIButton?
The observing view controller may or may not (more likely not in your case) be existing at the end of "application:didFinishLaunchingWithOptions:" in your app delegate.
Another alternative is to post the notification after a delay using the scheduledTimerWithTimeInterval:target:selector:userInfo:repeats: method.
Now, conceptually, why would you post a notification when the appFinsihedLaunching?.... just curious.... e

how to implement memoryReceiveWarning

i am in initial phase of development and i want to make sure i am in right direction with regards to memory management.
i have a view controller named LayoutViewController with xib.I have a custom ui-subclass with its xib named LayoutContainerView which basically contains a scrollview. I am using LayoutContainerView in LayoutViewController xib by IBOutlet.
I have an another UIView subclassed, which contains a view with background image, some labels and a transparent button with same frame as of viw.I am adding this custom controll in LayoutContainerView's scrollview.
my view controller .h looks like this:
#import <UIKit/UIKit.h>
#protocol LayoutVcRemovedProtocol;
extern const char* MyConstantKey;
#interface LayoutViewController : UIViewController
// public properties
#property (nonatomic, copy) NSString *databasePath;
#property (nonatomic, weak) id<LayoutVcRemovedProtocol> layoutVcRemovedProtocolDelegate;
#end
#protocol LayoutVcRemovedProtocol<NSObject>
-(void) layoutVcRemovedProtocolMethod;
#end
=========================
**some of relevant code of **implementation** file looks like this:**
//private stuffs goes here
const char* MyConstantKey = "MyConstantKey";
#interface LayoutViewController () <UIActionSheetDelegate, UIAlertViewDelegate>
#property(nonatomic, weak) IBOutlet LayoutContainerView *layoutContainerView;
#property(nonatomic, weak) IBOutlet UIButton *backbutton;
#property(nonatomic, weak) IBOutlet UILabel *layoutNameLabel;
#property (nonatomic, strong) UIView *baseView;
#property (nonatomic, strong) NSMutableArray *layoutModelArray;
-(IBAction)backButtonPressed;
#end
#implementation LayoutViewController
//my viewDidLoad and viewWillAppear look like this:
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
[self adjustLayoutContainerFrameAndSetDataBasePath];
}
-(void) viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
self.layoutNameLabel.text = [Utils getLayoutName];
[self getLatestData];
}
-(void) getLatestData
{
[self setUpDataSource];
[self setUpComponentsOnLayoutScreen];
}
#pragma mark - datasource method
-(void)setUpDataSource
{`
self.layoutModelArray = (NSMutableArray *)[LAYOUTMODULE getAllLayoutData];
}`
-(void)setUpComponentsOnLayoutScreen
{`
for (int i = 0; i < self.layoutModelArray.count; i++)
{
Layout *layout = [self.layoutModelArray objectAtIndex:i];
[self drawViewWithLayoutObject:layout];
}
[self.layoutContainerView.scrollView adjustContentSize];
}
this is what i am trying to manage memory:
-(void) cleanLayoutModelArray
{
if (self.layoutModelArray != nil && self.layoutModelArray.count >0)
{
[self.layoutModelArray removeAllObjects];
}
}
-(void) cleanComponents
{
[self.layoutContainerView.scrollView.subviews makeObjectsPerformSelector:#selector(removeFromSuperview)];
}
//user events
-(void) placeOrderForLayout:(Layout *)layout
{
[DELEGATE showLandscapeLoading];
//web service COMMUNICATION HERE here
OrderModule *oModule = [OrderModule sharedModule];
NSDictionary *requestDictionary = [NSDictionary dictionaryWithObjectsAndKeys:oModule.currentOrder.mOrderId,#"order_id",oModule.currentOrder.mPosId,#"pos_id",[Utils currentDate],#"book_date", layout.componentId, #"component_id", nil];
BOOL status = [LAYOUTMODULE placeComponentOrderThroughAPI:requestDictionary];
if (status == TRUE)
{
[self performCleanUp];
[self getLatestData];
}
[DELEGATE stopLandscapeLoading];
}
help me or any suggestion for:
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
what ever i try in memoryWarningDelegate view controller becomes black screen.
You've got a lot of #properties marked 'weak' in your code - when you mark things as weak, that means that something else is keeping track of whether it should still exist. Here you're doing it with IBOutlet items, which your controller should be keeping track, so they should be marked 'strong,' not weak. I'd review all your usage of 'weak' in this. Also refer to Apple's excellent doc on memory management. ARC will be handling most of your memory management for you. Usually, the only thing you need to do in didReceiveMemoryWarning, is to set to nil anything that's a large object, say a video or webpage you can reload if the user needs it again. Often, there's not much that you can free up at this time in your typical view. Note also that iOS devices have a fairly substantial memory footprint these days, so you should not worry about small data structures remaining resident in memory as long as they have the potential to be needed again. If you're having out of memory issues, I'd run with instruments and check for leaks.

Resources