Scroll View inside a View - iphone

i need your help. Basically I created a small scrollView and a pageControl inside my main view controller. Now when ever a button inside a scroll view is pressed I lose the value of every property in my mainViewController. To help you get a clearer picture let me explain:
(NoteViewController.m) This is the action the button that is pressed from the scrollview responds to
- (IBAction)removePerson:(UIButton *)sender {
MainViewController *remover = [[MainViewController alloc] init];
[remover removePersonWithPage:pageNumber];
[self.view removeFromSuperview];
[remover release]; }
(MainViewController.m)
- (void)removePersonWithPage:(int)page {
// The managedObjectContext is lost the moment it leaves MainViewController.m and goes to NoteViewController.m
// so you need to reload the managedObjectContext
if (managedObjectContext == nil)
{
managedObjectContext = [(OrdersAppDelegate *)[[UIApplication sharedApplication] delegate] managedObjectContext];
}
// Get the list of people (Persons) from the managed Object Context
arrayOfPeople = [[NSMutableArray alloc] initWithArray:[self fetchDataWithEntity:#"Person" andSortKey:#"pageId"]];
// Find a specific person to delete using their page number and delete it finally remove it from array
NSManagedObject *personToDelete = [arrayOfPeople objectAtIndex:page];
[managedObjectContext deleteObject:personToDelete];
[arrayOfPeople removeObjectAtIndex:page];
// kNumberOfPages is replaced with the new number of people
kNumberOfPages = arrayOfPeople.count;
/* This is where problem occurs */
self.pageControl.numberOfPages = kNumberOfPages;
NSLog(#"The number of pages in the page control in remove is: %d", self.pageControl.numberOfPages);
[self saveObjectContext];
}
So everything works but when I get to the NSLog at the end there, it returns 0 when it ought to be returning the number of pages in the database. I've been working on this for days now and can't figure it out, please help. Thanks

Related

Need Help with applicationDidBecomeActive

I have been trying for days to get this code to work, but I have no idea what I am doing wrong. Everytime the app wakes up from sleep, or the user closes the app and opens it again (without closing the app from multitasking), I want a label value to change.
In my applicationDidBecomeActive, I am running a counter, which I want to display on whatever viewcontroller is open at that moment.
Code:
- (void)applicationDidBecomeActive:(UIApplication *)application {
counter = counter + 1;
W1G1 *view1 = [[[W1G1 alloc] initWithNibName:#"W1G1" bundle:nil] retain];
[view1 setlabel];
}
In my viewcontroller W1G1, I have the following code:
Code:
- (void) setlabel {
NSString *string = [NSString stringWithFormat:#"%d", counter];
vocabword.text = string;
}
I have imported W1G1 in my appdelegate, but the code does not run :( Please help!
Thanks
In the AppDelegate.m file, where you have
- (void)applicationDidBecomeActive:(UIApplication *)application {
counter = counter + 1;
W1G1 *view1 = [[[W1G1 alloc] initWithNibName:#"W1G1" bundle:nil] retain];
[view1 setlabel];
}
the variable counter being incremented is confined to the AppDelegate. In other words, your view controller doesn't know that it has been incremented.
I would suggest that you use NSUserDefaults to store the value of counter so that you can easily pass it between these view controllers. Either that, or you could allow for an input into the method setLabel, e.g.
- (void) setlabel:(int)counter {
NSString *string = [NSString stringWithFormat:#"%d", counter];
vocabword.text = string;
}
and then in the AppDelegate you'll want to do:
- (void)applicationDidBecomeActive:(UIApplication *)application {
counter = counter + 1;
W1G1 *view1 = [[[W1G1 alloc] initWithNibName:#"W1G1" bundle:nil] retain];
[view1 setlabel:counter]; // <-- now you're using counter
[self.window addSubview:view1];
}
1) When you say 'the code does not run' do you mean that? That is, if you put NSLogs in applicationDidBecomeActive: and in setLabel does it show the code is run?
2) I would suspect the code is running. But your code won't "show the counter on whatever view controller is open at that moment". Your code creates a new view (view1), but that view won't be displayed. It is not added as a subview to anything. Your code will also leak. You create a W1G1 object, but it is never released and you throw away any reference you have to it.
To achieve what you want, you could add a subview to the application's window. Depending how your app delegate is set up, something like the following should do the trick:
counter++;
W1G1 *viewController1 = [[W1G1 alloc] initWithNibName:#"W1G1" bundle:nil];
[viewController1 setlabel: counter];
[[self window] addSubview: [viewController1 view]]
// you'll want to save a reference to the viewController somehow so you can release it at a later date
Then in W1G1
- (void) setlabel: (int) counter;
{
NSString *string = [NSString stringWithFormat:#"%d", counter];
vocabword.text = string;
}
There are, of course, lots of other approaches you could take towards this problem. And you'll need some strategy for removing the W1G1 view that you are adding at some stage, otherwise you'll just get more and more views added.
Update: You ask (in comments) how to keep track of your viewController throughout lifetime of the app... One approach is to keep track of it in your appDelegate. In the header have something like:
#class W1G1;
#interface MyAppDelegate : : NSObject <UIApplicationDelegate>
{
// other decelerations
int counter;
W1G1 * _myW1G1
}
#property (nonatomic, retain) W1G1* theW1G1
In the .m file include
#synthesize theW1G1 = _myW1G1;
Probably in application:didFinishLaunchingWithOptions: create the viewController, set the property to refer to it, and add its view to the view hierarchy.
W1G1* theViewController = [[W1G! alloc] initWithNibName: #"W1G1" bundle: nil];
[[self window] addSubview: [theViewController view]];
[self setTheW1G1: theViewController];
[theViewController release];
Then when you want to access the viewController again from with the app delegate use [self theW1G1], e.g.
[[self W1G1] setlabel: counter];

TableView obstructed by Titlebar

I'm building my first basic tabbed, application with one of the views as a navigation controller that will display a view controller.
I'm running into an issue at the point the user selects a category from the first tableview as shown in the screenshot: http://www.cl.ly/7YOF
When another instance of the tableviewcontroller is loaded and pushed onto the stack of the navigationcontroller, the table is obstructed by the title bar:
http://www.cl.ly/7ZRz
The table view select logic is below:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
KHCategory *selectedItem = [categoryArray objectAtIndex:indexPath.row];
if (selectedItem.categories.count > 0) {
KHCategoryTableViewController *nextCategoryController = [[KHCategoryTableViewController alloc] init];
nextCategoryController.categoryArray = [[NSArray alloc] initWithArray:selectedItem.categories];
nextCategoryController.title = selectedItem.labelValue;
[self.navigationController pushViewController:nextCategoryController animated:YES];
[nextCategoryController release];
} else {
NSLog(#"show detail view");
}
}
EDIT:
I should be clear that an instance of KHCategoryTableViewController is the root of my NavigationController and the NavController is wired up to the first tab of a TabController.
Two interesting things: it measures 20 pixels down (size of status bar) and your line "nextCategoryController.title = ..." doesn't seem to do anything. So...
1) I assume you haven't used setStatusBarHidden?
2) Looks like navController stuff isn't working. Can you give the code from the appDelegate that creates the tabBar and NavController?
3) Add this code, and try calling [self dumpWindow: #"VDL"] from your Subcategory ViewDidLoad method. I find it invaluable whenever checking whether my view structure is correct.
- (void) dumpWindowFrom:(NSString *) fromText {
[self dumpViews: nil from:fromText];
}
void dumpViewsRecursive(UIView* view, NSString *text, NSString *indent) {
Class cl = [view class];
NSString *classDescription = [cl description];
if ([text compare:#""] == NSOrderedSame)
NSLog(#"%d: %# %# %#", (int)view, classDescription, NSStringFromCGRect(view.frame), view.hidden ? #"Inv" : #"Vis");
else
NSLog(#"%d: %# %# %# %#", (int)view, text, classDescription, NSStringFromCGRect(view.frame), view.hidden ? #"Inv" : #"Vis");
for (NSUInteger i = 0; i < [view.subviews count]; i++)
{
UIView *subView = [view.subviews objectAtIndex:i];
NSString *newIndent = [[NSString alloc] initWithFormat:#" %#", indent];
NSString *msg = [[NSString alloc] initWithFormat:#"%#%d:", newIndent, i];
dumpViewsRecursive (subView, msg, newIndent);
[msg release];
[newIndent release];
}
}
- (void) dumpViews: (UIView *) view {
dumpViewsRecursive (( (!view) ? [[UIApplication sharedApplication] keyWindow] : view), #"" ,#"");
}
- (void) dumpViews: (UIView *) view from:(NSString *) fromText{
dumpViewsRecursive ((!view) ? [[UIApplication sharedApplication] keyWindow] : view, fromText, #"");
}
4) You could always just cheat and add:
CGRect frame = [nextCategoryController.view frame];
frame.origin.y = frame.origin.y+20.0;
[nextCategoryController.view setFrame:frame];
Check the autoResizingMask of your KHCategoryTableViewController's view.
UINavigationController overview at iPhone Dev Center says:
Note: Because the amount of space
available for the custom view can vary
(depending on the size of the other
navigation views), your custom view’s
autoresizingMask property should be
set to have a flexible width and
height. Before displaying your view,
the navigation controller
automatically positions and sizes it
to fit the available space.
This issue became resolved when I built against iOS 4.3 and not iOS 5.

Refresh UINavigationController?

I have a UINavigationController with two ViewControllers on the stack. At a certain point in the program execution, the second view controller is visible on the screen and at that moment, I would like to replace that ViewController with another. However, it's not working. Here is my code:
UINavigationController * thisNavController = self.waitingController;
// remove the Dummy and set the new page instead
NSMutableArray * newControllers = [NSMutableArray arrayWithArray: thisNavController.viewControllers];
[newControllers replaceObjectAtIndex: ([thisNavController.viewControllers count] - 1) withObject: page];
NSLog (#"visible before: %#", [thisNavController.visibleViewController description]);
[thisNavController setViewControllers: [NSArray arrayWithArray: newControllers] animated: YES];
NSLog (#"visible after: %#", [thisNavController.visibleViewController description]);
[thisNavController.visibleViewController.view setNeedsDisplay];
The above code produces this output:
2011-05-05 13:30:22.201 myApp[3286:207] visible before: <DummyViewController: 0x4c8b4c0>
2011-05-05 13:30:22.209 myApp[3286:207] visible after: <RealViewController: 0x60173f0>
But what is shown on the screen does not change. It seems that everything works fine after I switch tabs, so it seems that it is a redrawing problem, but setNeedsDisplay does nothing and I couldn't find a method that tells the NavigationController that its viewControllers have changed.
Is there some refresh mechanism that I have to trigger to refresh the screen?
One solution would be to say add 2 (initial) view controllers when your app is started, and only allow navigation from the 2nd and 3rd ones, falling back to the 1st (root) view controller in your senario described. You never allow navigation back to this 1st view controller or from this 1st view controller to the 2nd; you see this sort of behaviour in some of Apple's apps, like iTunes and Remote - if there's no network connect the app shows a no-network connection view immediately.
So, when you want to show the 1st view controller above, you do something like:
NSArray *array = [navigationController popToRootViewControllerAnimated:NO];
Without more info about the navigation behaviour of your app I hope this helps.
Or show a modal view controller?
The problem turned out to be the fact that I was trying to replace the view controller stack before the initial transition animation for the Dummy controller has finished. This can be prevented in the following manner.
First, preserve the (eventual) delegate, set the current object as the delegate, set a flag that animation is in progress and push the new controller:
self.oldNavigationControllerDelegate = self.waitingController.navigationController.delegate;
self.waitingController.navigationController.delegate = self;
self.isAnimating = YES;
[viewController.navigationController pushViewController: [[DummyViewController alloc] init] animated: YES];
Then, implement the UIViewControllerDelegate protocol methods as follows:
#pragma mark -
#pragma mark UINavigationControllerDelegate methods
- (void) navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated {
if (navigationController == self.waitingController.navigationController)
self.isAnimating = YES;
}
- (void) navigationController:(UINavigationController *)navigationController didShowViewController:(UIViewController *)viewController animated:(BOOL)animated {
if (navigationController == self.waitingController.navigationController) {
self.isAnimating = NO;
if (self.readyPage != nil)
[self pageIsReady: self.readyPage]; // method to load the ready controller
}
}
After that, whenever your content/controller/download/whatever is ready, make sure that the navigation controller is no longer animating. If it is, set a flag that the page is ready. If it isn't, load the page:
if (self.isAnimating)
self.readyPage = controller;
else
[self pageIsReady: controller];
And, of course, implement the actual loading of the new stack (as usual):
- (void) pageIsReady: (UIViewController *) page {
// this method should replace the dummy that is spinning there
UINavigationController * thisNavController = self.waitingController.navigationController;
// remove the Dummy and set the new page instead
NSMutableArray * newControllers = [NSMutableArray arrayWithArray: thisNavController.viewControllers];
[newControllers replaceObjectAtIndex: ([thisNavController.viewControllers count] - 1) withObject: page];
thisNavController.viewControllers = [NSArray arrayWithArray: newControllers];
thisNavController.delegate = self.oldNavigationControllerDelegate; // restore the original delegate
// clean up
self.isAnimating = NO;
self.readyPage = nil;
self.waitingController = nil;
self.oldNavigationControllerDelegate = nil;
}
This makes everybody happy :P

UITableView reloadData causing all animations to stop working

Alright this is a really weird one so I am going to layout what is happening and then give some code after. For my example I am going to use a static amount of views, 2.
The Basics
I have a UIPageControl with X many Subviews added. On each subviews viewDidLoad is an NSXMLParse to grab an XML feed. Once the feed is obtained, it's parsed and the table is reloaded using the parsed array. There is also a Settings button on each view. When the Settings button is pressed, UIModalTransitionStyleCoverVertical:Animated:YES is run and a UINavigationController slides up into view with full animation. Dismiss also shows animation sliding out back to the previous view. If you are in Settings, you can PushViews two levels deep (Slide In Animation).
The Problem
A random amount of the time, when the app is built and run (Not Resumed) when you tap the Settings button, the Animation does not occur. Everything is functional except for all Core Animations are removed. DismissModal simply swaps back to the previous screen. PushView in the NavigationController no longer has any animation, the next view simply appears.
If you quit the app (Kill Process) and relaunch it, it may work fine for a period of time but at some point when you tap the Settings button, it will lose all animations.
The Details
I started with Apples PageControl Application for the groundwork. It creates a dynamic amount of views based on user settings.
- (void)awakeFromNib
{
kNumberOfPages = 2;
// view controllers are created lazily
// in the meantime, load the array with placeholders which will be replaced on demand
NSMutableArray *controllers = [[NSMutableArray alloc] init];
for (int i = 0; i < kNumberOfPages; i++)
{
[controllers addObject:[NSNull null]];
}
self.viewControllers = controllers;
// a page is the width of the scroll view
scrollView.pagingEnabled = YES;
scrollView.contentSize = CGSizeMake(scrollView.frame.size.width * kNumberOfPages, scrollView.frame.size.height);
scrollView.showsHorizontalScrollIndicator = NO;
scrollView.showsVerticalScrollIndicator = NO;
scrollView.scrollsToTop = NO;
scrollView.delegate = self;
pageControl.numberOfPages = kNumberOfPages;
pageControl.currentPage = 0;
// pages are created on demand
// load the visible page
// load the page on either side to avoid flashes when the user starts scrolling
[self loadScrollViewWithPage:0];
[self loadScrollViewWithPage:1];
}
- (void)loadScrollViewWithPage:(int)page
{
if (page < 0)
return;
if (page >= kNumberOfPages)
return;
// replace the placeholder if necessary
SecondViewController *controller = [viewControllers objectAtIndex:page];
if ((NSNull *)controller == [NSNull null])
{
controller = [[SecondViewController alloc] initWithPageNumber:page];
[viewControllers replaceObjectAtIndex:page withObject:controller];
[controller release];
}
// add the controller's view to the scroll view
if (controller.view.superview == nil)
{
CGRect frame = scrollView.frame;
frame.origin.x = frame.size.width * page;
frame.origin.y = 0;
controller.view.frame = frame;
[scrollView addSubview:controller.view];
}
}
As each view is generated, it runs an NSXMLParse in its viewDidLoad. Everything works fine up to this point. Both views are generated and you can swipe between them.
If you push the Settings Button
- (IBAction)settingsButtonPressed:(id)sender;
{
SettingsViewController *settingsViewController = [[SettingsViewController alloc] initWithNibName:#"SettingsViewController" bundle:nil];
UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:settingsViewController];
navigationController.modalTransitionStyle = UIModalTransitionStyleCoverVertical;
[self presentModalViewController:navigationController animated:YES];
[settingsViewController release];
[navigationController release];
}
At this point, SettingsViewController appears into view. However, sometimes it slides up with its proper animation. Other times it will simply appear and all further core animations are broken until the process is restarted.
I went through and checked all of NSXMLParse and have narrowed down the problem to one line. On each of my subviews, is a tableView, after the XML Parsing is done, I created an array with the results and ran [self.tableview reloadData]. If I comment out that line, the table obviously only loads blank but it doesn't have any issues with Animations.
- (void)parserDidEndDocument:(NSXMLParser *)parser
{
NSMutableArray *tableData = ARRAY_GENERATED_HERE;
[self.tableView reloadData];
}
My Testing
I will note from my tests, everything is fine if kNumberOfPages is set to 1 instead of 2. Only 1 view gets generated, the Animation glitch never occurs. Add a second view in, usually within opening Settings five times, it will glitch.
Still haven't come to a solution but it has to do with [tableView reloadData]. Any insight would be great.
Daniel pointed out something that makes sense.
My XML is fetched in the viewDidLoad using:
[NSThread detachNewThreadSelector:#selector(parseXMLFileAtURL:) toTarget:self withObject:path];
- (void)parseXMLFileAtURL:(NSString *)URL
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
stories = [[NSMutableArray alloc] init];
//you must then convert the path to a proper NSURL or it won't work
NSURL *xmlURL = [NSURL URLWithString:URL];
// here, for some reason you have to use NSClassFromString when trying to alloc NSXMLParser, otherwise you will get an object not found error
// this may be necessary only for the toolchain
rssParser = [[NSXMLParser alloc] initWithContentsOfURL:xmlURL];
// Set self as the delegate of the parser so that it will receive the parser delegate methods callbacks.
[rssParser setDelegate:self];
// Depending on the XML document you're parsing, you may want to enable these features of NSXMLParser.
[rssParser setShouldProcessNamespaces:NO];
[rssParser setShouldReportNamespacePrefixes:NO];
[rssParser setShouldResolveExternalEntities:NO];
[rssParser parse];
[pool release];
}
From your comment, you said you are running the parser in a background thread...UIKit is not thread safe and i suspect that is whats causing your problems...try making the reloadData call on the main thread, you can use NSObjects performSelectorInMainThread to do this...
[self performSelectorOnMainThread:#selector(operationComplete) withObject:nil waitUntilDone:false];

Tableview refreshing to parent view after selecting child using reload data

I have a UITableView that uses JSON to to get new data from the AppDelegate. It saves the data and then is pulled into this tableview class from the AppDelegate.data3, After I add a record to the mysql database I launch the Delegate method that refreshes the data.
However,[self.tableview reLoadData]; breaks the drill down ability of the table, If I select the row, it pushes the child view for a split second and the refreshes the screen with the Parent Rows. If I take out the [self.tableview reLoadData]; The parent pushes to the child but I don't get a refreshed screen with the new data.
Any Ideas?
-(void) loadData3;{
//Initialize table data source
MyAppDelegate *AppDelegate = (MyAppDelegate *)[[UIApplication sharedApplication] delegate];
self.tableDataSource3 = [AppDelegate.data3 objectForKey:#"Rows"];
}
- (void)viewDidLoad {
[super viewDidLoad];
if(CurrentLevel3 == 0) {
self.navigationItem.title = #"Parent Table";
}
else
self.navigationItem.title = CurrentTitle3;
}
}
- (void)viewDidAppear:(BOOL)animated {
[self loadData3];
[self.tableview reloadData];
[super viewDidAppear:animated];
}
There are several issues. It's not clear what you are trying to do.
You set self.tableDataSource3 to tempArray, and then set it to [AppDelegate.data3 ....];
Why?
NSArray *tempArray = [[NSArray alloc] init];
self.tableDataSource3 = tempArray;
[tempArray release];
MyAppDelegate *AppDelegate = (MyAppDelegate *)[[UIApplication sharedApplication] delegate];
self.tableDataSource3 = [AppDelegate.data3 objectForKey:#"Rows"];
On Startup [self loadData3] gets called twice. Once in viewDidLoad and viewDidAppear. Unnecessary. Should only be in viewWillAppear.
You're either not saving data that you're adding, or not retrieving it properly. Might have to step through your code to see if you're getting the data you should be getting.