Drawing + UITableViewCell + UIScrollView subclass? - objective-c

My (rather complicated) situation is as follows:
TestView is a subclass of UIScrollView which implements -drawRect:, but at some point inside -drawRect: it'll call a method, let's say -drawAnotherPartWithRect:context:. This method is implemented by subclasses of TestView to draw individually a certain part of the context.
There are two subclasses of TestView which implement -drawAnotherPartWithRect:context:, which currently do the same thing inside it: Subclass1 and Subclass2.
As of now, frame size is the only different between the two during initialization.
An instance of Subclass1 is used as a table view's section header, and it works perfectly, yet if Subclass2 is used as a subview of the cell's content view, it'll display, yet not scroll. Its initialization is as follows:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"PortoAppSubjectViewTableViewCell"];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"PortoAppSubjectViewTableViewCell"] autorelease];
Subclass2 *contentView = [[[Subclass2 alloc] initWithFrame:CGRectMake(0.f, 0.f, [tableView bounds].size.width, 32.f)] autorelease];
[contentView setContentSize:CGSizeMake(tableView.bounds.size.width * 3, 32.f)];
[contentView setTag:55];
[[cell contentView] addSubview:contentView];
}
[(SubjectTableViewCellContentView *)[[cell contentView] viewWithTag:55] setContainer:[[[[$container subGradeContainers] objectAtIndex:[indexPath section]] subGradeContainers] objectAtIndex:[indexPath row]]];
return cell;
}
The interesting thing is, the horizontal scroll indicator shows up and shows me that it's scrolling finely, yet the text (drawn with CoreText) doesn't move left/right along with it. That works out-of-the-box with Subclass1. Additionally, if Subclass2 is used instead as the view class of the section header view, it'll work finely.
So, what's up with horizontal scroll views and table view cells? I've checked out other related questions on SO but haven't been able to find any solution.

I solved the issue by instead of drawing directly to a UIScrollView subclass, drawing to a UIView subclass added as a subview of UIScrollView.
I'd still like some follow-ups, therefore leaving the question unanswered – why did it work with the header view and not with the table view cell then?

Related

How to avoid data being copied to UITableViewCell?

I have an array with charts that I would like to display in a table view. Since drawing the charts takes a few milli seconds, which would make scrolling choppy, I would like to remove the old chart from cell.contentView of a re-used cell and add a new subview with the correct chart when scrolling (see source code below). This works: the charts are correctly displayed.
However, when adding the subviews, the used memory increases and scrolling the first time down is choppy (scrolling is not choppy after all rows were displayed once).
It seems that the chart data, which is already stored in an instance variable (strong) is copied (not just a reference) into the UITableViewCell.
I would like to avoid this so that less memory is used.
Summary: How can I avoid it that my charts are being copied into a UITableViewCell when using addSubview. Instead, I would like to add just a reference to my data when using addSubview.
static NSString *CellIdentifier = #"chart";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell==nil)
{
cell = [[UITableViewCell alloc]
initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:CellIdentifier];
}
[cell.contentView.subviews makeObjectsPerformSelector:#selector(removeFromSuperview)];
VMKChartData *chartData = [shinobiCharts_ objectAtIndex:rowNumber];
ShinobiChart *shinobiChart = chartData.shinobiChart;
[cell.contentView addSubview:shinobiChart];
[self setSeparatorInsets:cell];
return cell;
Don't use a plain UITableViewCell and addSubview:, but create a custom table view subclass that has a property #property (nonatomic, weak) UIView *chartView, in tableView:cellForRowAtIndexPath: just assign it.
cell.chartView = shinobiChart;
in the setter for chartView, add it to the content view.
-(void)setChartView:(UIView *)chartView
{
[_chartView removeFromSuperview];
_chartView = chartView;
[self.contentView addSubView:chartView];
}

UITableViewCell UIButton dynamic height

I have CustomCellView which is custom view for cell in tableview. I have UIButton in xib and set it's default frame in xib.
In MainViewController (viewDidLoad)
// Get nib for custom cell
UINib *nib = [UINib nibWithNibName:#"CustomFeedCell" bundle:nil];
[self.mainTableView registerNib:nib forCellReuseIdentifier:#"CustomFeedCell"];
In cellForRow...
CustomFeedCell *cell = [self.mainTableView dequeueReusableCellWithIdentifier:#"CustomFeedCell"];
// here i need to set UIButton frame (height) depending on image height.
// I have tried diferent ways and it always return height set in CustomCellView xib
If you want to create buttons, images etc in UIView dynamically, you have to use viewDidLoad: method and do not use xibs.
Ok so what I am understanding from your question is you are having problem setting the dynamic height of a button present in your tableview cell. It sometimes might be more then the height of the cell and sometimes quiet less then height of the cell. And you want to adjust it accordingly. Correct ?
If thats your problem then you can set height of your each tableview cell in its delegate
tableView:heightForRowAtIndexPath:
as
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
return [indexPath row] * 20; // your desired height
}
Hope this solves your problem.

Update TableView (RootView) of SplitView

In my SplitView application I have some general data in the TableView at startup. When data in the TableView is selected, I want to display detailed data in the TableView at first and when something is selected there, it should be displayed in the DetailView, too. My question concerns the second step: How should I update the data for the TableView and avoid this TableView to disappear when something is selected?
If you are starting with Apple provided MasterDetail application, what it does by default is the following
Creates a split view controller.
On the left hand side it has a UINavigationController
On the right hand side it has DetailViewController
In the AppDelegate it has a UINavigationController with its rootcontroller as MasterViewController. (that is the reason you see MasterViewController) on the left.
The MasterViewController on the left does nothing related to navigation controller by default though. Looks like this is where you want to be able to drill down further and get another drill down view and so on.
Steps you need to take
First create another DrillDownLevel1ViewController which is a subclass of UITableViewController.
In that class, have two properties, one for data and one for reference to DetailViewController.
#property(strong,nonatomic)NSArray *drillDownLevel1Data;
#property(strong,nonatomic)DetailViewController *detailViewController;
In the MasterViewController's didSelectRowAtIndexPath do some thing like this
DrillDownLevel1ViewController *drillDownLevel1ViewController = [[DrillDownLevel1ViewController alloc] init];
drillDownLevel1ViewController.drillDownLevel1Data=[NSArray arrayWithObjects:#"One",#"Two",#"Three", nil];
drillDownLevel1ViewController.detailViewController=self.detailViewController;
[self.navigationController pushViewController:drillDownLevel1ViewController animated:YES];
In the DrillDownLevel1ViewController, you can correctly set numberOfSections, numberOfRows etc
In the cellForRowAtIndexPath of DrillDownLevel1ViewController, set correct value
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"Cell"];
if(cell==nil){
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"Cell"];
}
cell.textLabel.text=[drillDownLevel1Data objectAtIndex:indexPath.row];
return cell;
In the didSelectRowAtIndexPath of DrillDownLevel1ViewController
self.detailViewController.detailItem = [drillDownLevel1Data objectAtIndex:indexPath.row];
[self.detailViewController configureView];
DetailViewController on the right hand side should show you the data now. If it is not, then you can do some debugging as to whether the outlets are connected etc.
I have a simple project which I built, if you want I can email it to you.

What are the possible reasons why -(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath didn't get called?

I got a really strange problem.
My tableView has all the delegate and datasource set up.
Everything is fine.
However, clicking the rows do not activate:
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
I used custom cells.
After I click and click and click and click and click, sometimes it goes through.
I wonder what can possibly cause that? It's as if the customCell is "absorbing" the touch event or something or what?
Could that be why? If so, if we want to implement customCell and we want the tableView to handle the touch up event, what should we do?
Additional symptom:
If I remove user interaction enabled from the custom cell then the problem is solved with a catch.
However, clicking the button will somehow erase all the label texts in the customCell.
The implementation of the custom Cell is the following:
- (BGUIBusinessCellForDisplay *) initWithBiz: (Business *) biz
{
if (self.biz == nil) //First time set up
{
self = [super init]; //If use dequeueReusableCellWithIdentifier then I shouldn't change the address self points to right
NSString * className = NSStringFromClass([self class]);
//PO (className);
[[NSBundle mainBundle] loadNibNamed:className owner:self options:nil];
self.frame =self.view.frame;
[self addSubview:self.view]; //What is this for? self.view is of type BGCRBusinessForDisplay2. That view should be self, not one of it's subview Things don't work without it though
}
if (biz==nil)
{
return self;
}
_biz = biz;
self.prominentLabel.text = [NSString stringWithFormat:#"Isi: %#", biz.isiString];
self.Title.text = biz.Title; //Let's set this one thing first
self.Address.text=biz.ShortenedAddress;
//if([self.distance isNotEmpty]){
self.DistanceLabel.text=[NSString stringWithFormat:#"%dm",[biz.Distance intValue]];
self.PinNumber.text =biz.StringPinLineAndNumber;
NSString * URLString=nil;
if(biz.Images.allObjects.count!=0){
//self.boolImage=[NSNumber numberWithBool:true];
Image * image=(biz.Images.allObjects)[0];
URLString = image.URL;
URLString = [NSString stringWithFormat:#"http://54.251.34.144/thumbnailer/Thumbnailer.ashx?imageURL=%#",URLString.UTF8Encode];
//url=[NSURL URLWithString:image.URL];
}else{
float latitude = biz.getCllLocation.coordinate.latitude;
float longitude = biz.getCllLocation.coordinate.longitude;
URLString = [NSString stringWithFormat:#"http://maps.googleapis.com/maps/api/staticmap?&zoom=16&size=160x160&maptype=roadmap&sensor=true&center=%f,%f&markers=size:small|color:blue|%f,%f",latitude,longitude,latitude,longitude];
URLString = URLString.UTF8Encode;
}
//Should add code and add loading indicator here
[BGHPTools doBackground:^{
UIImage * imageBiz = [BGMDImageCacherAndDownloader getImageFromURL:URLString];
[BGHPTools doForeGround:^{
self.Image.image=imageBiz;
[self.Image makeRound];
}];
}];
//self.view=self.view;
/*if (self.tableViewCell == Nil)//Instantiate that tableviewCell
{
PO(self.tableViewCell);
}
self.tableViewCell.business = bis;
self.pinLbl.text = bis.StringPinLineAndNumber;
self.lblTitle.text=bis.Title;
//self.pinLbl.text=bis.pinNumber;*/
//}
/*self.name=[dict objectForKey:#"Title"];
self.address=[dict objectForKey:#"Street"];
CLLocation * cll=[[CLLocation alloc]initWithLatitude:[[dict objectForKey:#"Latitude"] doubleValue] longitude:[[dict objectForKey:#"Longitude"] doubleValue]];
self.distance=[NSNumber numberWithDouble:[cll distanceFromLocation:[cachedProperties currentLocation]]];*/
return self;
Update: I already figure out why the texts are gone. Turns out my background is white. When a row got selected, the text suddenly turn into white. So by setting selected style to blue I sort of get that "fixed".
However, I still do not see where in my code I specify that all label texts should be white if the underlying tableViewCell is selected.
After all, what's selected is the cell, not the label. How the hell the label knows that it has to turn white is beyond me.
If you are using a Storyboard to handle the interface, instead of using:
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
Try using
#pragma mark --- Push selectedObject to the detailView ---
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
RRAppDelegate *myAppDelegate = [[UIApplication sharedApplication]delegate];
if ([[segue identifier] isEqualToString:#"PushObjectSegue"]) {
NSIndexPath *selectedRowIndex = [self.tableView indexPathForSelectedRow];
RRObjectViewController *detailViewController = [segue destinationViewController];
detailViewController.selectedObject = [myAppDelegate.goals objectAtIndex:selectedRowIndex.row];
}
}
I was having the same problem with the method you used and instead used this, it started working perfectly. Of course you'd have to adapt the code to your app's viewControllers and data source because I used my AppDelegate as the datasource, and I wasn't using a custom cell.
The most likely thing is that a view in your custom cell is absorbing the touch. Sometimes this is what you want, e.g. a button that does something, rather than selecting the entire cell. Assuming you don't want this, then just set those views' userInteractionEnabled property to NO.
--Additional code for custom NIB loading.
All you have to do is register the NIB in your viewDidLoad routine:
[tableView registerNib: [UINib nibWithNibName:#"yourCellNibName" bundle:nil] forCellReuseIdentifier:#"yourCellTypeID"]
and then in your cellForRowAtIndexPath just call:
newCell = [tableView dequeueReusableCellWithIdentifier #"yourCellTypeID"];
...
return newCell;
And it will load a cell from your XIB (or give you one from the previously used queue).
I just want to update that I think I have figured out what the problem is but still can't solve that quite right yet. And well the update is comprehensive so I think it should be an answer though I hope it's not the answer because some puzzle is still missing.
All the problem is interrelated.
The problem is in this line:
[self addSubview:self.view];
I basically turn that into:
Basically the my custom view cell has a view whose type is also tableViewCell. That view cover the real tableViewCell.
That's why when user interaction is enabled, that view will absorb the user's interaction.
That's also why the label "disappear". What happen is the label doesn't disappear. The label got highlighted and become white. However, what's highlighted is the tableViewCell not the opague view. The white opague self.view is still white while the tableCell itself is tinted with blue. So the label becomes white in the middle of white background and is gone.
I think I should replace [self addSubview:self.view] into self= self.view
However, that would mean changing the value of self. Yes it's in init. But it's still awkward. If anyone has the WAY to implement custom subclass of UI with XIB it'll be great because I haven't found one till now.
Awkward.
I wonder if we can draw a pointer to an XIB and specify that the outlet is self itself.
If that fail, I'll set background of self to white and background of self.view to transparent.
After tons of error and trying I did this:
self.contentView.backgroundColor = [UIColor whiteColor];
//self.view.backgroundColor = [UIColor redColor];
self.frame =self.view.frame;
/*PO(self.view.subviews);
PO(self.subviews);
PO(self.Title.superview);
PO(self.Title);
PO(self.view);
PO(self.Title.superview);
PO(self.view.contentView);*/
//Suck all the subviews from my minions
for (UIView* aSubView in self.view.contentView.subviews) {
[self.contentView addSubview: aSubView];
//[self.contentView add]
}
Basically I "move" all the subViews of my view object to my self object. There is a catch though that when subclassing tableViewCell I should move the subviews of the contentView. Who knows why.
At the end I just set self.view to nil for it's no longer needed and my program works as expected.
Also to set background of your tableViewCell, you need also to set the background of self.contentView rather than self.view.
Another approach you can try is to use story board. Alternatively you can just move the contentView of the self.view to self.
Make sure you'r implementing that method and not
deselectRowAtIndexPath:animated

Unexpected behaviour when adding subviews to UICollectionViewCell

I'm currently facing a strange issue with UICollectionViewCell when adding subviews to it but only during certain situations.
Here is the scenario:
I have a "container" view which conforms to a very specific protocol (ADGControl) with a nested view, typically a UIKit control subclass I.e MyCustomTextField : UITextField for custom controls.
The "container" view exposes a property called "innerControlView" which holds a strong reference to the custom control which is what I'm trying to add as a sub view to the cell's content view.
Here is the code:
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
FormControlCollectionViewCell *cell = [self.formCollectionView dequeueReusableCellWithReuseIdentifier:#"formControlCell" forIndexPath:indexPath];
NSArray *sectionContents = [_controlList objectAtIndex:[indexPath section]];
// This works
//UITextField *textField = [[UITextField alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 315.0f, 30.0f)];
//textField.borderStyle = UITextBorderStyleLine;
//[cell.controlView addSubview:textField];
// This doesn't (see the behaviour in video clip)
id <ADGControl> control = [sectionContents objectAtIndex:[indexPath row]]; // The container view I'm referring to
[cell.contentView addSubview:(UIView *)[control innerControlView]]; // [control innerControlView] is the typical UIKit control subclass for custom controls. In this example it will be a UITextField
return cell;
}
As you can see in the code comments above, whenever I try to add just a UIKit control (textField) directly, it works just fine. However, as soon as I try to add my custom control ([control innerControlView] I get the unexpected behaviour as seen in the video clip here: http://media.shinywhitebox.com/ryno-burger/ios-simulator-ios-simulator-ipad-ios-a
The above link is just a short 23 seconds video clip to better demonstrate the "unexpected behaviour" that I get.
If anybody can point out what I'm doing wrong of what the issue might be I will be grateful.
Thanks
As you can read in the documentation on UICollectionViewCells, you shouldn't add content subviews to the cell itself, but to it's contentView.
And, like said before in my comment, you shouldn't add subviews in the data source, but in the subclass. You already noted that initWithFrame: wasn't called, use initWithCoder: instead:
-(id)initWithCoder:(NSCoder *)aDecoder {
self = [super initWithCoder:aDecoder];
if (self) {
// Add your subviews here
// self.contentView for content
// self.backgroundView for the cell background
// self.selectedBackgroundView for the selected cell background
}
return self;
}
A view can only be in one superview at once. If its already a subview of your container view, you can't just add it as a subview of another view (your cell).
It's not really clear why you're using a view as part of your model object, but you'll either have to change that or remove the inner view from its current superview before adding it to the cell.