I am trying to learn Objective-C for iOS. I have tried researching this, but I must not be using the right keywords.
I have several labels that are simply named, Label1, Label2, etc. I also have a ton of code that basically looks the same except the Label# changes. Can I build one method and pass the number to it and shrink my app significantly?
You probably want to have those object in an NSArray or another type of collection. Then you will be able to loop through the content and do the same operation on each element.
Consider defining a new class, CisonLabel, which abstracts out the shared behavior of these labels. The CisonLabel holds the control, and also its associated data. So you'd say
CisonLabel *label1=[CisonLabel for: self.labelControl1 withIdentifier: 1];
CisonLabel *label2=[CisonLabel for: self.labelControl2 withIdentifier: 2];
The CisonLabel would have methods like:
- (void) update; // sets the label text, based on the identifier
As DRiis suggests, you can collect all your CisonLabels in a collectionm perhaps an NSArray.
- (void) updateLabels: (NSArray*) theLabels
{
for(CisonLabel *label in theLabels) [label update];
}
Your instinct is sound: abstract out shared behavior into a class, and avoid repeating yourself.
Related
I read somewhere recently that if you have an exceptionally large view controller class, you might consider splitting it up into multiple categories based on the work being performed. I'm attempting to do that, but I can't for the life of me figure out how to do it properly. And, on top of that, I can't find any online sources on the practice.
So I have this view controller that has 5+ UIViews laid out in a UIScrollView. I'm trying to create a private category for each UIView and put any work for those views within the category, i.e. any programmatic visual work, button presses, etc. However, I need a reference to the parent object to change the visual values because all labels and buttons are defined on the parent object. I can't define them within the category, Xcode won't let me. It will only let me define button presses. I've already tried sending a weak copy of self to the category upon initialization, but using it didn't change the visual values of any of the labels or anything.
It's pretty imperative that I have access to other objects within the parent. If I don't, this is kind of a lost cause. So, is it a lost cause? or am i doing it wrong?
within main class
__weak __typeof(self)weakSelf = self;
[_diaryViewController setupDietDiaryViewForController:weakSelf];
within category
- (void)setupDietDiaryViewForController:(PADashboardViewController *)mainDashboard {
mainDashboard.chooseHungerView.backgroundColor = [UIColor colorWithRed:0 green:0 blue:0 alpha:0.4f];
mainDashboard.chooseHungerView.layer.borderWidth = 0.4f;
mainDashboard.chooseHungerView.layer.borderColor = [UIColor whiteColor].CGColor;
A category is nothing more than an extension of the base class. In essence, you could place any code within a category at the bottom of the main file and it would work exactly the same. Commonly, programmers prefer to split functionality up into categories in an attempt to make the code more readable.
Since it's nothing more than an extension of the main functionality, there is no need to create a reference to the parent class. If you want to make variables available to the category, all you need to do is place the variables in the header file of the main file. When you call a category function, you'll want to do it as if the function was within the base file itself.
[self setupProgressView];
Make sure the function setupProgressView is not static (it should be defined with a - instead of a +).
and within the category itself, you access variables like so:
[self.chooseHungerButton changeTitle:#"New Title"];
[self.deviationButton changeTitle:#"New Title"];
If you're trying to shift IB functionality over to a category, you'll need to define the buttons/views/objects within the base file's header. After defining the variables, you can access them from the category. If you want to create a method call for the button, you can control-drag the button straight into the category's .m file.
Make sure you include the category's .h file in the base file before attempting to use any functions within.
I have 4 ivars:
UIView *view1;
UIView *view2;
UIView *view3;
UIView *view4;
I would like to be able to alloc and init them in a dynamic way, instead of doing:
view1 = [[MyView1 alloc] initWithFrame:....
view3 = [[MyView2 alloc] initWithFrame:....
view4 = [[MyView3 alloc] initWithFrame:....
view4 = [[MyView4 alloc] initWithFrame:....
So, I tried to use a array and store the names of these ivars in it:
[array addObject:#"view1"];
[array addObject:#"view2"];
[array addObject:#"view3"];
[array addObject:#"view4"];
And so that within a loop I would do:
[self valueForKey:[array objectAtIndex:x]] = [[[[self valueForKey:[array objectAtIndex:x]] class] alloc] initWithFrame:(CGRect){.....
The above generates the error:
Expression is not assignable.
Hope someone can tell me why the above cannot be done.
My question is:
I have a feeling that this is not a clever way of doing things.
Right now I have only 4 views, but who knows if in the future I might have more.
So, my idea is that instead of hard-coding things, I would like to find a more dynamic way of accomplishing this.
My idea is that at compile-time, all this views are just of UIViews.
And only until run-time would I resolve these views to the individual class types
(i.e. MyView1, MyView2 etc etc) and alloc and init them and assign them
accordingly to the ivars (i.e. view1, view2, view3, etc) within my class.
The reason why I use an array is because, if in the future I added another view called view5 of class type MyView5, I could loop the alloc and init process using [array count]. If this way of doing it is still not optimal, please correct me.
To sum up, I would like to set up my controller in a way that it only knows during compile-time that these objects are just of class type UIView. Only until run-time would I resolve them individually to MyView1, MyView2(subclass of UIView) etc and assign them to the ivars within my controller (again, they are named view1, view2 etc).
And if I added another view in the future, I wouldn't have to look all over the place within this controller and hard-code: view5 = [[MyView5 alloc] init....
Can someone show me how to accomplish this optimally (future-wise) and dynamically?
EDIT:
It just occurred to me: it would be even better if I could create these ivars only during runtime, so that in the future everything could be created dynamically.
If I understand what you're asking, let me provide a different approach which you might like:
// Set up a mutable array of objects
NSMutableArray *views = [[NSMutableArray alloc] init];
// Set up an array of strings representing the classes - you can add more
// later, or use -stringWithFormat: to make the class names variable
NSArray *classes = #[#"MyView1", #"MyView2", #"MyView3", #"MyView4"];
// Now loop through it and instantiate one of each kind
for (NSString *className in classes)
[views addObject:[[NSClassFromString(className) alloc] initWithFrame:CGRectZero]];
Remember to be careful with NSClassFromString, as you might accidentally send the -initWithFrame: message to a type that doesn't implement that.
Hope this helps!
Edit: I see that I have given you an overly implementation-based answer, while you seem to be looking for the program design aspect.
When you're designing your controller class, it's good that you're considering how you will use the class in the future. That said, you need to have a specific idea of how abstract you want the class to be. In other words, don't go trying to make the controller class completely decoupled, because at some point your class will be a bulk of useless management code.
So how do you go about writing a class that is both decoupled and functional at the same time? I suggest you look for examples in Apple's classes. Here are a few:
UIViewController, probably the most important and versatile class on iOS. They designed it to be easily subclassable, yet there are also many premade subclasses like the navigation controller and table view controller varieties.
UIDocument, a template for all document model objects you will ever need. The system implementation handles all the nitty-gritty of iCloud sync, file management, etc., while not knowing anything about the document contents itself. The user subclass, however, provides the necessary information in an NSData object.
UIGestureRecognizer, the foundation of the touch-based UI. Most people only use the system-provided tap/swipe/pinch subclasses, but the abstract superclass itself (whether subclassed or not) detects any gesture you want and sends the necessary messages. Again, the gesture recognizer doesn't know what views you attach it to, yet it still performs its job.
Do you see what I'm getting at here? Apple's classes illustrate that there are ways to provide the necessary functionality while staying abstract, but without going into runtime acrobatics. As one of the commenters suggested, all you really need is an array of view objects. Instead of having your controller class instantiate the views, maybe you should have your client objects do that. It's all about finding a balance between abstraction and functionality.
The problem is, that you're thinking about this, as if your calls to the array elements would replace your code before compiling (like macros would do). It just doesn't work that way. For example:
[self valueForKey:[array objectAtIndex:x]] = ...
The compiler sees [self valueForKey:[array objectAtIndex:x]] as a value and "thinks" hey you can't assing sth to a value.
Try the following (i've splitted it into multiple statements for better readability and also to make the code more self-explanatory):
string className = [array objectAtIndex:x];
Class classToInit = NSClassFromString(className);
UIView *viewToInit = [[classToInit alloc] initWithFrame:...];
[self setValue:viewToInit forKey:className];
Keep in mind that with this approach the property names (and btw. they also need to be properties not ivars for KVC to work) must match your class names, i.e. if your class is named MyView1 your property must also be called MyView1. You might not want this (in fact according to you description in the text, you don't). So to make it work you could create a dictionary mapping your property names to your class names and enumerate over it's keys:
NSMutableDictionary classNameMapping = [NSMutableDictionary new];
[classNameMapping setObject:#"MyView1" forKey:#"view1"];
[classNameMapping setObject:#"MyView2" forKey:#"view2"];
//...
foreach (string propertyName in [classNameMapping allKeys])
{
string className = [classNameMapping objectForKey:propertyName];
Class classToInit = NSClassFromString(className);
UIView *viewToInit = [[classToInit alloc] initWithFrame:...];
[self setValue:viewToInit forKey:propertyName];
}
What's the difference between declaring a UIButton in Xcode like this:
- (IBAction)testButton;
and declaring a button like this:
- (IBAction)testButton:(id)sender;
I understand that in the .m file you would then implement the buttons accordingly, as shown below:
- (IBAction)testButton
{
// insert code here..
}
and setting it up like this:
- (IBAction)testButton:(id)sender
{
// insert code here..
}
Is there any additional things you can do by declaring the button with :(id)sender, is there some additional stability, or is there no difference?
With :(id)sender you are able to access the button itself through the sender variable. This is handy in many situations. For example, you can have many buttons and give each a tag. Then use the [sender tag] method to find which button was tapped if many buttons are using this IBAction.
- (IBAction)someMethod:(id)sender {
// do stuff
}
Using (id)sender, you have a reference to who sent the method call. Please note, this doesn't have to be limited to a UIButton.
If you're created this method via control-dragging from the storyboard an only hooking up a single button, then sender is basically useless (it will always be the same), and should probably be marked as unused:
#pragma unused (sender)
(The compiler can better optimize your code if you do this.)
However, there's nothing wrong with hooking up several UI elements to the same IBAction method. You can then distinguish the sender via:
[sender tag]
...which returns an int that was either set via the storyboard or programmatically.
Moreover, you can call this method elsewhere in your class. You can either pass nil as the sender, or you can pass it a particular UI element in order to force it into the results you've coded for objects of that tag.
Nonetheless, if you plan to call the method with a nil argument, you can always throw:
if(!sender)
... into the method in order to handle special logic for when the method has been invoked programmatically as opposed to via user interaction.
It allows you to know which button you are working with. I have posted a simple example for a card game below
- (IBAction)flipCard:(id)sender {
[self.game flipCardAtIndex:[self.cardButtons indexOfObject:sender]];
self.flipCount++;
[self updateUI];
}
This method is used for a card flipping game. There are multiple buttons on the screen representing different cards. When you hit the button, a card in the model must be flipped. We know which one by finding the index of the variable sender
Consider an Core Data entity with two properties: text and -for the sake of simplicity- textLength. textLength is a property that is calculated every time text changes. I have three requirements:
Update textLength every time text changes, ideally inside the NSManagedObject subclass.
textLength cannot be calculated on demand for performance reasons (the actual calculated property that I'm using is much more expensive to calculate).
Update the UI every time text changes.
My solution is almost there. I'm providing a custom accessor for setText:, like this:
- (void)setText:(NSString *)text
{
static NSString *key;
if (!key) key = NSStringFromSelector(#selector(text));
[self willChangeValueForKey:key];
[self setPrimitiveText:text];
self.textCount = text.count;
[self didChangeValueForKey:key];
}
And using KVO in the UI to observer text changes:
[someObject addObserver:self forKeyPath:NSStringFromSelector(#selector(text)) options:NSKeyValueObservingOptionNew context:someContext];
This works fine in most cases, except when I perform undo. I take that Core Data calls setPrimiteValue:forKey: directly, and this does not trigger my calculation logic. As a consequence, when the UI is notified of the change, the textLength value is outdated.
Short of calculating textLength on demand, where should the calculation logic be?
This is the purpose of the - (void)awakeFromSnapshotEvents:(NSSnapshotEventType)flags method. It tells you the reason for the snap shot change and allows you to update computed / derived data.
(when you update the derived value you should set it using the appropriate primitive method)
I need to pass a parameter to IBAction (but it has only sender(id) - UIButton in my case), so I'm wondering if it possible to convert description of some object to an object. Right now I'm passing parameter as button's [titleLabel text]:
[[button titleLabel]setText:[someObject description]];
And in IBAction I'm getting description:
- (IBAction)AddToCalendarEvent:(id)sender {
NSString * description = [[sender titleLabel]text];
NSLog(#"description is %#", desc);
}
And now I want to convert this description to an object. Is it possible?
UPD
I'm dynamically filling table view with cells. Each cell has four buttons and I want these buttons to keep some object as parameter to pass to IBAction.
I think the best solution for you, based on what I've read and currently understand about your problem, is to maintain an array (or some other appropriate data structure) of your buttons on your View Controller. Then, in your action method that each button calls when it is tapped, you can search your array of buttons for the sender of your action method. Then, once you've figured out which button has been tapped, you can use that to then find whatever data you're looking for in your data model. You should apply this solution to your situation and it will probably end up looking a bit different, but the basic idea is sound. You should avoid maintaining state in your view (in this case your buttons) and it looks like you're approaching that from your description of the problem.
You definitely don't won't to convert in this way.
A simple option is to set the tag of each UIButton to a unique integer, and store an array of the objects you need to look up:
- (IBAction)AddToCalendarEvent:(id)sender {
NSInteger senderTag = [sender tag];
NSLog(#"Sender index = %d", senderTag);
// Use this tag as an index to the array.
}
A more complicated route is to subclass UIButton (usually not recommended), to store the associated data with each UIButton. Then you can look it up, once again from the sender.