How to position a UIToolbar in a UIWebView - objective-c

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)

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

UIView in front of everything not rotating with screen?

I have this code:
UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];
imageView.image = [UIImage imageNamed:#"image.png"];
UIApplication *app = [UIApplication sharedApplication];
[app addSubViewOnFrontWindow:imageView];
...
- (void)addSubViewOnFrontWindow:(UIView *)view {
int count = [self.windows count];
UIWindow *w = [self.windows objectAtIndex:count - 1];
[w addSubview:view];
}
The problem is, when the app rotates, the view on the front doesn't rotate. It just stays in portrait.
How can I get this to rotate normally with the rest of the device?
I haven't tried this, but UIWindow has a property rootViewController
The root view controller for the window.
#property(nonatomic, retain) UIViewController *rootViewController
Discussion
The root view controller provides the content view of the
window. Assigning a view controller to this property (either
programmatically or using Interface Builder) installs the view
controller’s view as the content view of the window. If the window has
an existing view hierarchy, the old views are removed before the new
ones are installed.
The default value of this property is nil.
Availability
Available in iOS 4.0 and later.
Declared In
UIWindow.h
As you should be providing this root view controller you should be able to add it to the rootViewContoller's view, and handle aout rotation correctly.
another solution could be, that you swap the windo with a custom one while presenting your view, with another view controller. this trick in action you can see in the implementation of TSAlertView.
- (void) show
{
[[NSRunLoop currentRunLoop] runMode: NSDefaultRunLoopMode beforeDate:[NSDate date]];
TSAlertViewController* avc = [[[TSAlertViewController alloc] init] autorelease];
avc.view.backgroundColor = [UIColor clearColor];
// $important - the window is released only when the user clicks an alert view button
TSAlertOverlayWindow* ow = [[TSAlertOverlayWindow alloc] initWithFrame: [UIScreen mainScreen].bounds];
ow.alpha = 0.0;
ow.backgroundColor = [UIColor clearColor];
ow.rootViewController = avc;
[ow makeKeyAndVisible];
// fade in the window
[UIView animateWithDuration: 0.2 animations: ^{
ow.alpha = 1;
}];
// add and pulse the alertview
// add the alertview
[avc.view addSubview: self];
[self sizeToFit];
self.center = CGPointMake( CGRectGetMidX( avc.view.bounds ), CGRectGetMidY( avc.view.bounds ) );;
self.frame = CGRectIntegral( self.frame );
[self pulse];
if ( self.style == TSAlertViewStyleInput )
{
[self layoutSubviews];
[self.inputTextField becomeFirstResponder];
}
}
with
#interface TSAlertOverlayWindow : UIWindow
{
}
#property (nonatomic,retain) UIWindow* oldKeyWindow;
#end
#implementation TSAlertOverlayWindow
#synthesize oldKeyWindow;
- (void) makeKeyAndVisible
{
self.oldKeyWindow = [[UIApplication sharedApplication] keyWindow];
self.windowLevel = UIWindowLevelAlert;
[super makeKeyAndVisible];
}
- (void) resignKeyWindow
{
[super resignKeyWindow];
[self.oldKeyWindow makeKeyWindow];
}
//
#end
add a new window, with new rootviewController, and subscribe it to
[[NSNotificationCenter defaultCenter] addObserver:(id) selector:(SEL) name:UIDeviceOrientationDidChangeNotification object:nil];
Windows don't autorotate. Ever. You should be adding this imageView to a UIViewController's view as the UIViewController has built in autorotation.
If you want to use a UIWindow to hold all your views, you will need to write your own transform code to handle rotations.

Fixing Header for UITableView

I want to fix the header for a UITableView such that the header sticks to the top when the table view is scrolled.
I did some googling and it seems that cleanest way is to use subclass a UINavigationController and make it follow the UITableDataSource and UITableViewDelegate protocols, and the in the view of the UINavigationController add the header view and the tableView and implement all the protocol methods.
basically I am doing exactly as the chosen answer for this stackoverflow question
I have in the didSelectRowAtIndexPath method the following:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
UIViewController *viewController2 = [[[SecondViewController alloc] initWithNibName:nil bundle:nil] autorelease];
viewController2.title = #"test";
[self pushViewController:viewController2 animated:YES];
}
If I have in the last line "self.navigationController" (as one normally would if this is a UITableViewController) instead of "self", nothing happens (of course, since self is the UINavigationController) but when I have "self" as it currently is, the view of the viewController# is not pushed, however something is happening because the navigation bar has changed with "test" appearing, but the fixed header and UITableView remains. More interestingly, when I tap the table view for the first time, only "test" appear in the navbar but not the "Back" button. I have to tap the rows another time (since the UITableView remains after I tap the first time) for the "Back" button to show in the navbar...
so in short
1) what should I do to make the view of viewController2 shows when i tap on the table view row?
2) how do I get the "Back" button to show at the first tap?
my init method of the UINavigationController is as follow:
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
[self.navigationController setNavigationBarHidden:NO];
UIView *mainView = [[[UIView alloc] initWithFrame:CGRectMake(0, statusBarHeight+navBarHeight, 320, 480-statusBarHeight-navBarHeight-tabBarHeight)] autorelease];
UIImage *stripedBackground = [UIImage imageNamed:#"bg.png"];
UIImageView *background = [[[UIImageView alloc] initWithImage:stripedBackground] autorelease];
background.frame = CGRectMake(0, 0, 320, mainView.frame.size.height);
[mainView addSubview:background];
UIImage *header = [UIImage imageNamed:#"header.png"];
UIImageView *headerView = [[[UIImageView alloc] initWithImage:header] autorelease];
headerView.frame = CGRectMake(0, 0, header.size.width, header.size.height);
[mainView addSubview:headerView];
UITableView *streamTableView = [[[UITableView alloc] initWithFrame:CGRectMake(0, header.size.height, 320, mainView.frame.size.height-header.size.height) style:UITableViewStylePlain] autorelease];
[streamTableView setDelegate:self];
[streamTableView setDataSource:self];
[mainView addSubview:streamTableView];
[header release];
[self.view addSubview:mainView];
}
return self;
}

UIToolbar Memory Leak

Currently I have a navigation based application and obviously the RootViewController is a UITableView. However, I deemed it necessary to create a UIToolbar that floats above the UITableView. Currently I do this like this.
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
//Initialize the toolbar
toolbar = [[UIToolbar alloc] init];
toolbar.barStyle = UIBarStyleDefault;
//Set the toolbar to fit the width of the app.
[toolbar sizeToFit];
//Caclulate the height of the toolbar
CGFloat toolbarHeight = [toolbar frame].size.height;
//Get the bounds of the parent view
CGRect rootViewBounds = self.parentViewController.view.bounds;
//Get the height of the parent view.
CGFloat rootViewHeight = CGRectGetHeight(rootViewBounds);
//Get the width of the parent view,
CGFloat rootViewWidth = CGRectGetWidth(rootViewBounds);
//Create a rectangle for the toolbar
CGRect rectArea = CGRectMake(0, rootViewHeight - toolbarHeight, rootViewWidth, toolbarHeight);
//Reposition and resize the receiver
[toolbar setFrame:rectArea];
//Create a button
UIBarButtonItem *infoButton = [[UIBarButtonItem alloc] initWithTitle:#"Settings" style:UIBarButtonItemStyleBordered target:self action:#selector(account_details)];
[toolbar setItems:[NSArray arrayWithObjects:infoButton,nil]];
//Add the toolbar as a subview to the navigation controller.
[self.navigationController.view addSubview:toolbar];
[infoButton release];
[self.tableView reloadData];
}
However, after using the Leaks instrument tool, I was able to determine that this was the cause for a few memory leaks, only small, but memory leaks nonetheless. I then drilled down even further and was able to pin point the exact lines that are causing the memory leaks. They are the following.
UIBarButtonItem *infoButton = [[UIBarButtonItem alloc] initWithTitle:#"Settings" style:UIBarButtonItemStyleBordered target:self action:#selector(account_details)];
[toolbar setItems:[NSArray arrayWithObjects:infoButton,nil]];
[self.navigationController.view addSubview:toolbar];
I am struggling to figure out how to remove these memory leaks and thus causing my application to run smoother. Any help would be appreciated as to why the above lines are causing leaks!
A new toolbar is created every time the view appears, added to the view and never released. This means that both that tool bar and its bar button item will last forever. You can fix this by simply releasing the toolbar after you add it to the view, or sending it the autorelease message when you create it. So, a decent way to do this would be to replace:
toolbar = [[UIToolbar alloc] init];
with:
toolbar = [[[UIToolbar alloc] init] autorelease];
Also, the way you're doing this, every time your view appears you end up adding another toolbar to the navigation controller's view. So you almost certainly have quite a few of these objects sitting on top of each other (so you will still see leaks until the navigation view finally goes away). What you might want to do is keep this toolbar as an ivar. When your view disappears, remove the toolbar from the nav controller's view. When it appears, add it. Create the toolbar itself in your viewDidLoad method and clean it up in viewDidUnload then release it in dealloc. So your new class might look like this (let's assume you create a synthesized property named toolbar that's retain):
- (void)viewDidLoad
{
[super viewDidLoad];
UIToolbar* toolbar = [[[UIToolbar alloc] init] autorelease];
// set up toolbar
[self setToolbar:toolbar];
// other load code
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[[[self navigationController] view] addSubview:[self toolbar]];
// other vwa code
}
- (void)viewDidDisappear:(BOOL)animated
{
[super viewDidDisappear:animated];
[[self toolbar] removeFromSuperview];
}
- (void)viewDidUnload
{
[self setToolbar:nil];
[super viewDidUnload];
}
- (void)dealloc
{
UIToolbar* toolbar = [self toolbar];
[toolbar removeFromSuperview]; // shouldn't ever need this, but be safe
[toolbar release];
// other dealloc
[super dealloc];
}

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;