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
}
Related
I am trying to create first viewcontroller button click to call second viewcntroller method without any navigation. I mean need to stay first view controller but need to cal method only on second viewcontroller and print there some NSLog.
Simply try this:
Second View Controller :
-(void)viewDidLoad
{
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(doClickIt:) name:#"notificationName" object:nil];
}
-(void)doClickIt:(NSNotification*)notification {
}
First View Controller:
On Button click IBAction method called:
-(IBAction) someMethod3:(id) sender{
[[NSNotificationCenter defaultCenter] postNotificationName:#"notificationName" object:obj];
}
Thats it.
Hope it will help you.
If I understand your question right you could do the following:
In your firstviewcontroller:
#import "secondviewcontroller.h" - (Or the name of the viewcontroller)
In the (IBAction) method do the following:
Secondviewcontroller *sec = [Secondviewcontroller alloc]init]
[sec theMethod];
Then it will get called.
Can i ask, how come you want to call an action from a non-active viewcontroller?
Try Following:
In ViewControllerA.h declare a method.
In ViewControllerA.m Define a method.
like below:
-(NSMutableArray*) MethodOfA;
In ViewControllerB.h:
#import "ViewControllerA.h"
.......
#property (nonatomic, strong) ViewControllerA * viewControllerA;
#property (nonatomic, strong) NSMutableArray * mutableArray;
In ViewControllerB.m:
in your Button action add below code:
self.mutableArray =[self.viewControllerA MethodOfA];
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.
I'm learning objective-c for iOS and have a question about creating my first target-action mechanism. I've got it to work, but currently I just set the target: portion of the addTarget:action:changeForControlEvents: method to nil, meaning it will search around my app for the target instead of drilling down on ViewController.m, where the method I want to send a message is located.
How can I tell the addTarget:action:changeForControlEvents: method which class to search first?
Here is a simple version of my current code:
The view:
// View.m
#import View.h
#implementation
- (void)sendAction
{
UIControl *button = [[UIControl alloc] init];
[button addTarget:nil // How do I make this look for ViewController.m?
action:#selector(changeButtonColor)
changeforControlEvents:UIControlEventTouchUpInside];
}
#end
...and the View Controller:
// ViewController.m
#import ViewController.h
#implementation
- (void)target
{
NSLog(#"Action received!");
}
#end
Thanks for the help!
You cannot simply called UIViewController if it doesn't load or allocate in the memory. To achieve this you need to alloc that class.
one way to do using singleton
[button addTarget:[ViewController sharedManager] action:#selector(target)
forControlEvents:UIControlEventTouchUpInside];
or using NSNotificationCenter, assuming that the class is already running (stack in previous navigation / another tabs).
// View.m
#import View.h
#implementation
- (void)sendAction
{
UIControl *button = [[UIControl alloc] init];
[button addTarget:self
action:#selector(controlAction)
changeforControlEvents:UIControlEventTouchUpInside];
}
-(void)controlAction
{
[[NSNotificationCenter defaultCenter]
postNotificationName:#"changeButtonColor"
object:self];
}
#end
and for target UIViewController
// ViewController.m
#import ViewController.h
#implementation
-(void) viewDidLoad
{
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(receiveNotification:)
name:#"changeButtonColor"
object:nil];
- (void)receiveNotification:(NSNotification *) notification
{
NSLog(#"Action received!");
}
#end
Assuming ViewController is the VC that created the view you're working in you should be able to use:
addTarget:[self superview]
I have a simple test app to help me learn how to persist data from a NSMutableArray to a plist. Everything seems to be working well, until I try to save the data by calling a ViewController method called "saveData" in my AppDelegate.m file:
- (void)applicationDidEnterBackground:(UIApplication *)application
{
[ViewController saveData];
}
I get a "No known class method for selector 'saveData', although the method is clearly declared in ViewController.h, like so:
//
// ViewController.h
// PlistTest
//
// Created by Tim Jones on 10/30/13.
// Copyright (c) 2013 TDJ. All rights reserved.
//
#import <UIKit/UIKit.h>
#interface ViewController : UIViewController
#property (weak, nonatomic) IBOutlet UILabel *nameLabel;
#property (weak, nonatomic) IBOutlet UILabel *timeLabel;
#property NSMutableArray *mainActivityArray;
- (IBAction)buttonHit:(id)sender;
-(NSString *) getFilePath;
-(void) saveData;
-(void) loadData;
#end
and implemented in ViewController.m, thusly:
//
// ViewController.m
// PlistTest
//
// Created by Tim Jones on 10/30/13.
// Copyright (c) 2013 TDJ. All rights reserved.
//
#import "ViewController.h"
#import "DataClass.h"
#interface ViewController ()
#end
#implementation ViewController
-(NSString *) getFilePath
{
NSArray *pathArray = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
return [[pathArray objectAtIndex:0] stringByAppendingPathComponent:#"PlistTestData"];
}
-(void) saveData
{
[self.mainActivityArray writeToFile: self.getFilePath atomically:YES];
}
I imported the ViewController.h into AppDelegate.h.
I'm pretty green, so I expect the problem may be obvious to many here. Would sure appreciate some help.
Issue:
[ViewController saveData];
You are calling saveData method using class name ViewController.
But saveData is an instance method, not class method.
-(void) saveData;
Fixes:
1) Declare saveData as class method
+(void) saveData;
2) Call saveData using the object of ViewController.
ViewController *vControl = [[ViewController alloc] init];
[vControl saveData];
Previous answer (Midhun #2) can work, but I think you would be better off using the Application Did Enter Background Notification and skip the delegate.
Just add this to "view did load": it will call saveData whenever the app goes to background.
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(saveData) name:UIApplicationDidEnterBackgroundNotification object:nil];
Hope this helps.
As others have noted, your issue is that your [ViewController saveData] method invocation clearly suggests that you're trying to call a "class method", and you undoubtedly want to call your "instance method" (because it's your instance of your view controller that has the data you want to save). To achieve that, you have two basic choices:
You can have your app delegate call the saveData method in your view controller.
In various comments, you mention that you've "tried instantiating the VC every way and in every place I can imagine." Don't. Midhun's example was a conceptual one, illustrating the difference between a class and instance method. But, while you want to call the instance method, you want to call this for your existing instance of your view controller, definitely not instantiating a new view controller.
So, you may ask, how do you get a reference to the existing instance of your view controller? What you'll want to do is to (a) create a property in your app delegate to hold the reference to the view controller with the saveData method; (b) have that view controller set that property of the app delegate. So, first, create a property in your app delegate's .h file to reference the view controller:
#property (weak, nonatomic) ViewController *viewController;
Obviously, don't forget the #import "ViewController.h" line early in the .h file.
Second, have the viewDidLoad method of the view controller update the app delegate's viewController property:
- (void)viewDidLoad
{
[super viewDidLoad];
AppDelegate *delegate = [[UIApplication sharedApplication] delegate];
delegate.viewController = self;
}
Again, don't forget to #import "AppDelegate.h" at the top of your ViewController.m file.
Having done that, then app delegate's applicationDidEnterBackground can now reference this property that you set in viewDidLoad
- (void)applicationDidEnterBackground:(UIApplication *)application
{
[self.viewController saveData];
}
Frankly, if you did this technique, I might suggest further refinements, notably employing a delegate-protocol pattern, but I'm going to defer that conversation until after you have mastered the above technique.
Even easier than the above is to eliminate this app delegate applicationDidEnterBackground code altogether, and just have your view controller, itself, respond to the system notification that is associated with the app entering background. And, needless to say, you put this code right in the view controller itself.
- (void)viewDidLoad
{
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(saveData) name:UIApplicationDidEnterBackgroundNotification object:nil];
}
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidEnterBackgroundNotification object:nil];
}
- (void)saveData
{
// save your data here
}
As you can see, I register to observe UIApplicationDidEnterBackgroundNotification in viewDidLoad, but also make sure to remove my observer in dealloc. I also made sure that my #selector method name exactly matched my method name (e.g., in my example, no parameters, and hence no colon).
In the app I'm working on, I have a UIViewController sublcass and a UIView subclass. in the storyboard the view controller contains the UIview. in the uiview I'm drawing something but I need it to know some values that it should be getting from the view controller. So I created a custom protocol in the view controller .h file:
#protocol SSGraphViewControllerProtocol <NSObject>
- (void)numberOfSemesters:(int)number;
#end
#property (weak, nonatomic) id <SSGraphViewControllerProtocol> delegate;
and in the UIView class I confirmed it as having the protocol above and I implemented its method. However. when I pass a number from the view controller, UIView doesn't receive it. Using NSLog, I figured out that UIView isn't entering - (void)numberOfS:(int)number; am I doing anything wrong? How can I fix it? and is there another way that I can send data from the UIViewController class to the UIView controller?
Here is the full code:
UIViewController.h
#protocol SSGraphViewControllerProtocol <NSObject>
- (void)numberOfSemesters:(int)number;
#end
#interface SSGraphViewController : UIViewController
#property (weak, nonatomic) id <SSGraphViewControllerProtocol> delegate;
#end
UIViewController.m
#implementation SSGraphViewController
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
[self.delegate numberOfSemesters:2];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#end
UIView.h
#interface SSGraph : UIView <SSGraphViewControllerProtocol>
#end
UIView.m
static int numberOfS = 0;
#implementation SSGraph
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
}
SSGraphViewController *graph = [[SSGraphViewController alloc] init];
graph.delegate = self;
return self;
}
- (void) numberOfSemesters:(int)number{NSLog(#"YES");
numberOfSemesters= number;
}
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect
{
}
Read This Article, It is best example with Description
http://css.dzone.com/articles/do-not-publishcreating-your
Also read for create Protocol
Following i describe simple Example for How to create protocol
#DetailViewController.h
#import <UIKit/UIKit.h>
#protocol MasterDelegate <NSObject>
-(void) getButtonTitile:(NSString *)btnTitle;
#end
#interface DetailViewController : MasterViewController
#property (nonatomic, assign) id<MasterDelegate> customDelegate;
#DetailViewController.m
if([self.customDelegate respondsToSelector:#selector(getButtonTitile:)])
{
[self.customDelegate getButtonTitile:button.currentTitle];
}
#MasterViewController.m
create obj of DetailViewController
DetailViewController *obj = [[DetailViewController alloc] init];
obj.customDelegate = self;
[self.navigationController pushViewController:reportTypeVC animated:YES];
and add delegate method in MasterViewController.m for get button title.
#pragma mark -
#pragma mark - Custom Delegate Method
-(void) getButtonTitile:(NSString *)btnTitle;
{
NSLog(#"%#", btnTitle);
}
You're creating a view controller instance inside of initWithFrame:, assigning its delegate to be self, and then not keeping a reference to the controller or adding its view into the view hierarchy. This is certainly not what you meant to do. Make the connection in your storyboard instead, by making the delegate property an IBOutlet and connecting them by right clicking on the view controller and dragging from the circle next to the property name onto your view instance.
As an aside I'm not convinced of the utility of using a protocol in this way. If the view needs to know some information to do its job, if should either expose some properties that can be set by the controller, or declare a dataSource protocol and query its dataSource rather than rely on the view controller defining the interface it needs.
// Add an observer to your ViewController for some action in uiview
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(receiveActionNotification:)
name:#"someActionNotification"
object:nil];
// Post Notification and method in your Viewcontroller will be called
[[NSNotificationCenter defaultCenter] postNotificationName:#"someActionNotification" object:self];
// at the end Dont forget to remove Observer.
[[NSNotificationCenter defaultCenter] removeObserver:#"someActionNotification"];