Categories best practices - ios

I am wrapping my head around using categories for some things where I might previously have been using inheritance.
The thing I am doing now is more of a best-practice question where I am not sure how it should be implemented. I am writing a category on UIActivityIndicatorView which basically will be used to put a activity indicator in an arbitrary view. Below you'll find the code example of how I am doing it and my main question is whether or not this is good. And I would appreciate comments on why it's bad if that is the case. Thanks.
The category:
#interface UIActivityIndicatorView (Customizations)
- (UIActivityIndicatorView *) inView:(UIView *) target;
#end
Implementation:
#implementation UIActivityIndicatorView (Customizations)
- (UIActivityIndicatorView *) inView:(UIView *) target {
[self startAnimating];
[self setHidden:NO];
self.frame = target.bounds;
self.backgroundColor = [UIColor colorWithRed:0.0f green:0.0f blue:0.0f alpha:0.6f];
return self;
}
#end
Then I would use it like this:
[background addSubview:[loader inView:background]];
I am guessing that another way of doing it would be to make an initialization function to take the container view and just return the "styled" view, or perhaps not return anything (void) and just let the method do the styling.
So I am looking for some guidance as to how to handle this.

What kind of worries me is that I am actually making a second copy of the UIActivityIndicatorView which seem unnecessary
No, you don't. You might be confused by the fact that you are returning self from your category method, but that is just a pointer, not the object itself getting copied.
However, I would implement it slightly different:
- (void) addToSuperView:(UIView *) target {
[self startAnimating];
[self setHidden:NO];
self.frame = target.bounds;
self.backgroundColor = [UIColor colorWithRed:0.0f green:0.0f blue:0.0f alpha:0.6f];
[target addSubview:self];
}
That way, you don't need to make an extra, unnecessary call when adding it:
[loader addToSuperView:background];

Related

Appcelerator IOS Module Development

I am pretty stuck trying to create a IOS module for Titanium/Appc i am trying to intergrate https://github.com/antiguab/BAFluidView so i can use it in titanium.
I have followed the module tutorials have it working fine with just the standard view but when i try to add BAFluidView it doesnt work.
I have included the classes in xcode and have the code below.
#import "ComExampleFluidView.h"
#import "TiUtils.h"
#import "BAFluidView.h"
#import "UIColor+ColorWithHex.h"
#implementation ComExampleFluidView
- (void)initializeState
{
// Creates and keeps a reference to the view upon initialization
square = [[UIView alloc] initWithFrame:[self frame]];
BAFluidView *view = [[BAFluidView alloc] initWithFrame:view.frame];
[view fillTo:#1.0];
view.fillColor = [UIColor colorWithHex:0x397ebe];
[view startAnimation];
[square addSubview:view];
[self addSubview:square];
[super initializeState];
}
-(void)dealloc
{
// Deallocates the view
RELEASE_TO_NIL(square);
[super dealloc];
}
-(void)frameSizeChanged:(CGRect)frame bounds:(CGRect)bounds
{
// Sets the size and position of the view
[TiUtils setView:square positionRect:bounds];
}
-(void)setColor_:(id)color
{
// Assigns the view's background color
square.backgroundColor = [[TiUtils colorValue:color] _color];
}
#end
header file is
#import "TiUIView.h"
#interface ComExampleFluidView: TiUIView {
UIView *square;
}
#end
Can anyone give some suggestions on this?
since you are trying to bridge a native view, you need some layout helpers that are required to handle the Titanium-layout-system properly. Please check modules like ti.googlemaps, especially the initialization of views. In addition, your custom setters like setColor need to apply the color to your BAFluidView, not to your UIView, so you need to keep a reference of that inside your header. I guess the ti.googlemaps example should explain all concepts you are looking for. Good luck!

Confused by behavior of property setter

(this question probably needs a more descriptive title, feel free to improve it)
I have a UIView subclass with a property:
#property (weak, nonatomic) UILabel *label;
In an initialize method I have:
[self addSubview: (self.label = [UILabel new])];
I like the terseness of this, but I have questions how it works.
For one, I get a warning:
Assigning retained object to weak property; object will be released after assignment
Warning aside, it actually seems to work. Is that because before the release machinery can run, the addSubview: re-retains it?
If I understand correctly, the self.label = ... code is just sugar for a [self setLabel: ...]. But if I override the property access with my own setLabel: implementation, its signature will be
- (void) setLabel: (UILabel*) label;
So the return value is void. But it's being fed into the the addSubview: send and working? So how does that work?
Update
One of the things that frustrates doing a one liner here, is that Objective-C tend to eschew returning useful information from methods like addSubview: which is divergent from the Smalltalk influence which spawned Objective-C in the first place. In Smalltalk, it would be expected/common for the addSubview: method to return the added object. If this was the case, one could write these expressions as:
self.label = [self addSubview: [UILabel new]];
This would allow the strong/weak semantics to be happy. The addSubview: would do the strong retain before the it got to the weak label setter.
I fight this problem with both subviews and recognizers, so I thought I'd be clever, and write a category to do something like that (I inverted the receiver/argument so it would be easily distinguishable from an alternate addSubview: signature).
#interface UIView (UIView_Adding)
- (UIView*) addedToView: (UIView*) superview;
#end
#interface UIGestureRecognizer (UIView_Adding)
- (UIGestureRecognizer*) addedToView: (UIView*) view;
#end
#implementation UIView (UIView_Adding)
- (UIView*) addedToView: (UIView*) superview {
[superview addSubview: self];
return self;
}
#end
#implementation UIGestureRecognizer (UIView_Adding)
- (UIGestureRecognizer*) addedToView: (UIView*) view {
[view addGestureRecognizer: self];
return self;
}
#end
Unfortunately, this just invokes the Law of Conservation of Ugly. I get rid of one kind of warning and get another. With that category included, I can now write:
self.label = [[UILabel new] addedToView: self];
But this generates a warning that self.label is meant to hold a specific subclass of UIView, namely a UILabel and the return type of addedToView: is `UIView'. I'm not aware of an Objective-C pseudo type which means be the right darn subtype of this known super type to make property types happy. :(
Final Update
I discovered the instancetype type. By changing my category method signatures to return these types, everything just works. I'm new enough to instancetype to not know if I'm abusing something here, but I'm thrilled it worked.
The warning comes from:
self.label = [UILabel new]
since your label property is weak.
You are correct that it ends up working because the label is retained by the call to addSubview:.
But if the label is ever removed from its superview, the label property will become nil and the label will be lost. So if there is any chance that the label will be removed but you want to keep a reference to the label (maybe to add it back later), then change your property to be strong.
You are also correct that self.label = is really just [self setLabel:]. The reason that this can be passed to addSubview: is due to the fact that an expression such as x = y has a value equal to the assignment.
So:
[self addSubview: (self.label = [UILabel new])];
if equivalent to:
UIView *view = (self.label = [UILabel new]);
[self addSubview:view];

iOS >> Why isn't it Possible to Set UIControls Properties Directly in Code When Approaching Them via "sender"?

Let's say that I wish to approach a certain UIControl in a certain method that it triggers - I can send the method a pointer to the UIControl using "sender". but then, for some reason, I cannot approach sender's properties directly and have to use the setX:forState: methods. If I approach the properties directly, I get no error or warning; it simply does nothing...
Here's an example:
h. file...
#interface MYViewController : UIViewController
#property (weak, nonatomic) IBOutlet UIButton *dButton; //connected to a UIButton in IB
-(IBAction)dButtonClick:(UIButton*)sender; //connected to the same UIButton in IB
#end
then...
m. file...
- (void)viewDidLoad
{
[super viewDidLoad];
self.dButton.titleLabel.textColor = [UIColor greenColor]; //this is working...
}
-(IBAction)dButtonClick:(UIButton*)sender
{
sender.titleLabel.textColor = [UIColor redColor]; //this is not working...
self.dButton.titleLabel.textColor = [UIColor redColor]; //this is also not working...
[sender setTitleColor:[UIColor redColor] forState:UIControlStateNormal]; //only this is working.
//But why?!?!?
}
I tried to search for some info about the way these items work, but couldn't find anything that explains the logic behind it clear enough.
Any help would be... well... helpful...
This:
sender.titleLabel.textColor
And this:
[sender setTitleColor:[UIColor redColor] forState:UIControlStateNormal];
Are different not just because of the dot notation but also because of the state. The button has different colours for each state so any colour set directly will be overridden by the colour for the state.
This line:
self.dButton.titleLabel.textColor = [UIColor redColor];
Could be the same issue or you may just not have connected the outlet.

Animate UIView on touchesEnded event

I am trying to animate a UIView (block-based animation) but I cannot make the timing work (duration, etc.). The animation runs, because the changes are done, and the completition output message is shown, but it just ignores the duration and instantly makes the changes.
I asume there is something wrong with the way I am doing things, but I just cannot figure out where the mistake is. Let me explain what I am trying to do:
I have a UIViewController (viewcontroller) that handles two objects (view1 and view2) of a UIView sublcass in which I have defined the touchesEnded method.
The idea is that when view1 has been touched, I want to animate view2. So what I did was to implement an animation method in the UIView subclass, a notification in the touchesEnded method (also in the UIView subclass), and
a trigger method in the controller that would call the animation of the second view. Something like this:
// In the UIView subclass:
- (void) myAnimation{
[UIView animateWithDuration:2.0
delay:0.0
options: UIViewAnimationCurveEaseOut
animations:^{
self.alpha = 0.5;
}
completion:^(BOOL finished){
NSLog(#"Done!");
}];
}
// In the UIView subclass:
- (void) touchesEnded: (NSSet*) touches withEvent: (UIEvent*) event {
[pParent viewHasBeenTouched]; // pParent is a reference to the controller
}
// In the UIViewController:
- (void) viewHasBeenTouched {
[view2 myAnimation];
}
(The animation and work flow is in fact a bit more complex, but this simple example just fails to work)
If I place the animation somewhere else, it may work, for example just after initializing the views in the init method of the controller. But if I try to do this forward-backward calls, the animation will just ignore the duration and execute in one step.
Any ideas? What have I missed about touches and gestures that I should know? Thank you.
NEW INFORMATION ADDED TO THE ORIGINAL POST:
The AppDelegate does nothing but this:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions{
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
// Override point for customization after application launch.
self.viewController = [[TestViewController alloc] init];
self.window.rootViewController = self.viewController;
self.window.backgroundColor = [UIColor whiteColor];
[self.window makeKeyAndVisible];
return YES;
}
The TestViewControler.h is just this:
#import <UIKit/UIKit.h>
#import "TestView.h"
#interface TestViewController : UIViewController{
TestView* myView;
}
- (void) viewHasBeenTouched;
#end
and the viewDidLoad only does this:
- (void)viewDidLoad{
[super viewDidLoad];
myView = [[TestView alloc] initWithParent:self];
[self.view addSubview:myView];
}
Finally, the UIView has a TestView.h which looks like this:
#import <UIKit/UIKit.h>
#class TestViewController; // Forward declaration
#interface TestView : UIView{
TestViewController* pParent;
}
- (id)initWithParent:(TestViewController*) parent;
- (void) myAnimation;
#end
And the init method used is:
- (id)initWithParent:(TestViewController *)parent{
CGRect frame = CGRectMake(50, 20, 100, 200);
self = [super initWithFrame:frame];
if (self) pParent = parent;
self.backgroundColor = [UIColor blueColor];
return self;
}
So... with this simple code I posted, the error occurs. As I said, the animation does change the alpha, but with no delay or timing. It's just instantaneous. Any ideas with this added information?
Thank you again for the help.
I found the problem. I want to apologize first for all the people how have studied my problem and have lost valuable time on it.
All the second part I posted in the original question was copy and pasted indeed, it was the first part that had those two mismatching errors because they came from a more complex code. And that code works without problem as you have all stated. The problem is that I did not copy and paste a method that I thought I had removed from the project (and which is called at the start of the application):
- (BOOL)shouldAutorotateToInterfaceOrientation (UIInterfaceOrientation)interfaceOrientation {
// ... More code
[UIView setAnimationsEnabled:NO];
// ... More code
return YES;
}
It obviously needs no further explanation.
Again thank you very much for your time, and my sincere apologizes.

UITableView headings shown on top of MBProgressHUD

So I have a subclass of UITableViewController that loads some data from the internet and uses MBProgressHUD during the loading process. I use the standard MBProgressHUD initialization.
HUD = [[MBProgressHUD alloc] initWithView:self.view];
[self.view addSubview:HUD];
HUD.delegate = self;
HUD.labelText = #"Loading";
[HUD show:YES];
This is the result:
.
Is there any way to resolve this issue, or should I just abandon MBProgressHUD?
Thanks!
My solution was pretty simple. Instead of using self's view, I used self's navigationController's view.
HUD = [[MBProgressHUD alloc] initWithView:self.navigationController.view];
[self.navigationController.view addSubview:HUD];
This should work for the OP because his picture shows he's using a UINavigationController. If you don't have a UINavigationController, you might add another view on top of your UITableView, and add the HUD to that. You'll have to write a little extra code to hide/show this extra view.
An unfortunate thing with this simple solution (not counting my idea adding another view mentioned above) means the user can't use the navigation controls while the HUD is showing. For my app, it's not a problem. But if you have a long running operation and the user might want to press Cancel, this will not be a good solution.
It's probably because self.view is a UITableView, which may dynamically add/remove subviews including the headers, which could end up on top of the HUD after you add it as a subview. You should either add the HUD directly to the window, or (for a little more work but perhaps a better result) you could implement a UIViewController subclass which has a plain view containing both the table view and the HUD view. That way you could put the HUD completely on top of the table view.
My solution was:
self.appDelegate = (kmAppDelegate *)[[UIApplication sharedApplication] delegate];
.
.
_progressHUD = [[MBProgressHUD alloc] initWithView:self.appDelegate.window];
.
[self.appDelegate.window addSubview:_progressHUD];
Works like a charm for all scenarios involving the UITableViewController. I hope this helps someone else. Happy Programming :)
Create a category on UITableView that will take your MBProgressHUD and bring it to the front, by doing so it will always appear "on top" and let the user use other controls in your app like a back button if the action is taking to long (for example)
#import "UITableView+MBProgressView.h"
#implementation UITableView (MBProgressView)
- (void)didAddSubview:(UIView *)subview{
for (UIView *view in self.subviews){
if([view isKindOfClass:[MBProgressHUD class]]){
[self bringSubviewToFront:view];
break;
}
}
}
#end
A simple fix would be to give the z-index of the HUD view a large value, ensuring it is placed in front of all the other subviews.
Check out this answer for information on how to edit a UIView's z-index: https://stackoverflow.com/a/4631895/1766720.
I've stepped into a similar problem a few minutes ago and was able to solve it after being pointed to the right direction in a different (and IMHO more elegant) way:
Add the following line at the beginning of your UITableViewController subclass implementation:
#synthesize tableView;
Add the following code to the beginning of your init method of your UITableViewController subclass, like initWithNibName:bundle: (the beginning of viewDidLoad might work as well, although I recommend an init method):
if (!tableView &&
[self.view isKindOfClass:[UITableView class]]) {
tableView = (UITableView *)self.view;
}
self.view = [[UIView alloc] initWithFrame:[UIScreen mainScreen].applicationFrame];
self.tableView.frame = self.view.bounds;
self.tableView.contentInset = UIEdgeInsetsMake(0.0, 0.0, 0.0, 0.0);
[self.view addSubview:self.tableView];
Then you don't need to change your code you posted in your question any more. What the above code does is basically seperating the self.tableView from self.view (which was a reference to the same object as self.tableView before, but now is a UIView containing the table view as one might expect).
I've Just solved that issue manually , it has been 2 years since Chris Ballinger asked but maybe someone get used of what is going on here.
In UITableViewController i execute an HTTP method in viewDidLoad , which is running in background so the table view is loaded while the progress is shown causing that miss.
i added a false flag which is changed to yes in viewDidLoad, And in viewDidAppear something like that can solve that problem.
-(void) viewDidAppear:(BOOL)animated{
if (flag) {
[self requestSomeData];
}
flag = YES;
[super viewDidAppear:animated];
}
I had the same problem and decided to solve this by changing my UITableViewController to a plain UIViewController that has a UITableView as a subview (similar to what jtbandes proposed as an alternative approach in his accepted answer). The advantage of this solution is that the UI of the navigation controller isn't blocked, i.e. users can simply leave the ViewController in case they don't want to waiting any longer for your timely operation to finish.
You need to do the following changes:
Header file:
#interface YourViewController : UIViewController <UITableViewDelegate, UITableViewDataSource>
- (id)initWithStyle:(UITableViewStyle)style;
#end
Implementation file:
#interface YourViewController ()
#property (nonatomic, retain) UITableView *tableView;
#property (nonatomic, retain) MBProgressHUD *hud;
#end
#implementation YourViewController
#pragma mark -
#pragma mark Initialization & Memory Management
- (id)initWithStyle:(UITableViewStyle)style;
{
self = [super init];
if (self) {
// create and configure the table view
_tableView = [[UITableView alloc] initWithFrame:CGRectNull style:style];
_tableView.delegate = self;
_tableView.dataSource = self;
}
return self;
}
- (void)dealloc
{
self.tableView = nil;
self.hud = nil;
[super dealloc];
}
#pragma mark -
#pragma mark View lifecycle
- (void)loadView {
CGRect frame = [self boundsFittingAvailableScreenSpace];
self.view = [[[UIView alloc] initWithFrame:frame] autorelease];
// add UI elements
self.tableView.frame = self.view.bounds;
[self.view addSubview:self.tableView];
}
- (void)viewWillDisappear:(BOOL)animated
{
// optionally
[self cancelWhateverYouWereWaitingFor];
[self.hud hide:animated];
}
The method -(CGRect)boundsFittingAvailableScreenSpace is part of my UIViewController+FittingBounds category. You can find its implementation here: https://gist.github.com/Tafkadasoh/5206130.
In .h
#import "AppDelegate.h"
#interface ViewController : UITableViewController
{
MBProgressHUD *progressHUD;
ASAppDelegate *appDelegate;
}
In .m
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
appDelegate = (ASAppDelegate *) [UIApplication sharedApplication].delegate;
progressHUD = [MBProgressHUD showHUDAddedTo:appDelegate.window animated:YES];
progressHUD.labelText = #"Syncing To Sever";
[appDelegate.window addSubview:progressHUD];
This should work.
[MBProgressHUD showHUDAddedTo:self.navigationController.view animated:YES];
And to remove you can try
[MBProgressHUD hideHUDForView:self.navigationController.view animated:YES];

Resources