One of 35 header files in project (Handed over to me by some other developer; All of them contains same delegates declaration)
#interface ActivityDetailsCN : UIViewController <NSXMLParserDelegate,
AccountStatusDelegate, AccountTypeDelegate, DirectionDelegate, RecipientDelegate,
PriorityDelegate, DurationDelegate, CurrencyDelegate, OppTypeDelegate,
OppCategoryDelegate, DatePickerDelegate, SalutationDelegate, DepartmentDelegate,
LeadTypeDelegate, OwnershipDelegate, MailingDelegate, SourceDelegate,
StateDelegate, CommentsDelegate, CityDelegate, ZipCodeDelegate>
{
//Declaration of iVars goes here...
}
All the delegates declared here contains the same functions. Even their definitions, too.
Each of these delegates are declared before their respective ViewController header file Like this:
#protocol AccountStatusDelegate <NSObject>
- (void)cancelTapped;
- (void)doneTapped;
- (void)selectTapped:(NSString *)string;
#end
#interface AccountStatusVC : UIViewController <NSXMLParserDelegate> {
}
#property (unsafe_unretained) id <AccountStatusDelegate> delegate;
Implementation of cancelTapped:
- (void)cancelTapped {
[objPopOver dismissPopoverAnimated:YES];
}
Implementation of cancelTapped:
- (void)doneTapped {
[tblView reloadData];
[objPopOver dismissPopoverAnimated:YES];
}
Implementation of cancelTapped:
- (void)selectTapped:(NSString *)string
{
if ([string isEqualToString:#"US"])
isTextField = FALSE;
else if([string isEqualToString:#"Other"]) {
appDelegate.strCountry = #"";
isTextField = TRUE;
}
[tblView reloadData];
[objPopOver dismissPopoverAnimated:YES];
}
Now, Coming to the Question: I don't want to repeat it in each and every class (as it is now); I want to make it in cleaner way, Is there any possible solution available ?
Implement the delegate methods in a common superclass, and refactor all of the protocols to be one common TapCallbackDelegate protocol
Related
I have no idea how I should correctly name the title but I know exactly what my problem is (I will eventually edit the title later).
I am pretty new to Objective-C and I am still learning.
So, I have a class that contains a tableView (I will call it ClassA) and another with a normal UIView (ClassB). What I want to do, is to let a button appear when a row is selected.
I created in my ClassB.h file:
+(id)sharedInstance;
#property (retain, nonatomic) IBOutlet UIButton *btn;
-(void) showBtn :(BOOL) show;
And in my ClassB.m file:
#synthesize btn;
static ClassB *this = nil;
(+id) sharedInstance {
if(!this) {
#synchronized (self) {
this = [[ClassB alloc] init];
}
}
return this;
}
-(void)viewDidLoad {
[self showBtn:NO] //because I only want to let it appear when a row is selected.
[self.view addSubview:btn];
}
-(void) showBtn :(BOOL) show { // I called this method in classA.
if (show == NO) {
btn.hidden = YES;
} else {
btn.hidden = NO;
}
}
So when I launch my app, the button is hidden and stays hidden when I select a row. I debugged, and found that btn is nil when I called the method in ClassA. After some research, I found that the method is called for another instance, so here my question, what can I do, to get it called for the right instance?
EDIT
Here part of my ClassA.m
- (void) tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
NSInteger row = [indexPath row];
[[ClassB sharedInstance] showBtn:YES];
}
Observation: The ClassB is a UIViewController which is wrong. UIViewControllers have viewDidLoad.
Implementation Suggestion:
The correct implementation for the requirement would be that you create a custom cell with a button. Hide the button in awakeFromNib method. in didSelectRowAtIndex set the cell.button.isHidden = YES.
This should alone take care of the requirement mentioned above.
I have a UIViewController subclass (say MyViewController).
MyViewController.h
#protocol TargetChangedDelegate
-(void) targetChanged;
#end
#interface MyViewController
#property (weak) id<TargetChangedDelegate> targetChangedDelegate;
-(void) doSomethingOnYourOwn;
#end
MyViewController.m
#implementation MyViewController <TargetChangedDelegate>
-(void) doSomethingOnYourOwn
{
// DO some stuff here
// IS THIS BAD ??
self.targetChangedDelegate = self;
}
-(IBAction) targetSelectionChanged
{
[self.targetChangedDelegate targetChanged];
}
-(void) targetChanged
{
// Do some stuff here
}
#end
Based on certain conditions a class that instantiates an instance of MyViewController may decide to set itself as the delegate or not.
Foo.m
#property(strong) MyViewController *myVC;
-(void) configureViews
{
self.myVC = [[MyViewController alloc] init];
[self.view addSubview:self.myVC];
if (someCondition)
{
self.myVC.targetChangedDelegate = self;
}
else
{
[self.myVC doSomethingOnYourOwn]
//MyViewController sets itself as the targetChangedDelegate
}
}
With reference to the code snippet above, I have the following question:
Is it a violation of MVC/delegation design pattern (or just a bad design) to say:
self.delegate = self;
There's absolutely no problem with setting the delegate to self. In fact it is a good way to provide default delegate functionality if a delegate is not set by somebody else.
Obviously, the delegate property has to be declared weak otherwise you get a reference cycle.
To expand a bit, having read the wrong answer and wrong comments above, if you allow an object to be its own delegate, your code is cleaner because you do not have to surround absolutely every single delegate call with
if ([self delegate] != nil)
{
[[self delegate] someMethod];
}
else
{
[self someMethod];
}
Its not proper way to assign self.delegate = self.
for your functionality, you can do this:
-(void) doSomethingOnYourOwn
{
// DO some stuff here
self.targetChangedDelegate = nil;
}
and when using delegate:
if(self.targetChangedDelegate != nil && [self.targetChangedDelegate respondsToSelector:#selector(targetChanged)]
{
[self.targetChangedDelegate targetChanged];
}
else
{
[self targetChanged];
}
It is bad design to set self.delegate = self; it should be another object. Delegation via protocols are an alternative design to subclassing and you can read more about delegation here:
https://developer.apple.com/library/archive/documentation/General/Conceptual/DevPedia-CocoaCore/Delegation.html
And here is more on protocols:
https://developer.apple.com/library/archive/documentation/General/Conceptual/DevPedia-CocoaCore/Protocol.html
I am a little new to iOS development, coming from a Java / Android background. My understanding is that your custom Protocols or Delegates are like Interfaces in Java land.
If that is the case then I believe these Protocols are also Objects as well.
Case:
Assume 2 ViewControllers, Home and Profile.
1 Presenter, let's call it StuffPresenter gets instantiated individually in both ViewControllers.
StuffPresenter has an initialization method called initWithInteractor that takes in a parameter of Interactor which is a protocol.
Both Home and Profile implement a Protocol called Interactor, which has a method called initStuffInTableView(NSMutableArray *)stuff.
So I have a dilemma where if I am in Home and StuffPresenter relays information then I switch over to Profile, StuffPresenter loads stuff in Home as well as Profile.
Why is this the case?
Here is the code I have setup:
Protocol
#protocol Interactor <NSObject>
- (void)initStuffInTableView:(NSMutableArray *)stuff;
#end
Presenter
#interface Presenter : NSobject
- (id)initWithInteractor:(id<Interactor>)interactor;
- (void)loadStuff;
#end
#implementation {
#private
id<Interactor> _interactor;
}
- (id)initWithInteractor:(id<Interactor>)interactor {
_interactor = interactor;
return self;
}
- (void)loadStuff {
// Load stuff
NSMutableArray *stuff = // Init stuff in array...
[_interactor initStuffInTableView:stuff];
}
#end
Home
#interface HomeViewController : UITableViewController <Interactor>
- (void)initPresenter;
#end
#implementation {
#private
StuffPresenter *_stuffPresenter;
}
- (void)viewDidLoad {
[super viewDidLoad];
[self initPresenter];
[self initPullToRefresh];
}
# pragma mark - init
- (void)initPresenter {
_stuffPresenter = [[StuffPresenter alloc] initWithInteractor:self];
}
- (void)initPullToRefresh {
// Init pull to refresh
// ...
[self.refreshControl addTarget:self
action:#selector(reloadStuff)
forControlEvents:UIControlEventValueChanged];
}
# pragma mark - Interactor
- (void)initStuffInTableView:(NSMutableArray *)stuff {
// Do some work
[self.tableView reloadData];
}
# pragma mark - reloadStuff
- (void)reloadStuff {
[_stuffPresenter loadStuff];
}
# pragma mark - TableView methods here
// TableView methods...
#end
Profile
#interface ProfileViewController : UITableViewController <Interactor>
- (void)initPresenter;
#end
#implementation {
#private
StuffPresenter *_stuffPresenter;
}
- (void)viewDidLoad {
[super viewDidLoad];
[self initPresenter];
}
# pragma mark - init
- (void)initPresenter {
_stuffPresenter = [[StuffPresenter alloc] initWithInteractor:self];
[_stuffPresenter loadStuff];
}
# pragma mark - Interactor
- (void)initStuffInTableView:(NSMutableArray *)stuff {
// Do some work
[self.tableView reloadData];
}
# pragma mark - TableView methods here
// TableView methods...
#end
Problem:
When I go to Profile the app crashes, because initStuffInTableView is being called in Home. Why is this the case?
A protocol is an Objective-C language feature for specifying that a Class (or another protocol) has certain features, for the benefit of the compiler/ARC/the programmer.
A delegate, or delegation, is a design pattern which makes Model View Controller easier. To make the object doing the delegation be more flexible, generally its delegate adopts a protocol.
There are a number of issues in your code:
Your Presenter has a reference cycle with its interactors
You need to call some init method that eventually calls [super init] in your Presenter's initWithInteractor: method.
As others have pointed out, your methods which begin with init violate Objective-C conventions.
It's hard to tell from what you've posted exactly what your problem is, but I'm very suspicious of how it's structured.
You have a single class (Presenter), which you make two instances of, and pass no parameters other than the Interactor.
How could each instance know to load different "stuff" based on which View controller it received as a parameter?
I still want to understand thoroughly what is going on here, but I did solve the problem by doing the following.
In both Home and Profile, I init the StuffPresenter here:
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animation];
[self initPresenter];
}
And when exiting a controllerview I do the following:
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
_stuffPresenter = nil;
}
Apologies for this really basic question, but in my books and online tutorials I cannot find an example of how to subclass a custom view controller - what exactly has to be written down and what we can get from inheritance. Based on experience in other languages, I thought I'd get everything from the parent class in my subclassed UIViewController without having to re-code it. I thought I could just modify the functions I wanted to modify, but this appears not to be the case. Description of what I tried below:
Already I have a customer UIViewController called SignupViewController. Now I want to add a view where the user can update her info, and I realized it would basically be a remake of SignupViewController except with a few UITextFields hidden and all UITextFields prepopulated with existing information. I thought I could subclass the way I would subclass a normal UIViewController, but I find that when I try to override methods in SignupViewController in the new subclass of SignupViewController called UpdateViewController, I am told that the properties are not found. It almost feels as though I am working with a blank file and that the properties of SignupViewcontroller have not been referenced. For example, I'm trying to cull back the fields so I'm modifying this code in UpdateViewController:
- (BOOL)textFieldShouldReturn:(UITextField *)textField {
CGFloat lineHeight = .05*_height;
if (textField == self.firstName) {
[textField resignFirstResponder];
[self.lastName becomeFirstResponder];
} else if (textField == self.lastName) {
[textField resignFirstResponder];
[self.emailAddress becomeFirstResponder];
[self.scrollView setContentOffset: CGPointMake(0, lineHeight)animated:YES];
} ...etc...
}
return YES;
}
However I am getting errors that UpdateViewController (inheriting from SignupViewController) has no scrollView, emailAddress, firstName, lastName, etc property. Do I have to redeclare all these properties/write everything out? If so, what does subclassing really mean?
Here's what the .h and .m files of the subclass of the SignupViewController look like:
.h
#import "SignupViewController.h"
#interface UpdateViewController : SignupViewController
#end
.m
#import "UpdateViewController.h"
#interface UpdateViewController ()
#end
#implementation UpdateViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (BOOL)textFieldShouldReturn:(UITextField *)textField {
CGFloat lineHeight = .05*_height;
if (textField == self.firstName) {
[textField resignFirstResponder];
[self.lastName becomeFirstResponder];
} else if (textField == self.lastName) {
[textField resignFirstResponder];
[self.emailAddress becomeFirstResponder];
[self.scrollView setContentOffset: CGPointMake(0, lineHeight)animated:YES];
} ...
return YES;
}
#end
If someone can point me to a resource that spells this all out I'd really appreciate it. I'm sure it's in the docs, but I have not been able to find it.
I am getting errors that UpdateViewController (inheriting from SignupViewController) has no scrollView, emailAddress, firstName, lastName, etc property. Do I have to redeclare all these properties/write everything out?
If the properties are declared in SignupViewController.m, then they will be invisible to all code outside SignupViewController.m. This is intentional and he most common way to declare properties.
If you want them to be public, you must declare the properties in SignupViewController.h, and remove them from the .m file.
Anything in a .m file is only available from within that file, if you want it available elsewhere you must put it in a .h file and import the .h file. It's not a common programming language feature today but in older programming languages (and Obj-C is aproaching 30 years) that is generally how all of them work.
I've a TTTableViewController which follows TTTableViewController -> TTDataSource -> TTModel pattern. I've TTTableMoreButton and my list goes on to load more items when the user clicks on it.
How can I change the behaviour of this TTTableMoreButton? When the user came to the end of the list, I want it to behave as if it is clicked. In Facebook app, there is an implementation like this. I hope I could tell what I want.
Here is how to do it.
full disclosure: It is my code blog.
Here I've my own approach which i found out just before coneybeare's answer. I simply subclassed TTTableMoreButton and TTTableMoreButtonCell classes and in the "- (void)layoutSubviews" method, I detect that "Load More" button is appearing, and it should start loading more data if it is not already doing it.
I'm not sure which approach (coneybeaare's or mine) is the best and I'm looking forward for the comments about it.
AutoMoreTableItem.h
#interface AutoMoreTableItem : TTTableMoreButton {
}
#end
AutoMoreTableItem.m
#import "AutoMoreTableItem.h"
#implementation AutoMoreTableItem
#end
AutoMoreTableItemCell.h
#interface AutoMoreTableItemCell : TTTableMoreButtonCell {
}
#end
AutoMoreTableItemCell.m
#import "AutoMoreTableItemCell.h"
#import "AutoMoreTableItem.h"
#implementation AutoMoreTableItemCell
- (void)setObject:(id)object {
if (_item != object) {
[super setObject:object];
AutoMoreTableItem* item = object;
self.animating = item.isLoading;
self.textLabel.textColor = TTSTYLEVAR(moreLinkTextColor);
self.selectionStyle = TTSTYLEVAR(tableSelectionStyle);
self.accessoryType = UITableViewCellAccessoryNone;
}
}
- (void)layoutSubviews {
[super layoutSubviews];
AutoMoreTableItem* moreLink = self.object;
if(moreLink.isLoading ==YES) {
return;
}
if (moreLink.model) {
moreLink.isLoading = YES;
self.animating = YES;
[moreLink.model load:TTURLRequestCachePolicyDefault more:YES];
}
}
#end
And of course, in the datasource implementation:
- (Class)tableView:(UITableView*)tableView cellClassForObject:(id) object {
if([object isKindOfClass:[AutoMoreTableItem class]]){
return [AutoMoreTableItemCell class];
} else {
return [super tableView:tableView cellClassForObject:object];
}
}