UIButton is resized without visible cause? - iphone

I have ViewController(1) which hierarchy is like this
ViewController
- UIView
-- UITableView (height 80% of it's superview)
-- UIButton ( height 20% of it's superview, 80% offset y)
After pressing the UIButton new ViewController is pushed. Till now everything is okay, but in the moment of the animation I see that UIButton is resized to fit in the entire view. After going back now i have my button on the entire view.
What can cause this problem to appear?
1.
UIPopOverController
- UINavigationController
-- UIViewController
EDIT 1:
- (void)viewDidAppear:(BOOL)animated
{
// ...
_storeButton = [[UIButton alloc] initWithFrame:frame];
_storeButton.adjustsImageWhenHighlighted = NO;
_storeButton.backgroundColor = [UIColor colorWithHexString:(_productData.count > 0) ? #"0x8BC53F" : #"0xA4A4A4" ];
[_storeButton addTarget:self action:#selector(storeButtonTouched:) forControlEvents:UIControlEventTouchUpInside];
// ...
}
#pragma mark - Store request.
- (void)storeButtonTouched:(id)sender
{
DLog(#"_storeButton: %#", _storeButton);
if ( _productData != nil && _productData.count > 0) {
_productData = [NSArray arrayWithArray:_iapManager.productData];
TTIAPTableViewController *iapVC = [[TTIAPTableViewController alloc] initWithStyle:UITableViewStylePlain];
iapVC.entries = _productData;
iapVC.contentSizeForViewInPopover = self.view.frame.size;
[self.navigationController pushViewController:iapVC animated:YES];
[iapVC release];
} else {
DLog(#"No products available.");
}
}

Solved through using Containing ViewControllers.

Related

How to set a view to a tag set in code not in Interface Builder?

I have an app that uses 4 different xibs, lets call them 1-4
So you start on view 1, if you press the button it takes you to view 2, on view 2, you have a back button (which takes you to 1) and forward button that takes you to 3 etc
Anyway, I am removing the next page buttons, and have added a swipe control instead of pressing a button, you can swipe to the next page.
However, I need to know how I can call a tagged view, using the swipe.
At the moment, the UIButton for next page is set in IB as tag 1
This is my swipe code (this is page 1 so only has a swipe left)
- (IBAction)swipeLeftDetected:(UIGestureRecognizer *)sender {
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad){
Page2ViewController *UIViewController =
[[Page2ViewController alloc] initWithNibName:#"Page2ViewController~ipad" bundle:nil];
[self presentModalViewController:UIViewController animated:YES];
}else{
Page2ViewController *UIViewController =
[[Page2ViewController alloc] initWithNibName:#"Page2ViewController" bundle:nil];
[self presentModalViewController:UIViewController animated:YES];
Page2ViewController *VC = [[Page2ViewController alloc] initWithNibName:#"Page2ViewController" bundle:nil];
[self presentModalViewController:VC animated:YES];
[self.view removeGestureRecognizer:[self.view.gestureRecognizers lastObject]];
[VC release];
}
}
Whereabout in that code, can I tell it to swipe to tag 1?
Would appreciate any help :)
Thanks,
Chris
---- Updated FAO Rob;
In the appdelegate.m
- (void)swicthView:(int)viewControllerIndex :(CGRect)viewRect {
if (viewControllerIndex < 0 || viewControllerIndex > viewControllers.count) {
//invalid index passed to function - do nothing
}else{
if (subViewForceUseNibSize == NO) {
//pass the view frame size at runtime
if (CGRectIsEmpty(viewRect) || viewControllerIndex == 0) {
//no frame size so force full screen
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad){
viewRect =CGRectMake(0, 0, 320, 480);
}else{
viewRect = CGRectMake(0, 0, 768, 1024);
}
}
}else{
//force use the nib size, so reduce size of NIB to leave display of NIB main nib below
viewRect = ((UIViewController *)[viewControllers objectAtIndex:viewControllerIndex]).view.frame;
}
}
//swicth our view
if (viewControllerIndex == 0) {
/*
for (UIView *subview in window.rootViewController.view.subviews) {
[window.rootViewController.view sendSubviewToBack:subview];
}
*/
for (int x = 1; x<[viewControllers count]; x++) {
if (((UIViewController *)[viewControllers objectAtIndex:x]).view.superview != nil) {
[window.rootViewController.view sendSubviewToBack:((UIViewController *)[viewControllers objectAtIndex:x]).view];
}
}
[window bringSubviewToFront:((UIViewController *)[viewControllers objectAtIndex:0]).view];
return;
}
if (((UIViewController *)[viewControllers objectAtIndex:viewControllerIndex]).view.superview != nil) {
((UIViewController *)[viewControllers objectAtIndex:viewControllerIndex]).view.frame = viewRect;
[window.rootViewController.view bringSubviewToFront:((UIViewController *)[viewControllers objectAtIndex:0]).view];
[window.rootViewController.view bringSubviewToFront:((UIViewController *)[viewControllers objectAtIndex:viewControllerIndex]).view];
}else{
((UIViewController *)[viewControllers objectAtIndex:viewControllerIndex]).view.frame = viewRect;
[window.rootViewController.view bringSubviewToFront:((UIViewController *)[viewControllers objectAtIndex:0]).view];
[window.rootViewController.view addSubview:((UIViewController *)[viewControllers objectAtIndex:viewControllerIndex]).view];
}
}
Looking at the revised code sample, it is clear that there is a UIAppDelegate method called swicthView [sic] that is used for transitioning between five different view controllers, all of which are loaded simultaneously. Given this structure, it is advised that you have a property to keep track of which of the five pages is loaded, and based on the left or right swipe, invoke swicthView to transition to that controller. Thus:
#interface ViewController ()
#property (nonatomic) NSInteger currentPage;
#end
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
self.currentPage = 0;
UISwipeGestureRecognizer *gesture = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:#selector(handleLeftSwipe:)];
gesture.direction = UISwipeGestureRecognizerDirectionLeft;
[self.view addGestureRecognizer:gesture];
[gesture release];
gesture = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:#selector(handleRightSwipe:)];
gesture.direction = UISwipeGestureRecognizerDirectionRight;
[self.view addGestureRecognizer:gesture];
[gesture release];
// the rest of the viewDidLoad
}
- (void)handleLeftSwipe:(UISwipeGestureRecognizer *)gesture {
if (self.currentPage < 4)
{
++self.currentPage;
[UIAppDelegate swicthView:self.currentPage :CGRectZero];
}
}
- (void)handleRightSwipe:(UISwipeGestureRecognizer *)gesture {
if (self.currentPage > 0)
{
--self.currentPage;
[UIAppDelegate swicthView:self.currentPage :CGRectZero];
}
}
Frankly, I'd strong advise retiring the swicthView design and rather employing a custom container view controller. If you watch WWDC 2011 - Implementing a UIViewController containment, you'll see a good introduction about the importance of keeping a view controller hierarchy synchronized with a view hierarchy, and see some practical demonstrations of custom containers.
The original answer, provided below, was based upon the original snippet of code that was performing presentViewController. It turns out that a very different solution was called for, outlined above, but I retain the original answer for historical purposes:
Original answer:
I assume you have the following sort of code in viewDidLoad:
UISwipeGestureRecognizer *gesture = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:#selector(handleLeftSwipe:)];
gesture.direction = UISwipeGestureRecognizerDirectionLeft;
[self.view addGestureRecognizer:gesture];
And then you gesture handler could be:
- (void)handleLeftSwipe:(UISwipeGestureRecognizer *)gesture
{
NSString *nibName;
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad)
nibName = #"Page2ViewController~ipad";
else
nibName = #"Page2ViewController";
Page2ViewController *controller = [[Page2ViewController alloc] initWithNibName:nibName bundle:nil];
// if supporting iOS versions earlier than 5.0, then you should use:
//
// [self presentModalViewController:controller animated:YES];
//
// otherwise you should use presentViewController as done below.
[self presentViewController:controller animated:YES completion:nil];
[controller release];
}
Note, I'm don't remove the gesture (unless you really don't want the gesture there when you return back to this view, which is unlikely). Also note, I'm creating controller, presenting, and releasing.
I'm not understanding your repeated reference to tag properties in this context, as numeric tag values are used for identifying subviews of a view, not for identifying view controller or anything like that. So you say "UIButton for next page is set in IB as tag 1" and later you ask "Whereabout ... can I tell it to swipe to tag 1?" It doesn't make sense to "swipe to a button". You could, though, have the two handlers, the button's IBAction (which I'll call onPressNextButton ... I don't know what you called it) and the handleLeftSwipe call the same method, e.g.:
- (void)handleLeftSwipe:(UISwipeGestureRecognizer *)gesture
{
[self goToNextViewController];
}
- (IBAction)onPressNextButton:(id)sender
{
[self goToNextViewController];
}
- (void)goToNextViewController
{
NSString *nibName;
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad)
nibName = #"Page2ViewController~ipad";
else
nibName = #"Page2ViewController";
Page2ViewController *controller = [[Page2ViewController alloc] initWithNibName:nibName bundle:nil];
[self presentViewController:controller animated:YES completion:nil];
[controller release];
}
References:
presentViewController, the preferred method for modal transitions.
presentModalViewController, the now deprecated method that you use if you need backward compatibility for iOS versions prior to 5.0.
Naming basics in the Coding Guidelines for Cocoa, for advice in naming variables and methods. Note variables generally start with lowercase letters and classes generally start with uppercase letters.

How to access the views of the MoreViewController and change their titles?

on language switch within my App, I need to access the views of the MoreViewController in a TabBar and change their titles.
Could anyone pls tell me how to do that?
Your help is much appreciated.
Cols
Here are some snippets that might work for you. Note that all of the below is subject to break on each and every new iOS release at it is not meant to be done.
Customizing the More view title as itself as well as the title while it is being edited by the user.
- (void)customizeTitleViewWithNavigationItem:(UINavigationItem *)navigationItem
{
VASSERT(navigationItem != nil, #"invalid navigationItem supplied", navigationItem);
UILabel *titleView = [[UILabel alloc] initWithFrame:CGRectZero];
titleView.backgroundColor = [UIColor clearColor];
titleView.font = [UIFont boldSystemFontOfSize:20.0f];
titleView.text = navigationItem.title;
[titleView sizeToFit];
navigationItem.titleView = titleView;
[titleView release];
}
This needs to be implemented within the UINavigationController's delegate;
- (void)navigationController:(UINavigationController *)navigationController
willShowViewController:(UIViewController *)viewController
animated:(BOOL)animated
{
if (navigationController == tabBarController_.moreNavigationController)
{
if ([viewController isKindOfClass:NSClassFromString(#"UIMoreListController")])
{
[self customizeTitleViewWithNavigationItem:viewController.navigationItem];
}
else
{
NSLog(#"viewController (%#) does not seem to be a UIMoreListController", viewController);
}
}
else
{
NSLog(#"navigationController (%#) does not seem to be the moreNavigationController", navigationController);
}
}
This needs to be implemented within the UITabBarController's delegate;
- (void)tabBarController:(UITabBarController *)controller willBeginCustomizingViewControllers:(NSArray *)viewControllers
{
//get the second view of the upcoming tabbar-controller
UIView *editView = [controller.view.subviews objectAtIndex:1];
//did we get what we expected, which is a UITabBarCustomizeView?
if (editView != nil && [editView isKindOfClass:NSClassFromString(#"UITabBarCustomizeView")])
{ //yes->get the navigation-view
UIView *navigationView = [editView.subviews objectAtIndex:0];
//is that a navigationBar?
if (navigationView != nil && [navigationView isKindOfClass:[UINavigationBar class]])
{ //yes->...
UINavigationBar *navigationBar = (UINavigationBar *)navigationView;
[self customizeTitleViewWithNavigationItem:navigationBar.topItem];
}
else
{
NSLog(#"the navigationView (%#) does not seem to be a navigationBar", navigationView);
}
}
else
{
NSLog(#"the editView (%#) does not seem to be a UITabBarCustomizeView", editView);
}
}
I was able to change the title of one of the tabBar items for my 6th view controller (the last one in the more list) with the following code:
NSArray *vcs = [(UITabBarController *)self.window.rootViewController viewControllers];
[[vcs.lastObject tabBarItem] setTitle:#"New Title"];
Is this what you want to do?
After Edit: To change these titles after they are set up the first time, you need to re-set the viewControllers property of the tabBar controller. In this code example, I connected a button in my 6th view controller to an action method that changes the titles of 3 of my controllers, 2 in the more list and one in the regular list.
-(IBAction)changeNames:(id)sender {
UITabBarController *tbc = (UITabBarController *)[[UIApplication sharedApplication] delegate].window.rootViewController;
NSArray *vcs = tbc.viewControllers;
[[vcs.lastObject tabBarItem] setTitle:#"New Title"];
[[[vcs objectAtIndex:4] tabBarItem] setTitle:#"New VC"];
[[[vcs objectAtIndex:3] tabBarItem] setTitle:#"New VC2"];
tbc.viewControllers = tbc.viewControllers;
}

random UIView switcher

I have three different views in my iPhone app, and when the user presses a button, I need the view to change to either of the three views randomly. I've found this code, but i don't know how to load images..sorry but i'm newbie....!!
- (IBAction) yourBtnPressed : (id) sender
{
int i = arc4random() % 3;
if(i == 1)
{
//load first view
}
else if(i == 2)
{
//load second view
}
else
{
//load third view
}
}
if view are all in the same viewcontroller you can simply write
self.view = firstView;
or for more cool transition you can use these method
+ transitionWithView:duration:options:animations:completion:
+ transitionFromView:toView:duration:options:completion:
see apple documentation:
http://developer.apple.com/library/IOs/#documentation/UIKit/Reference/UIView_Class/UIView/UIView.html
If your views are in a .xib file you can use this code:
[self.view addSubview:aView];
If you think to create your views programmatically, then:
UIView *aView = [[UIView alloc] initWithFrame:aFrame];
// add contents to your aView here
[self.view addSubview:aView];
[aView release]; // if you don't need it anymore

UISplitView - load different detailView's for each row in masterView

I'm using UISplitViewController for app on iPad. The first task was to show master and detail view in portrait mode. I have done this like that:
// It is possible to keep the Master View in portrait mode
// also. Just pass YES to this method to enable this mode.
- (id) initWithMasterInPortraitMode:(BOOL) masterInPortrait {
self = [super init];
self.keepMasterInPortraitMode = masterInPortrait;
return self;
}
// Thanks to http://intensedebate.com/profiles/fgrios for this code snippet
-(void) viewWillAppear:(BOOL)animated {
NSLog(#"viewWillAppear");
if(keepMasterInPortraitMode == NO) {
return;
}
//check interface orientation at first view and adjust it
//if it is in portrait mode
if (self.interfaceOrientation == UIInterfaceOrientationPortrait || self.interfaceOrientation == UIInterfaceOrientationPortraitUpsideDown) {
UIViewController* master = [self.viewControllers objectAtIndex:0];
UIViewController* detail = [self.viewControllers objectAtIndex:1];
[self setupPortraitMode:master detail:detail];
}
}
// Thanks to http://intensedebate.com/profiles/fgrios for this code snippet
- (void)setupPortraitMode:(UIViewController*)master detail:(UIViewController*)detail {
//adjust master view
CGRect f = master.view.frame;
f.size.width = 320;
f.size.height = 1024;
f.origin.x = 0;
f.origin.y = 0;
[master.view setFrame:f];
//adjust detail view
f = detail.view.frame;
f.size.width = 448;
f.size.height = 1024;
f.origin.x = 321;
f.origin.y = 0;
[detail.view setFrame:f];
}
I create splitView like this:
MySplitViewController *mySplitViewController = [[MySplitViewController alloc] initWithMasterInPortraitMode:YES];
Ok, that works ok and when I launch app in portrait mode it shows both master and detail view side by side.
Everything works for now. But I want to show a different view in detailView for each record (row) in master view.
My didSelectRowAtIndexPath method in masterView looks like that:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
if (indexPath.row == 0) {
FirstDetailViewController *firstDetailView = [[FirstDetailViewController alloc] initWithNibName:#"firstDetailView" bundle:nil];
UINavigationController *firstDetailNavigationController = [[UINavigationController alloc] initWithRootViewController:firstDetailView];
[firstDetailView release];
[self.splitViewController setViewControllers:[NSArray arrayWithObjects:self.navigationController, firstDetailNavigationController, nil]];
}
else {
SecondDetailViewController *secondDetailView = [[SecondDetailViewController alloc] initWithNibName:#"secondDetailView" bundle:nil];
UINavigationController *secondDetailNavigationController = [[UINavigationController alloc] initWithRootViewController:secondDetailView];
[secondDetailView release];
[self.splitViewController setViewControllers:[NSArray arrayWithObjects:self.navigationController, secondDetailNavigationController, nil]];
}
}
After a click (touch) on first or second row (in masterView) splitView shows only detailView (whole screen) with no masterView.
How can I force splitView to show on change both views, master and detail view ???
Thanks for your help!
I have found the solution. In masterView I created splitViewController instance and assign local instance to that instance:
settingsSplitViewController = (SettingsSplitViewController *)self.splitViewController;
In method didSelectRowAtIndexPath I did this like that:
[settingsSplitViewController setupPortraitMode:self.navigationController detail:detailNavigationController];
Hope it will help someone else to solve this kind of problem.

Multiple UIViews, dealloc and retain

I have a small application for displaying several UIImageViews in a UIScroller in a similar fashion to the Photo app. I have a TableView which, when i select an item, parses an XML document of photos (added to an array) and adds a UIViewController which displays the images.
The problem is I have a tab bar controller which, upon clicking a tab bar item should go back to the TableView after remove the View that displays the images and dealloc all used memory. The problem is I can't work out how to achieve this. Whether it's a lack of understanding the memory rules I don't know.
Here's the process I'm having issues with.
Table View Controller
This is called after parserDidEndDocument:. backToMenu is called from the tab bar item.
#interface AlbumsViewController : UIViewController <UITableViewDelegate, UITableViewDataSource> {
AlbumViewController *albumViewController;
}
#property (nonatomic, retain) AlbumViewController *albumViewController;
- (void)displayPhotos {
albumViewController = [[AlbumViewController alloc] initWithNibName:#"AlbumView" bundle:nil];
albumViewController.parentView = self;
albumViewController.arrPhotos = self.arrCurrentSetOfPhotos;
[self.view addSubview:albumViewController.view];
[albumViewController populateScroller];
}
- (void)backToMenu {
[albumViewController.view removeFromSuperview];
}
Album View Controller
This is a UIViewController containing a UIScroller and UIPageControl. It adds several ImageViewControllers.
- (void)populateScroller {
imagesScroller.pagingEnabled = YES;
imagesScroller.contentSize = CGSizeMake(imagesScroller.frame.size.width * [self.arrPhotos count], 380);
imagesScroller.showsHorizontalScrollIndicator = NO;
imagesScroller.showsVerticalScrollIndicator = NO;
imagesScroller.scrollsToTop = NO;
imagesScroller.delegate = self;
imagesScroller.backgroundColor = [UIColor blackColor];
pageControl.numberOfPages = [self.arrPhotos count];
pageControl.currentPage = 0;
pageControl.backgroundColor = [UIColor blackColor];
NSMutableArray *controllers = [[NSMutableArray alloc] init];
for (int i = 0; i < [self.arrPhotos count]; i++) {
CGRect frame = imagesScroller.frame;
frame.origin.x = frame.size.width * i;
frame.origin.y = 0;
NSString *strImagePath = [self.arrPhotos objectAtIndex:i];
ImageViewController *imageViewController = [ImageViewController alloc];
imageViewController.localImage = YES;
[imageViewController initWithPhotoName:strImagePath];
[controllers addObject:imageViewController];
imageViewController.view.frame = frame;
[imagesScroller addSubview:imageViewController.view];
[imageViewController release];
}
self.viewControllers = controllers;
[controllers release];
}
ImageViewController
This has one method. It adds an subclassed UIView called ImageView which handles adding a UIImageView.
- (void)loadImage {
NSString *strRootURL = #"http://www.marklatham.co.uk";
CGRect rect = CGRectMake(0.0, 0.0, 320.0, 480.0);
ImageView *imageView = [ImageView alloc];
imageView.strURL = [strRootURL stringByAppendingString:self.strThisPhoto];
imageView.strTmpPrefix = (self.localImage) ? #"_tmp_rockphotothumb_" : #"_tmp_rockphotolarge_";
[imageView initWithFrame:rect];
[self.view addSubview:imageView];
[imageView release];
}
The backToMenu function is supposed to remove/dealloc AlbumViewController and call delloc on ALL child subviews freeing up the used memory.
Where am going wrong? Any help would be appreciated.
Thanks.
Your AlbumViewController is still holding a reference to the view, so it won't get released until you release that reference. But, this is a good thing.
Don't worry about freeing memory every time the user changes screens. It'll just slow things down as they flip back and forth between different views. If the system gets low on memory, it'll call didReceiveMemoryWarning on all of your view controllers, which will cause them to release their views if they aren't being displayed.
You just want to be a bit more clever about how you allocate/release the albumViewController
- (void)displayPhotos {
////// Only create this view controller if it doesn't exist yet
if(albumViewController == nil) {
albumViewController = [[AlbumViewController alloc] initWithNibName:#"AlbumView" bundle:nil];
}
albumViewController.parentView = self;
albumViewController.arrPhotos = self.arrCurrentSetOfPhotos;
[self.view addSubview:albumViewController.view];
[albumViewController populateScroller];
}
- (void)backToMenu {
[albumViewController.view removeFromSuperview];
//// if you don't do this, you'll have a retain cycle between the two
//// view controllers and neither will ever get released
albumViewController.parentView = nil;
}
- (void)didReceiveMemoryWarning {
///// if the albunViewController isn't in use, go ahead and free its memory
if([self.albumViewController isViewLoaded] && self.albumViewController.view.superview == nil) {
self.albumViewController = nil;
}
[super didReceiveMemoryWarning];
}
Also, instead of calling populateScrollers from AlbumsViewController, call it in the setArrPhotos method of AlbumViewController, but only if arrPhotos has changed. For example:
- (void)setArrPhotos:(NSArray *)newArrPhotos {
if(newArrPhotos != arrPhotos)
{
[arrPhotos release];
arrPhotos = [newArrPhotos retain];
[self populateScrollers];
}
}
This way, if the user goes to view the same album over and over again, you won't recreate the same view hierarchy over and over.
After convincing my client I managed to get around this by going with the three20 framework.