Dynamic UITableView with multiple custom UITableViewCells - objective-c

I've been working on this for a while, doing a lot of research, but haven't found a solution I was particularly happy with.
Here's the situation:
The tableview is a settings page of sorts, with dynamic content. For example, rows need to be added and removed when a switch changes state in one of the cells. To accomplish this, I chose the delegate pattern to notify of the cell's changes
The problem:
1) I'm not sure what object should be the proper "owner," and therefore delegate, of the custom cells. It seems to me that the uitableview should delegate for the cells, and in turn it delegates to the view controller.
2) Whichever object delegates for the custom cells, it has to "figure out" which property would need to be updated based on the call. This is a problem because of multiple cells of the same type being applied to different properties.
For example, say there are 2 sections, each with 1 switch cell. When one of the cells fires its delegate call to notify of the change in state, the view controller has to determine which part of the model to update. In this example, you could easily check which section the cell was in to update the model, however it wouldn't really solve the problem, because if you were to add a second switch cell to one of the sections in the future, it would break.
Note:
As you'll see in the code below, it is conceivable to utilize the indexPath to check the property being edited. However, it would result in either a ever growing if/elseif or switch statement checking which property corresponds to which indexPath.
Reason being: at least some properties are not pointers, so storing them in an array and editing them directly wouldn't affect the data, and would eventually need to be translated using literals to the actual data object.
Here's some of what I have to better illustrate:
#protocol CustomUITextFieldCellDelegate <NSObject>
- (void)cellDidBeginEditingTextField:(CustomUITextFieldCell *)cell;
- (void)cellDidEndEditingTextField:(CustomUITextFieldCell *)cell;
#end
#interface CustomUITextFieldCell : UITableViewCell <UITextFieldDelegate>
#property (nonatomic, strong) NSString *title;
#property (nonatomic, assign) id <CustomUITextFieldCellDelegate> delegate;
#end
'
#protocol CustomTableViewDelegate <UITableViewDelegate>
- (void)textFieldCell:(CustomUITextFieldCell *)cell didBeginEditingIndexPath:(NSIndexPath *)indexPath;
- (void)textFieldCell:(CustomUITextFieldCell *)cell didEndEditingIndexPath:(NSIndexPath *)indexPath;
#end
#interface CustomTableView : UITableView <CustomUITextFieldCellDelegate>
#property (nonatomic, assign) id <CustomTableViewDelegate> delegate;
#end
In the ViewController, delegate for the CustomUITableView:
- (void)textFieldCell:(TTD_UITextFieldCell *)cell didBeginEditingIndexPath:(NSIndexPath *)indexPath {
// determine which property is being edited
// update model
}
Thanks for any help in advance! I'm curious to see how you would tackle this problem.

If you know which property the value is editing when creating the cell, you can use blocks instead of the delegate pattern, like this:
// for text editing
typedef void (^TextCellSetValueBlock)(NSString *);
typedef NSString *(^TextCellGetValueBlock)();
#interface CustomUITextFieldCell: UITableViewCell {
// ...
#property (nonatomic, copy) TextCellSetValueBlock onSetValue;
#property (nonatomic, copy) TextCellGetValueBlock onGetValue;
// ...
}
When creating the cell, assign onSetValue/onGetValue to blocks that read/write the appropriate property from your model, and call onGetValue()/onSetValue() from your cell when you want to get/set the property.
For the boolean switch that turns on/off parts of your UI, you can have your onSetValue blocks update your model and also add/remove cells as a side effect.

Related

Using a button to create a custom class instance and show it in an NSTableView

Im stuck on this problem and it's driving me crazy. What I am attempting to do is have a button click create an instance of a custom class, set it's variables, add it to an NSMutableArray, and display it in a table view. So far it seems that I have everything working except having the info display in the table view.
My custom class TradePaperback just has three NSString properties: title, volume, and publisher.
Here is the code for my header and implementation files:
Header:
#import <Cocoa/Cocoa.h>
#interface AppDelegate : NSObject <NSApplicationDelegate>
#property (nonatomic, strong) NSMutableArray *tradeArray;
#property (weak) IBOutlet NSTableView *tableView;
- (IBAction)addTrade:(id)sender;
#end
implementation file:
#import "AppDelegate.h"
#import "tradePaperback.h"
#interface AppDelegate ()
#property (weak) IBOutlet NSWindow *window;
#end
#implementation AppDelegate
-(NSMutableArray *)tradeArray{
if (!_tradeArray){
_tradeArray = [[NSMutableArray alloc]init];
TradePaperback *avengers = [[TradePaperback alloc]init];
avengers.title = #"Avengers";
avengers.volume= #"volume 01";
avengers.publisher = #"Marvel Comics";
[_tradeArray addObject:avengers];}
return _tradeArray;}
- (IBAction)addTrade:(id)sender {
TradePaperback *newTrade = [[TradePaperback alloc]init];
newTrade.title = #"New Trade";
newTrade.volume = #"Volume Number";
newTrade.publisher = #"publisher";
[_tradeArray addObject:newTrade];
NSLog(#"added");
NSLog(#"number of items in array is %ld", _tradeArray.count);
[_tableView reloadData];}
#end
My table view is hooked up using bindings. It seems that everything is hooked up correctly since the avengers instance of TadePaperback that i put in shows up when I run the program. As I click the add button I can see from the log in the console that the array is having items added to it, but they just won't display.
Why would the tableView show the first item in the array, but none of the rest?
Here is a picture of the program after running and clicking the add button a couple of times.
program running
I would greatly appreciate any help or advice you guys could give. Thanks in advance.
-Jack
If you're using bindings to populate a table view, you're almost certainly using an NSArrayController to manage the table's content. If you want to add an object to the array controlled by this controller (your tradeArray object), you should do so indirectly: add to the array controller, which will in turn update your array. NSArrayController provides a number of add... or insert.. methods; decide which one suits you best, and use it to replace your call to [_tradeArray addObject:...].
The reason this approach works, and yours doesn't is because this engages the key-value coding/observing machinery that underpins bindings. Your approach essentially adds the object behind the array controller's back - it's not KVO/KVC-compliant, so the array controller remains unaware of the change. Cocoa does provide you with a way of editing the original array in a KVO/KVC-friendly manner if you so wish, it's just a little more work: try the code below, then read Apple's NSKeyValueCoding Protocol Reference and their Key-Value Coding Programming Guide for an explanation:
// Instead of [_tradeArray addObject:newTrade];
[[self mutableArrayValueForKey:#"_tradeArray"] addObject:newTrade];

How to declare a variable from user input?

I am fairly new to programming and am working with Objective-C in Xcode 5.
I'm presently making an OSX application in Xcode that uses Cramer's Rule (this matrix math method to calculate the intersecting point of three lines).
I really need some help with this one concept- I need to be able to take the user's input from multiple text boxes (assign them all a variable), put them through cramer's rule, and feed the answer out through a label.
I've made the storyboard and assigned one of the 12 text boxes (to test it) as an outlet and the label as an outlet and a button as an action, and tried a few different ways to just take the user input and (unaltered) feed it back out through the label so I know what I'm working with before I get into the math, and it's been unsuccessful. Having major syntax problems.
I have attached my code below:
//
// NewClass.h
// Cramer's Rule
#import <Foundation/Foundation.h>
#interface NewClass : NSViewController <NSTextFieldDelegate> {
IBOutlet NSTextField *box_a;
IBOutlet NSTextField *coord;
NSString *string;
}
#property (nonatomic, retain) IBOutlet NSTextField *box_a;
#property (nonatomic, retain) IBOutlet NSTextField *coord;
- (IBAction)calculate:(id)sender;
#end
AND
//
// NewClass.m
// Cramer's Rule
#import "NewClass.h"
#implementation NewClass
#synthesize box_a;
#synthesize coord;
- (IBAction)calculate:(id)sender {
NSTextField * input=box_a;
coord =input;
}
#end
As far as I know, I have the most up to date version of Xcode, and there is no option for creating a storyboard for an OSX project. Storyboards are for iOS projects. And that would explain the reason why you're unable to hook any thing up from the storyboard to your code.
This isn't to say that a storyboard can't be put in an OSX project--it can't. But it can't be selected from the Cocoa section of new files to create--only the Cocoa Touch section, which is iOS stuff--not OSX.
You have to use NSTextFieldDelegate, it have callback methods like in iOS:
- (void)textDidBeginEditing:(NSNotification *)notification;
- (void)textDidEndEditing:(NSNotification *)notification;
- (void)textDidChange:(NSNotification *)notification;
- (BOOL)acceptsFirstResponder;
For example:
- (void)textDidChange:(NSNotification *)notification{
if ([notification object]== box_a)
{
// ...
}else if ([notification object]== box_b)
{
// ...
}
}
Your problem is more fundamental than syntactical, you need to go and study up on what various things are and how they behave, this includes: variables, properties, objects and object references.
To briefly introduce why you're going wrong: Think of an object as a building. What is "in" the building may change over time, but the address of the building (usually!) does not. An address refers you to a building, and that is what an object reference does.
A variable is a box which holds a value of some type, that value can change over time, but the box does not.
When you declare:
NSTextField *input;
You are requesting that a variable be created for you which can hold references to objects - it does not hold an object anymore than address is a building, it just tells you where to find an object.
When you then assign a value to your variable:
NSTextField *input = box_a;
You are requesting the the value in box_a be copied and placed (stored) in input. That value is an object reference, it is not an object. Whatever object was referenced by box_a is not altered in anyway by this statement - what is in the house doesn't change, you just write the house's address down somewhere else.
When you then do:
coord = input;
you are doing the same thing - copying addresses. No objects are altered. The objects you are referring to are of type NSTextField, they have a visual representation on the screen, copying their addresses doesn't alter that visual representation anymore than copying the address of a building changes what is in the building.
When it comes to properties your code suggests a confusion between a property, which is a piece of code which does something, and its backing variable, a variable which that piece of code operates on.
Understanding these concepts is vital. You need to go an study up some more on programming.
HTH

NSTableView edits not changing NSMutableArray

I have a Class that stores some strings along with a BOOL flag that is set to YES if one of the strings has been updated.
I have a NSTableView that displays the strings in my class. The view is controlled via separate controller class and the view is fed by an NSMutableArray.
The GUI stuff seems to work fine in terms of displaying data and allowing me to edit the cells in the table view. The problem I am having is the edits don't change the objects stored in the NSMutableArray. I have some debug code to print out the strings when I close the app, and none of the changes made in the GUI show up in the objects at this point. Setting a break point where those values are changed shows me the objects are indeed changed, but those changes seem to get lost. How can I get any changes I make in the view to persist in the object stored in the NSMutableArray backing the NSTableView?
Here is how I am coding:
// my class .h file
#interface Snip : NSObject <NSMutableCopying>
#property (assign) int64_t id_num;
#property (assign) BOOL changed;
#property NSMutableString *name;
#property NSMutableString *text;
#property (copy) NSString *language;
// my class .m file
import "Snip.h"
#implementation Snip
#synthesize id_num;
#synthesize name;
#synthesize text;
#synthesize changed;
#synthesize language;
method from my controller class
// edit table values
-(void)tableView:(NSTableView *)tableView setObjectValue:(id)object forTableColumn(NSTableColumn *)tableColumn row:(NSInteger)row
{
Snip *sn = [snippet_list objectAtIndex:row];
[sn setChanged:YES];
[sn setValue:object forKey:[tableColumn identifier]];
NSLog(#"Change: %#",sn.name);
}
That last NSLog statement displays the change I made in the GUI. Also, the setChanged:YES is lost as well when I print the NSMutableArray contents when closing the app.
This behaviour is often due to the data being initialised again at some point after being changed by the user.
You can catch this problem by setting a breakpoint (or NSLog statement) in your initialisation code.

Passing setters/getters of an instance variable up to a class

I'm working on creating a UIView subclass (which I'm calling MarqueeLabel) that animates a subview UILabel ivar in a marquee fashion when the UILabel text is too long for the containing view.
I was hoping to have a clean implementation, without having to write methods in my MarqueeLabel class just to set/retrieve all the the standard UILabel (text, font, color, etc) instance variables of the UILabel ivar. I've found a way to do this with message forwarding - all unrecognized methods sent to MarqueeLabel are passed on to the UILabel ivar. In my case the methods unrecognized by MarqueeLabel are the methods typically used with UILabel.
There are some problems with that approach though:
1. You have to use [marqueeLabel setText:#"Label here"], rather than marqueeLabel.text
2. The compiler gives warnings on the above line, because:
'MarqueeLabel' may not respond to '-setText:'
which I would know to ignore but would annoy anyone else.
To avoid these problems, is there any way to "bring forward" the methods an ivar so that they're accessible to something using the class while still acting upon the ivar object?
Thanks!
Note: The way I've set this up may not be the best way to do it either. Perhaps subclassing or class continuing UILabel would be better, but I wasn't able to grasp how the animation + clipping (when the text scrolling off moves out of containing UIView and disappears) could be done using those methods.
Note 2: I know you can use marqueeLabel.subLabel.text where subLabel is the subview UILabel. And this may be the direction I take, but might as well see if there's a better solution!
For properties, you could define a property in the interface and use #dynamic in the implementation so that you can don't have to create stub implementations. Make sure you also override valueForUndefinedKey: and setValue:forUndefinedKey: and forward to your label.
For any methods which are not part of a property, you can use a category to declare the method without implementing it. This will get rid of warnings but still use the builtin forwarding.
//MarqueeLabel.h
#import <UIKit/UIKit.h>
#interface MarqueeLabel : UIView {}
#property (nonatomic, copy) NSString *text;
#end
#interface MarqueeLabel (UILabelWrapper)
- (void)methodToOverride;
#end
//MarqueeLabel.m
#import "MarqueeLabel.h"
#implementation MarqueeLabel
#dynamic text;
- (id)valueForUndefinedKey:(NSString *)key {
return [theLabel valueForKey:key];
}
- (void)setValue:(id)value forUndefinedKey:(NSString *)key {
[theLabel setValue:value forKey:key];
}
#end

Cocoa / Objective-C: Access a (Boolean) variable in different Classes

Situation:
Noob / Xcode 3.1
I have an AppView (NSView subclass) and an AppController (NSObject subclass)
in AppView.h i declare a boolean (BOOL: booleanDraw), which i set to 'NO' in AppView.m
When a button is clicked it 'launches' an action (AppController .h/.m) now i want to change booleanDraw to YES when the button is clicked.
I searched and found: do it with #property okay i tried to do that but it didnt work. (because i didnt totally get what to do probably)
i did:
#property BOOL booleanDraw;
(in AppView.h)
#implementation AppView
#synthesize(readwrite, nonatomic) booleanDraw;
(in AppView.m)
AppView *obj;
obj.booleanDraw = YES; // implicitly calls [obj setVar:3]
(in AppController.m)
Thanks for any help, i read some tutorials already but often they suggest some steps that should be basic but that dont belong to my repertoire, and the ADN often confuse me xD sorry but believe me im trying^^
You just reversed the synthesize and property statements:
in .h:
#property (nonatomic) booleanDraw;
(by default properties are readwrite, you only need to state when they are readonly)
in .m:
#synthesize booleanDraw;
In the controller you need to get the app view reference, the code you posted would not work unless you set "obj" to something.