Objective-C: UILabel "stuck" in my view - objective-c

I'm working on a tabbed app that (among other things) creates a simple Facebook feed. The Facebook section is a UINavigationController holding a Master Table View and a Detail View. When I click one of the rows in the Master Table View, it brings up the detail view (and the appropriately filled labels) as intended. However, if I go back to the Master View and select another row, something strange happens. If the "detailLab" label (which is sizeToFit'd) was, say, four lines long on the first occurrence of Detail View, and the "detailLab" label was 2 lines long on the second occurrence of Detail View, then on the second occurrence of Detail View, "detailLab" will display the first two lines properly, but underneath them will be the last two lines from the first occurrence of Detail View.
A picture of this happening.
And some (hopefully) relevant code:
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
CGRect labelFrame = CGRectMake(100, 40, 220, 150);
detailLab = [[UILabel alloc] initWithFrame:labelFrame];
[detailLab setTextColor:[UIColor darkGrayColor]];
detailLab.font = [UIFont systemFontOfSize:14];
[detailLab setText:detail];
[detailLab setNumberOfLines:0];
[detailLab sizeToFit];
[self.view addSubview:detailLab];
NSURL *url = [NSURL URLWithString:pictureString];
NSData *imageData = [[NSData alloc] initWithContentsOfURL:url];
picture = [UIImage imageWithData:imageData];
bigImage.image = picture;
NSLog(#"dateString: %#", dateString);
dateLabel.text = dateString;
}
- (void)viewDidUnload
{
[super viewDidUnload];
bigImage = nil;
detailLab = nil;
detailLab.hidden = YES;
// Release any retained subviews of the main view.
self.detailDescriptionLabel = nil;
}
If you need any other code or details, just ask; I'd be happy to provide it.

The problem is that each time viewWillAppear is called you are creating a new label without, I assume, removing it in viewWillDisappear. So the labels are stacking up, one on top of the other.
Remember you may get called like this (ignoring irrelevant calls):
viewDidLoad
viewWillAppear
viewWillDisappear
viewWillAppear <- Whoops! Two labels
viweWillDisappear
viewDidUnload
A neater solution would be to add your label view in viewDidLoad and only alter the position of that already existing view in viewWillAppear. You still need to remember to dispose of the view in viewDidUnLoad.
Here's what I mean (note this was only eyeball compiled so you may need to correct some typos):
- (void)viewDidLoad
{
[super viewDidLoad];
detailLab = [[UILabel alloc] initWithFrame:CGRectZero];
[detailLab setTextColor:[UIColor darkGrayColor]];
detailLab.font = [UIFont systemFontOfSize:14];
[self.view addSubview:detailLab];
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
CGRect labelFrame = CGRectMake(100, 40, 220, 150);
[detailLab setFrame:labelFrame];
[detailLab setText:detail];
[detailLab setNumberOfLines:0];
[detailLab sizeToFit];
NSURL *url = [NSURL URLWithString:pictureString];
NSData *imageData = [[NSData alloc] initWithContentsOfURL:url];
picture = [UIImage imageWithData:imageData];
bigImage.image = picture;
NSLog(#"dateString: %#", dateString);
dateLabel.text = dateString;
}
- (void)viewDidUnload
{
[super viewDidUnload];
bigImage = nil;
detailLab = nil;
detailLab.hidden = YES;
// Release any retained subviews of the main view.
self.detailDescriptionLabel = nil;
}

That is normal with Master View to Detail Views - Frustrating, but Normal. The solution I found when I had the problem was to reset the label at the top of each -ViewDidLoad.
One option would be to remove it in viewDidUnload. Another would be to reset the label each time, using a segment of code like the one below, positioned above the section of your code where it actually loads in the content
detailLab = [NSString stringWithFormat:#""];
The issue is that with the setup of most Master/Detail views, it's not actually creating new detail views each time you add a view, it's just creating another link to the same view in the master view section. This is fine, but you just need to remember to use viewDidUnload lots
Hope it Helps

Related

Does it make sense to add ATMHud to tabBarController.view

When i try to add ATMHud to uitableviewcontroller subview it does work but it doesn't disable the scrolling and if the tableview is not on top i can't see the hud view. What i did was added to teh tabBarController.view that works but i want find out if this is a good idea or later on i might have issues with it.
Another question is tabBarController.view frame is that the whole screen or just the bottom part. How come atmhud shows in the middle of the screen?
Thanks in advance!
Yan
============
Found a blog post that shows how to reset self.view and add tableview separately in uitableviewcontroller
UITableViewController and fixed sub views
- (void)viewDidLoad {
[super viewDidLoad];
if (!tableView &&
[self.view isKindOfClass:[UITableView class]]) {
tableView = (UITableView *)self.view;
}
self.view = [[[UIView alloc] initWithFrame:
[UIScreen mainScreen].applicationFrame] autorelease];
self.tableView.frame = self.view.bounds;
self.tableView.contentInset = UIEdgeInsetsMake(44.0, 0.0, 0.0, 0.0);
[self.view addSubview:self.tableView];
UIView *fixedBar = [[UIView alloc] initWithFrame:
CGRectMake(0.0, 0.0, self.view.bounds.size.width, 44.0)];
fixedBar.backgroundColor = [UIColor colorWithRed:
0.0 green:1.0 blue:0.0 alpha:0.7];
[self.view addSubview:fixedBar];
[fixedBar release];
}
After this when add hud to self.view you will be able to disable the tableview on the bottom.
Let me know if this a good way to setup the tableview
The problem with using the tab bar is that the hud is now modal, and the user cannot change the tab.
It sounds like the tableview is not your primary viewm, as it can get "covered up". If its not the primary view, then add the ATMHud to self.view. If the tableView is the same as self.view, then add a new transparent view to it, then add the HUD to that view.
The tabBarController.view is the view that hosts the tabbed views - if you want to see its size (or frame) log it using NSStringFromCGRect(self.tabBarController.frame);
EDIT: I just did a test, the ATMHud DOES block the UI. All I can think of is that you have not inserted it where you need to (at the top of current view's subviews.) I have a demo project where I do this:
hud = [[ATMHud alloc] initWithDelegate:self];
[self.view addSubview:hud.view];
[hud setCaption:#"Howdie"];
[hud setActivity:YES];
[hud show];
[hud hideAfter:5];
A button under the hud is not active - in fact nothing in the view is active (probably the Nav Bar would be live though)
If you want an ARCified and field tested version, you can grab it here
EDIT2: The solution to your problem is below. Note that ATMHud blocks clicks from getting to the table, and the code below stops the scrolling:
- (void)hudWillAppear:(ATMHud *)_hud
{
self.tableView.scrollEnabled = NO;
}
- (void)hudDidDisappear:(ATMHud *)_hud
{
self.tableView.scrollEnabled = YES;
}
Dump the views:
#import <QuartzCore/QuartzCore.h>
#import "UIView+Utilities.h"
#interface UIView (Utilities_Private)
+ (void)appendView:(UIView *)v toStr:(NSMutableString *)str;
#end
#implementation UIView (Utilities_Private)
+ (void)appendView:(UIView *)a toStr:(NSMutableString *)str
{
[str appendFormat:#" %#: frame=%# bounds=%# layerFrame=%# tag=%d userInteraction=%d alpha=%f hidden=%d\n",
NSStringFromClass([a class]),
NSStringFromCGRect(a.frame),
NSStringFromCGRect(a.bounds),
NSStringFromCGRect(a.layer.frame),
a.tag,
a.userInteractionEnabled,
a.alpha,
a.isHidden
];
}
#end
#implementation UIView (Utilities)
+ (void)dumpSuperviews:(UIView *)v msg:(NSString *)msg
{
NSMutableString *str = [NSMutableString stringWithCapacity:256];
while(v) {
[self appendView:v toStr:str];
v = v.superview;
}
[str appendString:#"\n"];
NSLog(#"%#:\n%#", msg, str);
}
+ (void)dumpSubviews:(UIView *)v msg:(NSString *)msg
{
NSMutableString *str = [NSMutableString stringWithCapacity:256];
if(v) [self appendView:v toStr:str];
for(UIView *a in v.subviews) {
[self appendView:a toStr:str];
}
[str appendString:#"\n"];
NSLog(#"%#:\n%#", msg, str);
}
#end

How to position a UIToolbar in a UIWebView

I've a sample project, where I have created a custom UIWebView named WebView. They are added to the view in a UIViewController. Two WebView's are initialized in the viewDidLoad and added to an array list. The first one is added as subview på self.view. The Storyboard contains a UIBarButton, and when this is tapped, the second WebView is added as subview to the self.view.
- (void)viewDidLoad
{
[super viewDidLoad];
WebView *webView1 = [[WebView alloc] initWithFrame:self.view.bounds];
WebView *webView2 = [[WebView alloc] initWithFrame:self.view.bounds];
self.webViews = [NSArray arrayWithObjects: webView1, webView2, nil];
UIWebView *webView = [self.webViews objectAtIndex:0];
[self.view addSubview:webView];
[webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:#"http://m.google.com/"]]];
}
Here is the implementation of the WebView where a toolbar is added to the bottom (-50px) of the view:
#implementation WebView
- (void) initToolbar {
UIToolbar *toolbar = [UIToolbar new];
toolbar.barStyle = UIBarStyleBlackTranslucent;
toolbar.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleTopMargin;
[toolbar sizeToFit];
toolbar.frame = CGRectMake(0, self.frame.size.height-80, 320, 30);
[self addSubview:toolbar];
}
- (id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
self.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
[self initToolbar];
}
return self;
}
#end
The problem is, that the toolbar does not have the same position in the two views (they could be positioned like the first one). Why are they not positioned equally?
You can download the small sample project here:
http://uploads.demaweb.dk/UIWebView.zip
My notes: As mentioned, the two WebView's are currently initialized in the viewDidLoad. If I instead wait and first initialize then when I need them, it seems to work as expected.
The thing here is that self.frame.size.height on viewDidLoad is not the same when you initialize it via the button click. If you add the UIWebView directly in viewDidLoad, the UINavigationBar of your UINavigationController is not loaded yet and the frame of self.view is 'perceived' to be (not really, because it is) larger.
This explains the difference of 40.0 (?) points.
One possible solution to this problematic behaviour is to initialize and add (at least the first) instance of UIWebView in the viewWillAppear:animated method. (UIViewController does already implement this method)

Animate Spinner when loading new page

I have a UITableView which from an external RSS feed.
When you select a row it uses navigationController and slides in from the right, the problem is that the RSS feed contains images therefore it can can take a few seconds to load and without any indication of what is going on you can mistake it for an application crash.
I decided to add a spinner so that you know that new page is loading.
Here is my code:
RootViewController.m
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
NSLog(#"Loading New Page");
[tableView deselectRowAtIndexPath:indexPath animated:YES];
DetailsViewController *detailViewController = [[DetailsViewController alloc] initWithNibName:#"DetailsViewController" bundle:nil];
detailViewController.item = [rssItems objectAtIndex:floor(indexPath.row/2)];
[self.navigationController pushViewController:detailViewController animated:YES];
[detailViewController release];
UIActivityIndicatorView *spinner = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
spinner.center = CGPointMake(160, 240);
[self.view addSubview:spinner];
[spinner startAnimating];
[spinner release];
}
DetailsViewController.m
- (void)viewDidLoad {
[super viewDidLoad];
NSString *imgURL = [item objectForKey:#"image"];
NSData *mydata = [[NSData alloc] initWithContentsOfURL:[NSURL URLWithString:imgURL]];
item_photo.image = [[UIImage alloc] initWithData:mydata];
item_title.text = [item objectForKey:#"title"];
item_date.text = [NSString stringWithFormat:#"Date: %#",[item objectForKey:#"date"]];
item_time.text = [NSString stringWithFormat:#"Time: %#",[item objectForKey:#"time"]];
item_cost.text = [NSString stringWithFormat:#"Cost: £%#",[item objectForKey:#"cost"]];
item_info.text = [item objectForKey:#"description"];
self.navigationItem.title = #"Event Type";
}
There are two problems with this code.
The Spinner does not active until after the new page has loaded.
The Spinner does not disable once loaded.
If anyone could help me with this problem i would be truly gratefully.
You are adding the activity indicator view to the view of the controller which is pushing the detail view controller, so you wont see it anyway
try moving the second group of code to the viewDidLoad method of DetailsViewController, you can call stopAnimating on the activity indicator when you are finished loading. To get a reference to the UIActivityIndicator you should add a tag
e.g. in viewDidLoad
UIActivityIndicatorView *spinner = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
spinner.center = CGPointMake(160, 240);
spinner.tag = 12;
[self.view addSubview:spinner];
[spinner startAnimating];
[spinner release];
in the loadingFinished method (whichever method is called when finished loading)
[[self.view viewWithTag:12] stopAnimating];
You need to do some work in a background thread. If the following line is the one that takes the time:
detailViewController.item = [rssItems objectAtIndex:floor(indexPath.row/2)];
Then you could do this in the background with GCD:
UIActivityIndicatorView *spinner = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
dispatch_async(dispatch_get_global_queue(0, 0), ^{
// This is the operation that blocks the main thread, so we execute it in a background thread
id item = [rssItems objectAtIndex:floor(indexPath.row/2)];
// UIKit calls need to be made on the main thread, so re-dispatch there
dispatch_async(dispatch_get_main_queue(), ^{
detailViewController.item = item;
[spinner stopAnimating];
});
});
And +1 to #wattson12 - you need to add the spinner to the new view instead. Alternatively you could add the spinner to the current view, and instead put the pushViewControllercall into your GCD main-queue block.
Final point - you'll want to remove the spinner from its superview once you stop it animating. Alternatively, you can have a single instance of the spinner, and set hidesWhenStopped to YES.
This is a spinning wheel over a blurred view in swift:
func blurScence(){
let blurEffect: UIBlurEffect = UIBlurEffect(style: .Dark)
let blurView: UIVisualEffectView = UIVisualEffectView(effect: blurEffect)
blurView.translatesAutoresizingMaskIntoConstraints = false
blurView.frame = self.view.frame
let spinner = UIActivityIndicatorView(activityIndicatorStyle:.White)
spinner.center=blurView.center
blurView.addSubview(spinner)
spinner.startAnimating()
self.view.addSubview(blurView)
}

Hide button on first of two UIViews, but have it visible on second

So I have a UIViewController (main application controller is a TabBarController). On this there is a UINavigationBar, and a UIBarButtonItem. I'm PRETTY sure I hooked up everything correctly in the Interface Builder and that the outlet in the code is connected to the button in the .xib. It should be because the method works correctly.
Now I have another button on this view that brings up a second view, a UIWebView. I want this UIBarButtonItem, labeled "Back", to make the UIWebView dissapear, and bring back the first UIView, which it DOES DO correctly. However, when you are on the first UIView, there is no need to see the UIBarButtonItem, so how can I hide it but then bring it up for the UIWebView. By the way, both views use the same UINavigationBar, the UIWebView is brought up inside the tab bar and the nav bar.
Here is my code:
#import "WebViewController.h"
#implementation WebViewController
#synthesize webButton;
#synthesize item;
#synthesize infoView;
UIWebView *webView;
+ (UIColor*)myColor1 {
return [UIColor colorWithRed:0.0f/255.0f green:76.0f/255.0f blue:29.0f/255.0f alpha:1.0f];
}
// Creates Nav Bar with default Green at top of screen with given String as title
+ (UINavigationBar*)myNavBar1: (NSString*)input {
UIView *test = [[UIView alloc] initWithFrame:[[UIScreen mainScreen] applicationFrame]];
UINavigationBar *navBar = [[UINavigationBar alloc] initWithFrame:CGRectMake(0.0, 0.0, test.bounds.size.width, 45)];
navBar.tintColor = [WebViewController myColor1];
UINavigationItem *navItem;
navItem = [UINavigationItem alloc];
navItem.title = input;
[navBar pushNavigationItem:navItem animated:false];
return navBar;
}
- (IBAction) pushWebButton {
self.navigationItem.rightBarButtonItem = item;
CGRect webFrame = CGRectMake(0.0, 45.0, 320.0, 365.0);
webView = [[UIWebView alloc] initWithFrame:webFrame];
[webView setBackgroundColor:[UIColor whiteColor]];
NSString *urlAddress = #"http://www.independencenavigator.com";
NSURL *url = [NSURL URLWithString:urlAddress];
NSURLRequest *requestObj = [NSURLRequest requestWithURL:url];
webView.scalesPageToFit = YES;
[webView loadRequest:requestObj];
[self.view addSubview:webView];
[webView release];
}
- (void) pushBackButton {
[webView removeFromSuperview];
}
// Implement viewDidLoad to do additional setup after loading the view, typically from a nib.
- (void)viewDidLoad
{
self.navigationItem.rightBarButtonItem = nil;
[super viewDidLoad];
}
#end
Anyone know?
Edit: This answer does not work with nil, but I'm leaving it here as it does work when you want to temporarily replace the back button with another button. See correct answer below in comments.
You might try something like this:
In an App I'm working on there are cases where I'd like to temporarily swap the back button for a cancel button,
so I save a pointer to it:
tempButtonItem = self.navigationItem.leftBarButtonItem;
change the navigationItem.leftBarButtonItem to a cancel button:
self.navigationItem.leftBarButtonItem = [[[UIBarButtonItem alloc]
initWithBarButtonSystemItem:UIBarButtonSystemItemCancel
target:self
action:#selector(cancelButtonPressed)]
autorelease];
And then later when I want to have the back button again I restore it:
self.navigationItem.leftBarButtonItem = self.tempButtonItem;

I get EXC_BAD_ACCESS when MaxConcurrentOperationCount > 1

Hello i am using NSOperationQueue to download images in the background. I have created a custom NSOperation to download the images. I put the images in table cells. The problem is if I do [operationQueue setMaxConcurrentOperationCount: 10] and i scroll down several cells the program crashes with EXC_BAD_ACCESS. Every time it crashes at the same place in the table. There are 3 cells one after the other which are for the same company and have the same logo so basically it should download the images 3 times. Every other time it works fine.
- (void) main
{
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
NSURL *url = [[NSURL alloc] initWithString:self.imageURL];
debugLog(#"downloading image: %#", self.imageURL);
//NSError *error = nil;
NSData *data = [[NSData alloc] initWithContentsOfURL:url];
[url release];
UIImage *image = [[UIImage alloc] initWithData:data];
[data release];
if (image)
{
if (image.size.width != ICONWIDTH && image.size.height != ICONHEIGHT)
{
UIImage *resizedImage;
CGSize itemSize = CGSizeMake(ICONWIDTH, ICONHEIGHT);
//!!! UIGraphicsBeginImageContext NOT THREAD SAFE
UIGraphicsBeginImageContext(itemSize);
CGRect imageRect = CGRectMake(0.0, 0.0, itemSize.width, itemSize.height);
[image drawInRect:imageRect];
resizedImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
self.theImage = resizedImage;
}
else
{
self.theImage = image;
}
[image release];
}
[delegate didFinishDownloadingImage: self];
[pool release];
}
This is how i handle downloading the images. If i comment out
[delegate didFinishDownloadingImage: self];
in the function above it doesn't crash but of course it is useless.
-(void) didFinishDownloadingImage:(ImageDownloadOperation *) imageDownloader
{
[self performSelectorOnMainThread: #selector(handleDidFinishDownloadingImage:) withObject: imageDownloader waitUntilDone: FALSE];
}
-(void) handleDidFinishDownloadingImage:(ImageDownloadOperation *)imageDownloadOperation
{
NSArray *visiblePaths = [self.myTableView indexPathsForVisibleRows];
CompanyImgDownloaderState *stateObject = (CompanyImgDownloaderState *)[imageDownloadOperation stateObject];
if ([visiblePaths containsObject: stateObject.indexPath])
{
//debugLog(#"didFinishDownloadingImage %# %#", imageDownloader.theImage);
UITableViewCell *cell = [self.myTableView cellForRowAtIndexPath: stateObject.indexPath];
UIImageView *imageView = (UIImageView *)[cell viewWithTag: 1];
if (imageDownloadOperation.theImage)
{
imageView.image = imageDownloadOperation.theImage;
stateObject.company.icon = imageDownloadOperation.theImage;
}
else
{
imageView.image = [(TestWebServiceAppDelegate *)[[UIApplication sharedApplication] delegate] getCylexIcon];
stateObject.company.icon = [(TestWebServiceAppDelegate *)[[UIApplication sharedApplication] delegate] getCylexIcon];
}
}
}
According to this mailing list post, UIGraphicsBeginImageContext isn't thread safe. The post hints that CGBitmapContextCreate and related functions are the safe way to do it.
http://osdir.com/ml/cocoa-dev/2009-10/msg00035.html
I think you're crashing because you're trying to access cells in the tableview that are not there.
Regardless of how long the logical table is, the visual tableview only holds enough cells to display the section of the logical table currently on screen. At the default row size, a table shows and therefore contains, only 9 to 10 cell objects. For example, if you have a logical table that is 100 rows long and your view is showing rows 11-20 the table only has 9 cell objects. If you show rows 89-98, it only has the exact same 9 cell objects. No matter which rows you display, you see the same 9 cell objects over and over again. The only thing that changes is the data they display.
If you try to access the cell for a logical row off screen, you won't get anything back. In your case, you try to access the 11th logical row but there is no 11th cell and there never will be.
I think you have some conceptual confusion because you're trying to store data in the tableview itself by setting the cell contents. This will not work because tableviews do not store any data beyond what is immediately displayed. When a tableview needs to display more rows as you scroll, it reuses the existing cell and its DataSource delegate changes the data that the existing cells display.
Instead of storing the images in the cells, you need to create a data model and store the images there. Then when the tableview scrolls, it will send tableview:cellForRowAtIndexPath: to its datasource delegate. The datasource delegate will then ask the data model for the data for the logical row, populate the reused cell and return it to the tableview.
Ok it seems it is solved thanks to codewarrior. This is the part that changed.
#synchronized(delegate)
{
UIImage *resizedImage;
UIGraphicsBeginImageContext(itemSize);
CGRect imageRect = CGRectMake(0.0, 0.0, itemSize.width, itemSize.height);
[image drawInRect:imageRect];
resizedImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
self.theImage = resizedImage;
}