UIView memory address changing randomly - ios

I have a custom tableViewCell which contains a custom UIView. Within my cellForRowAtIndexPath I alloc and init my custom UIView and check the address of the UIView object. My custom UIView class also has a method called createArray that I call from cellForRowAtIndexPath that gives me an array with CGPoints that I use to create a path. I also check my UIView address at the end of my createArray method and it is consistent with what it was when I previously checked after I originally initialized the UIView. The createArray method works fine and gives me a valid array. However,after the method is done executing, layoutSubviews gets called immediately. What I noticed when I reach layoutSubviews is that my UIView address is changing and the array I created to create my path goes to nil. Anyone know whats happening?
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString* identifier = #"audioTableCell";
OSAudioTableCell *cell = [self.audioTable dequeueReusableCellWithIdentifier:identifier];
if(cell == nil)
{
cell = [[OSAudioTableCell alloc] initWithStyle: UITableViewCellStyleDefault reuseIdentifier: identifier];
}
self.waveView = [[OSWaveView alloc] init];
NSLog(#"self.view address %#", self.waveView);
[self.waveView createArray:transferredAsset andSetSize:cell.waveView.bounds.size];
}
-(void) layoutSubviews
{
if (!self.blueWave) {
self.blueWave = [self createShapeLayer];
self.blueWave.strokeColor = [UIColor blueColor].CGColor;
self.redWave = [self createShapeLayer];
self.redWave.strokeColor = [UIColor redColor].CGColor;
[self.layer addSublayer:self.blueWave];
[self.layer insertSublayer:self.redWave above:self.blueWave];
[self render];
}
}
Is layoutSubviews reinitializing my view or is there some background method that gets executed that is causing everything to go to nil? Also, I was thinking this might have to do with my UIView being an IBOutlet in storyboard and getting manually allocated/initialized.

Related

Unable to update the Subview in a Custom UITableViewCell

I have a custom UITableViewCell in which I have two UIViews . I want to change the BackgroundColor radius and some more properties of UIViews.
But I am unable to do so.
Here is my setUp.
Step 1:
Create A Custom Cell with XIB.
Step 2: Entered the Cell Identifer name in XIB for CustomCell.
Step 3: Instantiated NIB in viewDidLoad
UINib *nib = [UINib nibWithNibName:#"CustomCell" bundle:nil];
[[self mTableView] registerNib:nib forCellReuseIdentifier:#"CustomCell"];
Step 4: Cell For Row At Index Method:
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// Create an instance of ItemCell
CustomCell *cell = [tableView dequeueReusableCellWithIdentifier:#"CustomCell"];
[cell updateCellView];
return cell;
}
Step 5: Checked Twice the Outlet Connections all are Fine.
Step 6: Custom Cell Class:
#import "TransportCell.h"
#implementation TransportCell
- (void)awakeFromNib {
// Initialization code
}
- (void)setSelected:(BOOL)selected animated:(BOOL)animated {
[super setSelected:selected animated:animated];
// Configure the view for the selected state
}
- (void)updateCellView
{
self.backgroundView.backgroundColor = [UIColor redColor];
}
#end
This code has no effect on my Cell View.
I debug the code. When I logged the backgroundView I get nil when updateCellView method get called:
Here is my CustomCell's Xib:
I have to change the Properties of Inner UIView.(Blue in color)
Instead of calling the method in the cellForRowAtIndexPath try this:
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// Create an instance of ItemCell
CustomCell *cell = [tableView dequeueReusableCellWithIdentifier:#"CustomCell"];
return cell;
}
and then in your custom method
- (void)updateCellView
{
self.backgroundView.backgroundColor = [UIColor redColor];
// reload the table view
[self.tableView reloadData];
}
you need to add [ tableview reloadData ]
in method update cell view
Ideally all the info needed to render the cell should come from either the data source or other events. For example, if you are changing the border-color it should mean that either some entity (say user) has performed an event or something has changed in the data-source like a value passed its threshold.
The change in data-source or those events should either trigger refreshing the whole table:
-(void) reloadData
or some cells:
- (void)reloadRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation
Only then the cells will get updated.
you are changing the self.backgroundview.backgroundColor
Default it is nil for cells in UITableViewStylePlain, and non-nil for UITableViewStyleGrouped.
The 'backgroundView' will be added as a subview behind all other views.
#property (nonatomic, strong, nullable) UIView *backgroundView;
try this method in TransportCell, it's swift code make same for objective c
override func layoutSubviews()
{
self.backgroundView.backgroundColor = [UIColor redColor];
}
Use this:
self.contentView.backgroundColor = [UIColor redColor];
Hope this helps!

Deallocated ViewController inside custom UITableViewCell

The structure of my code is this:
UITableViewController (with one or more)-> Custom UITableviewCell (add the view of)-> UIViewController
Now, to notify an action on the UIViewController to the UITableViewController I have a protocol that follow the inverse flow explained before, but, when I do some action on UIViewController, app crashes because I'm trying to access to a deallocated instance...
I avoid the crash on IBAction on UIViewController in a dirty way: setting a property in the UIViewController as self
How can I solved this leak? This is my code:
UITableViewController:
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
GameTableViewCell *cell;
cell = [tableView dequeueReusableCellWithIdentifier: CellId];
[cell configureWithGame: currentGame];
cell.delegate = self;
return cell;
}
Custom TableViewCell:
-(void)configureWithGame:(Game *)game
{
outcomeController = [[OutcomeViewController alloc] initWithGame:game];
outcomeController.delegate = self;
activeGame = game;
//Adapting outcomeView
CGRect frame = outcomeController.view.frame;
frame.size = self.outcomeView.frame.size;
outcomeController.view.frame = frame;
[[self.outcomeView subviews] makeObjectsPerformSelector:#selector(removeFromSuperview)];
[self.outcomeView addSubview:outcomeController.view];
}
The OutcomeViewController has a property #property (nonatomic, strong) id forceRetain; and it sets in -(void)viewDidLoad in this way:
self.forceRetain = self;
This causes some leaks and I want to solve this issue.
try setting outcomeController property to strongin your cell code.
Moreover, with the code you posted, the OutcomeViewController will be allocated every time you scroll the UITableView. Is this the behavior you want?

UISlider in Dynamic UITableView

I am creating an app in where I need to add a bunch of sliders in a Dynamic UITableView. I added the slider like this:
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:#"Cell" forIndexPath:indexPath];
UISlider *slider = [[UISlider alloc]init];
//ALL OTHER CODE
[cell addSubview:slider];
return cell;
}
Now the slider is added to the UITableView but the if I changed the value of the first slider another slider changes with it.I know this is something to do with dequeing the cell but how do I fix it?
EDIT:
I tried #daveMack answer like this:
CustomCell.m:
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
// Initialization code
self.checkBox = [[M13Checkbox alloc]initWithTitle:#"Checkbox!"];
self.checkBox.checkAlignment = M13CheckboxAlignmentLeft;
[self addSubview:self.checkBox];
}
return self;
}
Cell For Row At Index Path:
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
CheckboxCell *cell;
cell = [self.tableView dequeueReusableCellWithIdentifier:#""];
if (!cell) {
cell = [[CheckboxCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"Cell"];
return cell;
}
return cell;
}
No, no, no! I would not suggest you add views like that to your UITableViewCell. Funky stuff can happen as you have experienced yourself because of the dequeueing process.
I suggest you do the following:
Create a custom table view cell with its appropriate .h, .m file and .xib file
In your custom cell you can add WHATEVER views you like and however many views that you like.
Make sure you create a property of type UIScrollView in your .h file and link it to the interface builder to your custom cell's slider, call the property slider.
Now in your main view controller where you are creating your table, make sure you have an NSMutableArray and name it something like sliderValuesArray that can store all your slider values for each cell. You want to make sure that the number of cells is equal to the number of elements in your sliderValuesArray.
Then you can do something like this in your cellForRowAtIndexPath delegate method:
In your cellForRowAtIndexPath method do something like this:
myCustomCell.slider.maximumValue = 100;
myCustomCell.slider.minimumValue = 1;
myCustomCell.slider.continuous = TRUE;
//set a method which will get called when a slider in a cell changes value
[myCustomCell.slider addTarget:self action:#selector(sliderChanged:) forControlEvents:UIControlEventValueChanged];
//Keep a reference to each slider by assigning a tag so that we can determine
//which slider is being changed
myCustomCell.slider.tag = indexPath.row;
//Grab the value from the sliderValuesArray and set the slider knob to that position
myCustomCell.slider.value = [[sliderValuesArray objectAtIndex:indexPath.row] intValue];
Now in your sliderChanged method you can do this:
-(void)sliderChanged:(UISlider)sender{
//Grab the slider value, it needs to be converted to an NSNumber so that we can
//store it successfully as an object in our sliderValuesArray
NSNumber sliderValue = [NSNumber numberWithInt:sender.value];
//This is how we determine which position in our slidersArray we want to update,
//Based on the tag we set our slider view on initialisation in our cellForRowAtIndexPath
int cellPosition = sender.tag;
//Use the cellPosition to update the correct number in our sliderValuesArray
//with the sliderValue retrieved from the slider that the user is sliding
[sliderValuesArray replaceObjectAtIndex:cellPosition withObject:sliderValue];
}
I ran into this same issue awhile back. The solution I came up with was to subclass UITableViewCell. Then I added the slider in the init method of the subclass and exposed it via a property.
Now, when you want to change the value of JUST ONE slider, you would do something like:
[cell slider]setValue:(someValue)];

Styling custom UITableViewCell in initWithCoder: not working

I have some issues with a custom UITableViewCell and how to manage things using storyboards. When I put the styling code in initWithCoder: it doesn't work but if I put it in tableView: cellForRowAtIndexPath: it works. In storyboard I have a prototype cell with its class attribute set to my UITableViewCell custom class. Now the code in initWithCoder: does get called.
SimoTableViewCell.m
#implementation SimoTableViewCell
#synthesize mainLabel, subLabel;
-(id) initWithCoder:(NSCoder *)aDecoder {
if ( !(self = [super initWithCoder:aDecoder]) ) return nil;
[self styleCellBackground];
//style the labels
[self.mainLabel styleMainLabel];
[self.subLabel styleSubLabel];
return self;
}
#end
TableViewController.m
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"NearbyLandmarksCell";
SimoTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
//sets the text of the labels
id<SimoListItem> item = (id<SimoListItem>) [self.places objectAtIndex:[indexPath row]];
cell.mainLabel.text = [item mainString];
cell.subLabel.text = [item subString];
//move the labels so that they are centered horizontally
float mainXPos = (CGRectGetWidth(cell.contentView.frame)/2 - CGRectGetWidth(cell.mainLabel.frame)/2);
float subXPos = (CGRectGetWidth(cell.contentView.frame)/2 - CGRectGetWidth(cell.subLabel.frame)/2);
CGRect mainFrame = cell.mainLabel.frame;
mainFrame.origin.x = mainXPos;
cell.mainLabel.frame = mainFrame;
CGRect subFrame = cell.subLabel.frame;
subFrame.origin.x = subXPos;
cell.subLabel.frame = subFrame;
return cell;
}
I have debugged the code and found that the dequeue... is called first, then it goes into the initWithCoder: and then back to the view controller code. What is strange is that the address of the cell in memory changes between return self; and when it goes back to the controller. And if I move the styling code back to the view controller after dequeue... everything works fine. It's just I don't want to do unnecessary styling when reusing cells.
Cheers
After initWithCoder: is called on the cell, the cell is created and has its properties set. But, the relationships in the XIB (the IBOutlets) on the cell are not yet complete. So when you try to use mainLabel, it's a nil reference.
Move your styling code to the awakeFromNib method instead. This method is called after the cell is both created and fully configured after unpacking the XIB.

TableViewCell image change when touched

I have my own TableViewCell class which inherit UITableViewCell. On my cell nib file, I have put a image in my TableViewCell. (The image does not fully occupy the whole cell area, there are spaces around image)
Then, I would like to implement a touch feedback feature that when user touch the image in the cell, the image will be replaced by another image.
I tried to do it in tableView:didSelectRowAtIndexPath: method :
- (void) tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
//my TableViewCell
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
//New image to replace the current one
UIImage* bg = [CCTheme imageNamed:#"green_btn_bg"];
UIImageView* bgView = [[UIImageView alloc] initWithImage:bg];
cell.selectedBackgroundView = bgView;
...
But it does not work at all. So, how can I implement this touch feedback feature?? That's when user finger touched the image in cell, the image get changed to another one.
Create a UIImageView property in your custom UITableViewCell class as an IBOutlet then set the image on that property from the cellForRowAtIndexPath: rather than the background property.
cell.yourCustomImageView.image = bgView;
Or add the UIImageView to the current generic UITableViewCell like below.
with your Current cell
[cell addSubview:bgView];
In didSelectRowAtIndexPath: you have to first change your data model (indicate that the status of the cell has changed).
You set the appropriate image cellForRowAtIndexPath:. (I.e. you check what the status is and provide the correct image.)
To update the cell, call reloadRowsAtIndexPaths:.
Explanation
You should not store the state of your cell in some view of the cell. E.g. if the different image represents some kind of selection, your array or other data structure that feeds your table view should keep track of which row is in this state.
Each time the cell has to be regenerated, cellForRowAtIndexPath is called. (This could be because the cell becomes visible, or because you explicitly update it.) This is the best place to check for the state information and display the cell accordingly.
I would recommend hooking a gesture recognizer to the UIImageView within your custom cell's init method:
// Add recognizer for tappable image
UITapGestureRecognizer *tapRecognizer = [[UITapGestureRecognizer alloc]
initWithTarget:self action:#selector(imageViewTapped:)];
[tapRecognizer setNumberOfTouchesRequired:1];
[tapRecognizer setDelegate:self];
self.tappableImageView.userInteractionEnabled = YES;
[self.tappableImageView addGestureRecognizer:tapRecognizer];
Then the handler:
- (void)imageViewTapped:(id)sender {
NSLog(#"image tapped");
self.tappableImageView.image = [UIImage imageNamed:#"some_different_image.png"];
}
Also, don't forget to have you custom cell declaration decorated like so:
#interface CustomTableViewCell : UITableViewCell <UIGestureRecognizerDelegate>
In the cellForRowAtIndexPath: method you need to ensure that the code in your init method is being fired so that the tapRecognizer is added.
Good luck!
[EDIT]
Depending on how you create your cell with the custom XIB you may not need this, but in my case I needed to explicitly call a method to initialize the state of the UI in the table cell. The way I do this is offer an initState method on the custom table view cell:
- (void)initState {
// Do other things for state of the UI in table cell.
// Add recognizer for tappable image
UITapGestureRecognizer *tapRecognizer = [[UITapGestureRecognizer alloc]
initWithTarget:self action:#selector(imageViewTapped:)];
[tapRecognizer setNumberOfTouchesRequired:1];
[tapRecognizer setDelegate:self];
self.tappableImageView.userInteractionEnabled = YES;
[self.tappableImageView addGestureRecognizer:tapRecognizer];
}
Then in cellForRowAtIndexPath I make sure to call initState on my table cell after it has been created:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
CustomTableViewCell *cell = (CustomTableViewCell*)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
UIViewController *temporaryController = [[UIViewController alloc] initWithNibName:#"CustomTableViewCell" bundle:nil];
// Grab a pointer to the custom cell.
cell = (CustomTableViewCell *)temporaryController.view;
[cell initState];
}
return cell;
}

Resources