UIVIew: Is there a way to have multiple tags to retrieve the superview? - objective-c

I'm coding for iOS7.
I have a grouped UITableView, with 3 different sections, each one of them has multiple rows.
In every row there is an UITextField added as accessoryView:
UITextField *myTextField = [UITextField alloc] init];
cell.accessoryView = myTextField;
I need to retrieve the right cell/textField in other places of my code, for example this code was working up to iOS6:
-(void) textFieldValueDidChange: (UITextField*) sender
{
NSIndexPath *indexPath = [self.tableView indexPathForCell: (UITableViewCell*) sender.superview];
switch (indexPath.section){
case(SECTION_1):{
switch(indexPath.row){
case(ROW_1):{
//DO SOMETHING
}
}
}
}
}
This kind of code breaks in iOS7.
On another thread someone suggested to me to never retrieve my subviews like this but to assign the indexPath.row value to the tag property of the sender when the cell is created and then retrieve the right cell from it. This works perfectly when the table is a normal one and I am dealing just with the row number but here I have to keep track both of the row and the sections and there is only one tag property.
What would be my best option to deal with it? I thought about adding 1000, 2000, 3000 etc.. to keep track of the section, example: 2003 would mean indexPath.section = 2 and indexPath.row = 3 but this doesn't apper an elegant solution to me. Any idea?
Thanks
Nicola

What about creating a category on UIView with a property that will hold the indexPath?
.h:
#interface UIView (IndexPathTag)
#property (nonatomic, retain) NSIndexPath *indexPathTag;
.m
#implementation UIView(IndexPathTag)
Then, in your code, if you import UIView+IndexPath, you can do:
#import "UIView+IndexPathTag.h"
myView = [[UIView alloc] init];
myView.indexPathTag = indexPath;

Related

NSTableView ViewBased never calling the needed delegate

I have a NSTableView where I wish to display a list of info.
Currently the viewForTableColumn method delegate never runs, but numberOfRowsInTableView does.
I have the NSTableViewDelegate and NSTableViewDataSource set in the ViewController head. And I set the tableview delegate and datasource to self. Does somebody know why it wouldn't run? I've added a screenshot and code below.
ViewController.h
#interface ViewController : NSViewController <NSTableViewDelegate, NSTableViewDataSource>
#property (strong) IBOutlet NSTableView *tableView;
#property (strong, nonatomic) NSMutableArray<App *> *installedApps;
#end
ViewController.m
- (void)viewDidLoad {
[super viewDidLoad];
_installedApps = [[NSMutableArray alloc] init];
_tableView.dataSource = self;
_tableView.delegate = self;
// Other stuff that populates the array
}
- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView {
return _installedApps.count;
}
-(NSView *)tableView:(NSTableView *)tableView viewForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row {
NSTableCellView *result = [tableView makeViewWithIdentifier:#"appCell" owner:self];
result.textField.stringValue = #"Hello world";
return result;
}
The view is in a container view, I have the 'appCell' identifier set to the Table Cell View.
The array _installedApps is empty and numberOfRowsInTableView: returns 0. Thus, tableView:viewForTableColumn: is not called because there is no row to show. No rows also means no columns.
You should also ensure that you have configured your table view as view based in attributed inspector of the table view.
I can't see it in the screenshots, but...is the highlighted row of the view hierarchy (Table Cell View) the one with the appCell identifier?
[minutes pass...]
Oops; sorry. I see you've noted that above.
The reason I ask is that I made a new project from your code, changing the array type from App to NSString, added a one-column table view to the storyboard, linked it to the code, added a couple enties to the array in -viewDidLoad, and -- once I put the appCell identifier in the right place (duh) -- it all worked fine.
This is super strange, but I had the very same issue, everything connected correctly, number of rows being called, but not viewForTableColumn... In the end the following Answer proved to be the solution:
https://stackoverflow.com/a/13091436/3963806
Basically, I had setup the tableview straight out of the Object library, no layout constraints etc... Once I added layout constraints, the method started to be called... Super strange as I could see and click on the "rows" but they weren't populated correctly... I think it's down to clipping as mention in the linked answer.

Why is a custom cell's label only appearing when a row is selected?

I am currently working on an app, in which I have a list of devices based on JSON, and created some custom cells for specific device types. My code sorts the devices just fine and sets the labels of the cells to the device name, I already checked that. The cellIdentifiers are correct, too. My table view contains a cell from the class ZWDeviceItem, so it should load properly since all classes like ZWDeviceItemSwitch are subclasses. I load my custom cells via NIBs.
My problem is, that the labels in those custom cells are not visible until you select a specific row. I set the IBOutlets for my tableview and linked the label to the custom cells.The cell height is more than sufficient and everything is displayed correctly when I select a row.
Code to set the device type:
#class ZWDevice;
#interface ZWDeviceItem : UITableViewCell
#property (strong, nonatomic) IBOutlet UIImageView *refreshingImage;
#property (strong, nonatomic) IBOutlet UILabel *nameView;
- (void)setDisplayName:(ZWDevice*)device;
#end
#import "ZWDeviceItem.h"
#import "ZWDevice.h"
#implementation ZWDeviceItem
#synthesize nameView = _nameView;
#synthesize refreshingImage = _refreshingImage;
- (void)setDisplayName:(ZWDevice *)device
{
NSDictionary *dict = device.metrics;
self.nameView.text = [dict valueForKey:#"title"];
}
#end
Code to select which NIB to load:
- (ZWDeviceItem*)createUIforTableView:(UITableView *)tableView atPos:(NSIndexPath *)indexPath
{
ZWDeviceItem *item = [tableView dequeueReusableCellWithIdentifier:_deviceType];
if (item == nil)
{
if ([_deviceType isEqualToString:#"probe"])
//this method returns the NIB
item = [ZWDeviceItemDimmer device];
else if ([_deviceType isEqualToString:#"switchBinary"])
item = [ZWDeviceItemSwitch device];
else if ([_deviceType isEqualToString:#"switchMultilevel"])
item = [ZWDeviceItemSensorMulti device];
else if ([_deviceType isEqualToString:#"thermostat"])
item = [ZWDeviceItemThermostat device];
else if ([_deviceType isEqualToString:#"battery"])
item = [ZWDeviceItemBattery device];
else if ([_deviceType isEqualToString:#"fan"])
item = [ZWDeviceItemSensorBinary device];
else
item = [[ZWDeviceItem alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:_deviceType];
}
return item;
}
and last but not least my CellForRowIndexPath method in the UIViewController (and yes, I have set the ViewController as delegate and datasource):
- (UITableViewCell *)tableView:(UITableView*)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
ZWDevice *device = [objects objectAtIndex:indexPath.row];
ZWDeviceItem *cell = [device createUIforTableView:tableView atPos:indexPath];
[cell setDisplayName:device];
return cell;
}
I would really appreciate help with this problem! I looked up several similar issues but could´t fix it yet. Most answers told me to check the linking but that works fine.
If you need any further information let me know and I will provide it.
Thanks in advance!
EDIT:
Here are the screenshots from the simulator:
Images
EDIT: Solved
Well, seems I was just too stupid for this one. The solution was to set the text color in the NIBs to something different than white… I set it to dark text color and now it gets displayed right away and beautifully.
Sorry for you guys who thought about a serious solution.
You have this line here:
ZWDeviceItem *item = [tableView dequeueReusableCellWithIdentifier:_deviceType];
this will always return a valid item or create one if it can't return one. And it's using _deviceType before you are setting it, so it's using the value of whatever it is set to before.

Displaying elements of an array on a buttons

I am working on an educational game for a local school system, in this game students are presented with six numbers and they have to try and use as many of them as they can to equal a seventh number, the numbers are stored in an array called currentHand, and the numbers need to be displayed as text on buttons present on the screen. The catch is, as they combined numbers together the values on the buttons need to change accordingly. Say I add two buttons a button with the value 1 and a button with a value 2, the value three gets added to the array and the values 1 and 2 are deleted, then one button needs to become invisible and the other needs to be updated with the value 3. I was told to try this with an IBOutletCollection, but because I am still very new to objective C, I have no idea how to use an IBOutCollection to do this, I had an idea to create a method that would tie each button to an element in the array, as the list shrank in size so would the number of buttons, so if you had the numbers
{1,2,3,4,5,6}
each button would have one value assigned to them, but as the list changed to say
{1,2,3,4,11}
the button displaying the number 6 would disappear and the button holding the number 5 would change to 11, but as I said I have no idea how to accomplish this.
As I cannot display pictures being such a new member, I have linked one to how I tried to set up the IBOutletCOllection
and below is the code that instantiates currentHand
-(NSMutableArray *) currentHand{
if (_currentHand == nil) {
_currentHand = [[NSMutableArray alloc]init];
}
self.currentHand = self.myDeck.giveHand;
return _currentHand;
}
Any advice on this would be greatly appreciated, thanks in advanced.
Here is one way to approach this problem. As I said in my comments, I don't see any need for an array to keep track of the numbers as you click the button (just one to provide the original numbers for the title), or the need for an IBOutletCollection.
So, in the example below, I created a single view project, added 6 buttons to the view controller's view, set all their tags to 1 and connected them all up to the one action method, buttonPressed:. I don't use any outlets for the buttons, because you can do what you need to do just by looping through the view's subviews -- the tags aren't really necessary in this example, but if you had any other buttons in your view, the tags would be a way to distinguish these 6 game buttons from any others.
I'm not sure this is exactly the look you were going for, but try it out and see if it's close.
#interface ViewController : UIViewController
#property (assign) int total;
#property (retain) UIButton *previousButton;
#import "ViewController.h"
#implementation ViewController
#synthesize previousButton,total;
- (void)viewDidLoad
{
[super viewDidLoad];
self.total = 0;
NSMutableArray *theData = [NSMutableArray arrayWithObjects:#"1",#"3",#"5",#"7",#"8",#"4",nil];
for (id obj in [self.view subviews]) {
if ([obj isKindOfClass:[UIButton class]] && [obj tag] == 1) {
[(UIButton *)obj setTitle:theData.lastObject forState:UIControlStateNormal] ;
[theData removeLastObject];
}
}
}
-(IBAction)buttonPressed:(UIButton *)sender {
self.total += sender.titleLabel.text.intValue;
[sender setTitle:[NSString stringWithFormat:#"%d",self.total] forState:UIControlStateNormal];
self.previousButton.hidden = YES;
sender.enabled = NO;
[sender setTitleColor:[UIColor lightGrayColor] forState:UIControlStateDisabled];
self.previousButton = sender;
}

Displaying an UIImageView property from another class programmatically

Well, here's the situation:
I've got...
a custom class with an UIImageView-property, let's call it the Enemy-class
a ViewController
a NSMutableArray to help create multiple instances of Enemy (called enemies)
and what I want:
be able to create an unlimited amount of Enemy-Instances through a method in my ViewController (like [self spawnEnemy];, where self is the ViewController)
and, subsequently, display the UIImageView property (let's call it "enemyImage") on the view that is controlled by my ViewController
I've tried something like this:
-(Enemy *) spawnEnemy
{
Enemy *tempEnemy = [Enemy new];
[enemies addObject:(Enemy*)tempEnemy];
[self.view addSubview:(UIImageView*)[[enemies objectAtIndex:[enemies count]] enemyImage]];
//randomLocation is declared in the Enemy-Class and just assigns a random
//CGPoint to self.enemyImage.center
[[enemies objectAtIndex:[enemies count]] randomLocation];
return [[enemies objectAtIndex:[enemies count]]createEnemy];
}
This runs without errors, randomLocation gets called (tried with NSLog), AND if I do something like this in another Method of ViewController:
[[self spawnEnemy] enemyTestMethod];
enemyTestMethod is being executed as well.
But still, no enemieViews are displayed on the screen...
What am I doing wrong?
Thank you so much for your help and time.
==== Edit ====
Here's the relevant code from Enemy.h/Enemy.m:
#interface Enemy : NSObject
{
UIImageView *enemyImage;
}
#property (nonatomic, retain) IBOutlet UIImageView *enemyImage;
-(Enemy*) createEnemy;
//Enemy.m
#implementation Enemy
#synthesize enemyImage, speed;
-(Enemy *) createEnemy
{
self.enemyImage = [[UIImageView alloc] init];
[self.enemyImage setImage:[UIImage imageNamed:#"enemy.png"]];
return self;
}
I also corrected the last line in the spawnEnemy-Method to properly send createEnemy.
You don't include the code in Enemy where you alloc/init the UIImageView property. Unless this code explicitly specifies a CGRect with the size and origin that you want, the view will be initialized with CGRectZero, which means even if you're correctly adding the subview (and it looks like you are) you still won't see it anywhere.
Post the Enemy code, and the problem will probably be immediately apparent.
Have you called -createEnemy before you added them to your view?
OK, you've got this checked.
Then maybe you should check as #MusiGenesis suggested.
To do this, you need to inspect the properties of your enemyImage.
You can do this in either of the following ways:
print its frame.size by:
NSLog(#"enemyImage frame size: %#", NSStringFromCGRect(enemy.enemyImage));
set a breakpoint where you feel great, and check with your debugger:
p (CGRect)[[enemy enemyImage] frame]

Handling multiple UISwitch controls in a table view without using tag property

I have a table view controller with multiple UISwitch controls in them. I set the delegate to the table view controller with the same action for all switches. I need to be able to determine what switch was changed, so I create an array of strings that contains the name of each switch. The indexes in the array will be put in the tag property of each UISwitch.
However, I'm ready using the tag property for something else, namely to find the right control in the cell in cellForRowAtIndexPath with viewWithTag! (There are several things I need to set within each cell.)
So, am I thinking along the right lines here? I feel I'm rather limited in how I find out exactly which UISwitch changed its value, so I can do something useful with it.
I fixed this by subclassing UISwitch like so:
#interface NamedUISwitch : UISwitch {
NSString *name;
}
It seems elegant (no index arrays required) and the tag property is free to do whatever it wants.
I read that you have to be careful with subclassing in Objective-C, though...
I have written a UISwitch subclass with a block based hander for value change control events which can help when trying to track which switch's value has changed. Ideally, we could do something similar with composition rather than subclassing, but this works well for my needs.
https://gist.github.com/3958325
You can use it like this:
ZUISwitch *mySwitch = [ZUISwitch alloc] init];
[mySwitch onValueChange:^(UISwitch *uiSwitch) {
if (uiSwitch.on) {
// do something
} else {
// do something else
}
}];
You can also use it from a XIB file, by dragging a switch onto your view, and then changing its class to ZUISwitch
You are close in your approach. What I have done in similar situations is create separate UITableViewCell subclasses, set the tag of the UISwitch to be the index.row of the index path, and only use that UITableViewCell subclass in a specific section of the table view. This allows you to use the tag of the cell to uniquely determine what cell has the event without maintaining a separate index list (as it sounds like you are doing).
Because the cell type is unique, you can than easily access the other elements of the cell by creating methods/properties on the UITableViewCell Subclass.
For example:
#interface TableViewToggleCell : UITableViewCell {
IBOutlet UILabel *toggleNameLabel;
IBOutlet UILabel *detailedTextLabel;
IBOutlet UISwitch *toggle;
NSNumber *value;
id owner;
}
#property (nonatomic, retain) UILabel *toggleNameLabel;
#property (nonatomic, retain) UILabel *detailedTextLabel;
#property (nonatomic, retain) UISwitch *toggle;
#property (nonatomic, retain) id owner;
-(void) setLable:(NSString*)aString;
-(void) setValue:(NSNumber*)aNum;
-(NSNumber*)value;
-(void) setTagOnToggle:(NSInteger)aTag;
-(IBAction)toggleValue:(id)sender;
#end
In:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// ... prior iniitalization code for creating cell is assumed
toggleCell.owner = self;
[toggleCell setLable:#"some string value"];
[toggleCell setTagOnToggle:indexPath.row];
toggleCell.owner = self;
return toggleCell;
//... handle cell set up for other cell types as needed
}
Owner is the delegate for the cell and can then be used to initiate actions in your controller. Make sure you connect your UISwitch to the toggleValue Action, so that you can initiate actions in the delegate when the UISwitch changes state:
-(IBAction)toggleValue:(id)sender;
{
BOOL oldValue = [value boolValue];
[value release];
value = [[NSNumber numberWithBool:!oldValue] retain];
[owner performSelector:#selector(someAction:) withObject:toggle];
}
By passing the UISwitch with the method call, you can then access the index path for the cell. You could also bypass the use of the tag property by explicitly having an ivar to store the NSIndexPath of the cell and then passing the whole cell with the method call.
I realize I'm about three years late to the party but I've developed a solution without subclassing that I think is preferable (and simpler). I'm working with the exact same scenario as Thaurin's described scenario.
- (void)toggleSwitch:(id) sender
{
// declare the switch by its type based on the sender element
UISwitch *switchIsPressed = (UISwitch *)sender;
// get the indexPath of the cell containing the switch
NSIndexPath *indexPath = [self indexPathForCellContainingView:switchIsPressed];
// look up the value of the item that is referenced by the switch - this
// is from my datasource for the table view
NSString *elementId = [dataSourceArray objectAtIndex:indexPath.row];
}
Then you want to declare the method shown above, indexPathForCellContainingView. This is a seemingly needless method because it would appear at first glance that all you have to do is identify the switch's superview but there is a difference between the superviews of ios7 and earlier versions, so this handles all:
- (NSIndexPath *)indexPathForCellContainingView:(UIView *)view {
while (view != nil) {
if ([view isKindOfClass:[UITableViewCell class]]) {
return [self.myTableView indexPathForCell:(UITableViewCell *)view];
} else {
view = [view superview];
}
}
return nil;
}