My app has an UITabViewController with 3 tabs. The first two tabs will read some data from disk and display it (done in viewDidLoad of the first two tabs).
The third tab has some kind of config information. If the user changes the config information in the third tab, i want the first two tabs to be refreshed, i.e., viewDidLoad should be re-called.
I cannot use viewWillAppear in the first two tabs, as the read from disk part is kind of intensive and I wouldn't want to do it everytime the tab is clicked. Also, I need to do some auxiliary tasks (in addition to updating the first two tabs) when the third tab data is edited, so I want to reload the tabs via viewDidLoad, while doing those auxiliary tasks.
Use NSNotifications to do this.
Since the third tab is your config settings you will probability want to be storing these in NSUserDefaults so use the NSUserDefaultsDidChangeNotification to watch for this in your viewDidLoad method and move your reloadData code into its own method.
- (void)viewDidLoad
{
[super viewDidLoad];
NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
[notificationCenter addObserver:self
selector:#selector(userDefaultsChanged:)
name:NSUserDefaultsDidChangeNotification
object:nil];
[self reloadData];
}
Now this will trigger a call to the method userDefaultsChanged: whenever your defaults are changed, add the method as follows.
- (void)userDefaultsChanged:(NSNotification *)notification
{
[self reloadData];
}
- (void)viewDidUnload
{
[super viewDidUnLoad];
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
Edit: Alternative method to watch for specific default values
[[NSUserDefaults standardUserDefaults] addObserver:self
forKeyPath:#"SomeDefaultKey"
options:NSKeyValueObservingOptionNew
context:NULL];
- (void)observeValueForKeyPath:(NSString *) keyPath ofObject:(id) object change:(NSDictionary *) change context:(void *) context
{
if([keyPath isEqual:#"SomeDefaultKey"])
{
// Do Something
}
if([keyPath isEqual:#"SomeOtherKey"])
{
// Do Something else
}
}
I would use -(void)viewWillAppear:(BOOL)animated. To get around the read from disk being 'kind of intensive' you could set a flag when the config changes in the 3rd tab and then only read from disk in the other tabs if that flag is set
You can use the -(void)viewWillAppear:(BOOL)animated method to trigger the refresh on the other two view controllers.
If you don't want to reload the data every time the user clicks the Tab you may use NSNotifications to trigger a refresh. See a detailed explanation at: http://www.numbergrinder.com/2008/12/patterns-in-objective-c-observer-pattern/
Related
I use NSUserDefaults to save a switch on/off and so far it is good. It remembers the switch position in next session.
Now to the thing which I do not understand.
I use the same switch (with the same name)in another view, let´s say a flip view which is pushed in from the first view. If I change the switch in the first view it is automatically changed in the flip view.
But the other way round, if I change it in the flip view it is not changed in the first view when I go back. Only if I restart the application the first view is also changed.
How can I solve this to be changed at the same time? Or kind of refresh the first view without need to restart.
Your first view is not refreshed, as it is not initialized again. If you using ViewControllers you could update your switch in viewWillAppear (if isViewLoaded).
You should observe NSUserDefaults changes in views that are interested in the changes.
You can use the following code to observe the changes:
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
[center addObserver:self
selector:#selector(defaultsChanged:)
name:NSUserDefaultsDidChangeNotification
object:nil];
And implement the defaultsChanged method:
- (void)defaultsChanged:(NSNotification *)notification
{
NSUserDefaults *defaults = (NSUserDefaults *)[notification object];
id value = [defaults objectForKey:#"keyOfDefaultThatChanged"];
self.something = [(NSNumber *)value intValue]; // For example.
}
Don't forget the remove the observer when your view closes (perhaps in dealloc):
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
[center removeObserver:self];
I am trying to set the position of a UIScrollView by using contentOffset as such:
- (void) navigateToTableViewPosition:(CGPoint)contentOffset {
NSLog(#"Position set method gets called...");
NSLog(#"%#", NSStringFromCGPoint(contentOffset));
[mainScrollView setContentOffset:contentOffset animated:YES];
}
I call this method from another view controller before I dismiss it, and everything checks out. I pass the argument correctly, and the method gets called (checked it with NSLog), but the scroll view does not move...
What is funny is that when I call this method from the view controller, in which it is located, it works fine. Only when I call it from another view controller, it stops working.
Just for future reference, here is the calling method:
MainViewController *mainView = [[MainViewController alloc] init];
[mainView navigateToTableViewPosition:contentOffset];
Content offset is a CGPoint I set beforehand. It doesn't matter here; besides, it gets passed correctly anyways.
Try this, You have to send notification from other viewcontroller when you want to change ..
[[NSNotificationCenter defaultCenter] postNotificationName:#"changepostion" object:NSStringFromCGPoint(CGPointMake(contentOffset.x, contentOffset.y))];
in mainviewcontroller
-(void)viewDidLoad
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(navigateToTableViewPosition:) name:#"changepostion" object:nil];
}
- (void) navigateToTableViewPosition:(NSNotification *)notification
{
contentOffset =CGPointFromString([notification object]);
NSLog(#"Position set method gets called...");
NSLog(#"%#", NSStringFromCGPoint(contentOffset));
[mainScrollView setContentOffset:contentOffset animated:YES];
}
You can't set the properties of a view which is not visible. If you are using iOS5+ you can implement the offset setting in the completion in the view dismiss completion block.
Use delegate for backward messaging in view controllers.
Refer Basic Delegate Example link for more reference.
Your are making new instance of viewcontroller which will call method but will have no effect.
I have an object that need to be notified when a QLPreviewController changes the shown document. QLPreviewController have the property currentPreviewItemIndex that is updated when the document change. I've added my object as observer for currentPreviewItemIndex and it receives the notification when in my code is changed the property, so far so good.
The problem is that the user can change the shown document swiping in the screen and I've found out that in this case the notification isn't generated.
Any solution to receive the notification also in this case? I suppose that the notification is generated when is called the setter of the property currentPreviewItemIndex and probably when the user swipe the property is changed internally in the object QLPreviewController.
Another solution may be to disable the horizontal swipe in QLPreviewController but preserving the vertical swipe (there are the arrows buttons to change the shown document). How do you do that?
Thanks in advance for the help.
Giannandrea
make a category on the QLPreviewController and swizzle the appropriate method and either add the willChange/didChange for KVO ;)
seriously though:
I tried KVO and it didnt work for me either.. 1) id file a bug with apple for that saying you need this
BUT as a workaround
(id )previewPanel:(QLPreviewPanel *)panel previewItemAtIndex:(NSInteger)index {
this is called ok and everytime we swipe so I would 'hack' this to FIRE your own correct KVO. something like
static NSInteger oldIndex = -1; //reset when the panel is hidden or shown
int newIndex = qlController.displayedIndex;
if(oldIndex != newIndex) {
oldIndex = newIndex;
[qlController willChangeValueForKey:#"displayedIndex"];
[qlController didChangeValueForKey:#"displayedIndex"];
}
I wrote it inline here so there are bound to be typos and mistakes but I think the general approach could work.
//1. Declare a static context:
static void *changePageContext = &changePageContext;
//2. In viewDidLoad add self as observer for currentPreviewItemIndex property of a strong ref to your QLPreviewController:
[self.previewController addObserver:self forKeyPath:#"currentPreviewItemIndex" options:(NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld) context:changePageContext];
//3. Implement the observer method:
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object
change:(NSDictionary *)change context:(void *)context
{
if (context == changePageContext)
{
NSLog(#"newValue:%ld",(long)self.previewController.currentPreviewItemIndex);
}
else
{
// Any unrecognized context must belong to super
[super observeValueForKeyPath:keyPath
ofObject:object
change:change
context:context];
}
}
//4. Remove the observer in viewWillDisappear:
-(void)viewWillDisappear:(BOOL)animated
{
if (![[self.navigationController viewControllers] containsObject: self])
{
[self.previewController removeObserver:self forKeyPath:#"currentPreviewItemIndex"];
}
}
I have UITabBarViewController which has 2 views.
The first view has a UITableView which has 1 section and 5 rows.
The second view has a UITableView as well which has a settings options like UISwitches.
My question is how can I show and hide or remove a cell from first view by using UISwitches on the settings view? Thanks in advance.
edit
this video explain what i am trying to do (check the app view)
Press Here
you can accomplish this by using NSNotificationCenter
in your firstView you can write a code like:
-(void)viewDidLoad{
[[NSNotificationCenter defaultCenter] removeObserver:self];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(modifyCell:) name:#"modifyCell" object:nil];
}
//make sure this is declared in your .h
-(void)modifyCell:(NSNotification*)notif
{
if (notif) {
//cellindex to modify
NSString *cellIndex = [[notif userInfo] objectForKey:#"index"];
[yourDataSource removeObjectAtIndex:[cellIndex intValue]]
[yourTableView reloadData];
}
}
in your secondView:
-(void)switchChanged
{
NSNotificationCenter *ncSubject = [NSNotificationCenter defaultCenter];
NSDictionary* dict = [NSDictionary dictionaryWithObjectsAndKeys:#"indexNum",#"index", nil];
[ncSubject postNotificationName:#"modifyCell" object:nil userInfo:dict];
[ncSubject removeObserver:self];
}
You should reload your tableview after each UISwitch change. Such as:
- you set a delegate from your UISwitch to your UITabBarViewController (or the class which controls the events)
- you should store your tableview's cells' number in a variable
- this variable will change after each UISwitch change
- after the variable change, you should reload the tableview
In the viewWillAppear method of the table view controller I would check whether the setting has been changed or not. If it has changed then I would redraw the cell by calling its the reloadData method.
Sometimes it is recommended to call reloadData through performSelectorOnMainThread:
[ self performSelectorOnMainThread:#selector(reloadData) withObject:nil waitUntilDone:NO]
And your data loading methods (numberOfSectionsInTableView, numberOfRowsInSection, cellForRowAtIndexPath, etc.) will have to consider the settings value accordingly.
I'm currently pulling my hair out solving this bug :/ I have already tried the solutions from other SO threads regarding this topic but had no luck so far.
Here's what's wrong:
I have a UINavigationController that pushes View A, from View A I can press a button to push View B - works fine. But when I push View B, then rotate the screen into landscape mode and then click the back button, I get the following output in the console and the view switching is not animated, just switches from B back to A:
2012-01-02 20:50:42.866 [13345:f803] Unbalanced calls to begin/end appearance transitions for <DimensionConversionViewController: 0x68831f0>.
2012-01-02 20:50:42.868 [13345:f803] attempt to dismiss modal view controller whose view does not currently appear. self = <UINavigationController: 0x6b541a0> modalViewController = <UISnapshotModalViewController: 0x6da5190>
This is how I push the View B into the stack:
- (void) showConverter:(id)sender {
[self.navigationController pushViewController:converter animated:YES];
}
-viewDidLoad of View B:
- (void) viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view from its nib.
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(updateInterface) name:#"UIDeviceOrientationDidChangeNotification" object:nil];
// ... Update text fields ...
[self updateInterface];
}
-viewDidUnload of View B:
- (void)viewDidUnload
{
[super viewDidUnload];
// Release any retained subviews of the main view.
// e.g. self.myOutlet = nil;
[[NSNotificationCenter defaultCenter] removeObserver:self name:#"UIDeviceOrientationDidChangeNotification" object:nil];
}
If you have questions or need more code samples, please let me know.
Thanks in advance for any help :-)
Turned out that in my case the root cause of the problem was, that I forgot to update all the shouldAutorotateToInterfaceOrientation: methods in the different view controllers to return YES for all UIInterfaceOrientations (or let's say they should all return the sam). Doing this solved the issue.