observeValueForKeyPath called multiple times - ios

I have a custom container navigation. I have a diary view controller and a diary detail view controller. When user clicks on a picture in diary, it goes to diary detail using cycleFromViewController:toViewController method as described in the Apple docs about container view controllers.
When the detail view loads, I want the container view controller to remove one of it's subviews and add another one. I use KVC to accomplish this. It's my first time using KVC. addObserver method is in viewWillAppear in diary detail vc.
Problem: diary detail VC is loaded, observeValueForKeypath is called once the first time, twice the second time, and so on. Additionally, in observeValueForKeypath, I add a subview - UIButton - and when it is clicked, cycleFromViewController:toViewController is called again and the previous subview is added back. It works on the first go around, but on subsequent ones, the original subview is not added back, the UIButton just sticks around.
Diary Detail.m
-(void)viewWillAppear:(BOOL)animated{
[self addObserver:self.parentViewController forKeyPath:#"didLoadNumber" options:0 context:nil];
[self setValue:[NSNumber numberWithInt:0] forKey:#"didLoadNumber"];}
Main Container VC (observer/parent VC)
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context{
NSLog(#"KVO called");
[self.usernameLabel removeFromSuperview];
self.backButton = [[UIButton alloc]initWithFrame:CGRectMake(12, 28, 28, 28)];
self.backButton.backgroundColor = [UIColor blackColor];
[self.view addSubview:self.backButton];
[self.backButton addTarget:self action:#selector(removeButtonAndAddLogo)
forControlEvents:UIControlEventTouchUpInside];
}
-(void)removeButtonAndAddLogo{
NSLog(#"got to remove button");
[self.backButton removeFromSuperview];
self.usernameLabel = [[UILabel alloc]initWithFrame:CGRectMake(12, 28, 28, 28)];
self.usernameLabel.text = #"username";
self.usernameLabel.textColor = [UIColor blackColor];
[self.view addSubview:self.usernameLabel];
[self cycleFromViewController:self.diaryViewController.diaryDetailVC toViewController:self.diaryViewController];
}

For this you need to remove it in viewWillDisappear method. If you will come at this view controller more then one time , it'll register this notification again and again and whenever you call this notification it'll call multiple time(number of time's you have register it).
-(void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
[self addObserver:self.parentViewController forKeyPath:#"didLoadNumber" options:0 context:nil];
[self removeObserver:self.parentViewController forKeyPath:#"didLoadNumber"];
}
i hope it will help you.

Related

popToRootViewController crashes when tableView is still scrolling

When I give a good swipe to my tableView and press the "Back" button before the tableView ended it's scrolling, my app crashes. I've tried the following:
- (void) closeViewController
{
[self killScroll];
[self.navigationController popToRootViewControllerAnimated:YES];
[self dismissViewControllerAnimated:YES completion:nil];
}
- (void)killScroll
{
CGPoint offset = sellersTableView.contentOffset;
[sellersTableView setContentOffset:offset animated:NO];
}
That didn't work, same crash. I don't see why, the error I'm getting is the following:
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'UITableView dataSource must return a cell from tableView:cellForRowAtIndexPath:'
So that means that the tableView is still requesting a cell when everything is already being deallocated. Makes no sense.
Then I tried this:
- (void) closeViewController
{
[self.navigationController popToRootViewControllerAnimated:YES];
[self dismissViewControllerAnimated:YES completion:nil];
}
- (void)dealloc
{
sellersTableView.dataSource = nil;
sellersTableView.delegate = nil;
sellersTableView = nil;
}
Gives me the same error. Any ideas?
Update:
My delegate methods
creation
if (textField == addSellerTextField) {
sellersTableView = [[UITableView alloc] initWithFrame:CGRectMake(addSellerTextField.frame.origin.x + addSellerTextField.frame.size.width + 10, addSellerTextField.frame.origin.y - [self heightForTableView] + 35, 200, [self heightForTableView])];
sellersTableView.delegate = self;
sellersTableView.dataSource = self;
sellersTableView.backgroundColor = [[UIColor grayColor] colorWithAlphaComponent:0.05];
sellersTableView.separatorColor = [[UIColor grayColor] colorWithAlphaComponent:0.15];
sellersTableView.rowHeight = 44;
sellersTableView.layer.opacity = 0;
[self.companyView addSubview:sellersTableView];
[UIView animateWithDuration:0.3 delay:0 options:UIViewAnimationOptionCurveEaseIn animations:^{sellersTableView.layer.opacity = 1;} completion:nil];
}
cellForRowAtIndexPath
if (tableView == sellersTableView) {
if (!cell) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
cell.backgroundColor = [UIColor clearColor];
if ([sellersArray count] > 0) {
cell.textLabel.text = [sellersArray objectAtIndex:indexPath.row];
} else {
UILabel *noSellersYetLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, sellersTableView.frame.size.width, [self heightForTableView])];
noSellersYetLabel.text = #"no sellers yet";
noSellersYetLabel.textAlignment = NSTextAlignmentCenter;
noSellersYetLabel.textColor = [UIColor grayColor];
[cell addSubview:noSellersYetLabel];
sellersTableView.separatorStyle = UITableViewCellSeparatorStyleNone;
}
}
removing
- (void) textFieldDidEndEditing:(UITextField *)textField
{
if (textField == addSellerTextField) {
[self updateSellers:textField];
}
}
- (void)updateSellers:(UITextField *)textField
{
[textField resignFirstResponder];
[self hideSellersTableView];
}
- (void)hideSellersTableView
{
[UIView animateWithDuration:0.3 delay:0 options:UIViewAnimationOptionCurveEaseIn animations:^{sellersTableView.layer.opacity = 0;} completion:nil];
sellersTableView.dataSource = nil;
sellersTableView.delegate = nil;
[sellersTableView removeFromSuperview];
sellersTableView = nil;
}
Solution
So apparently putting the dataSource = nil and delegate = nil into textFieldDidEndEditing fixed the problem. Thanks everybody for the answers!
It's strange behaviour of UITableView. The easiest way to resolve this issue just set the dataSource and delegate property of UITAbleView to nil before you make a call of function popToRootViewControllerAnimated. Furthermore you can use more common solution and add the code that set the properties to nil into the -dealloc method. In addition you no need the -killScroll method.
After a short research I have realized what the problem is. This unusual behaviour appeared in iOS 7. The scroll view retained by its superview may send message to delegate after the delegate is released. It happens due to -removeFromSuperview implementation UIScrollView triggers -setContentOffset: and, eventually, send message to delegate.
Just add following lines at the beginning of dealloc method:
sellersTableView.delegate = nil;
sellersTableView.dataSource = nil;
No need to use hacks like your killScroll method.
Also, I can't see why you want to call both popToRootViewController and dismissViewController.
If you dismiss a view controller which is embedded in a navigation controller, navigation controller itself as well as all contained view controllers will be released.
In your case you'll have just weird animation.
setContentOffset method won't help you, try to set
sellersTableView.dataSource = nil;
somewhere in your viewWillDisappear method.
This is not a good practice of course.
Change you closeViewController like below and see if works
(void) closeViewController
{
sellersTableView.dataSource = nil;
sellersTableView.delegate = nil;
[self.navigationController popToRootViewControllerAnimated:YES];
[self dismissViewControllerAnimated:YES completion:nil];
}
I don't think that setting the tableView (or it's delegate) to nil is the issue. You should be able to perform both dismissViewControllerAnimated or popToRootViewController individually without having to modify the tableView in this way.
So the issue is most likely due to calling both of these methods at the same time (and with animated = YES), and in doing so asking your viewController setup to do something unnatural.
Looks like upon tapping a "close" button you are both popping to a rootViewController of a UINavigationController, as well as dismissing a modal viewController.
In doing so, you're dismissing a modal viewController which is likely presented by the topViewController of the navigationController (so top vc is holding a reference to modal vc). AND you're trying to kill the top vc via the popToRootViewController method call. And you're doing both of these things using animated = YES, which means they take some time to complete, and you can't be sure when each finishes (ie you can't be sure when dealloc will be called).
Depending on your needs you could do one of several things.
Consider adding a delegate property to your modal vc. Dismiss the modal vc, and in the completionBlock of the modal vc tell its delegate that it's finished dismissing. At that point call popToRootViewController (because at this point you can be sure that the modal is gone and scrolling wasn't interrupted).
If it's your navController that's been presented modally, then do this in the opposite order. Notifying the delegate that the pop operation has completed, and do the modal dismissal then.

UILabel won't change off screen

I have a problem that i've trying to figure out for a few days now. Technically I'm trying to change a UILabel text in one view, from a trigger in a button in another view. When the button is in the view where the UILabel is, the label changes without a problem. But when assigning the trigger to a button in another view (even though using the same ViewController class) it won't change the UILabel's text.
Here's the piece of code triggering my button on both cases.
- (IBAction)getYearMonth:(UIButton*)sender {
//NSLog(#"Date: %# 1 %#", sender.titleLabel.text, self.year.text);
//evenCon.eventsCurrentDate.text = #"";
//NSLog(#"%#", evenCon.eventsCurrentDate.text);
//string = sender.currentTitle;
eventsCurrentDate.text = #"hello";
}
Excuse my knowledge in objective-c, I started learning two weeks ago.
Edit: I think i need to add this. My views are being displayed without segues, they are childs of a scrollview in the main view controller. (Kind of like the snapchat app effect)
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
//FBLoginView *loginView = [[FBLoginView alloc] init];
//loginView.frame = CGRectOffset(loginView.frame, (self.view.center.x - (1)), 517);
//[self.loginView addSubview:loginView];
[self.scrollView setPagingEnabled:YES];
[self.scrollView setScrollEnabled:YES];
[self.scrollView setShowsHorizontalScrollIndicator:NO];
[self.scrollView setShowsVerticalScrollIndicator:NO];
[self.scrollView setDelegate:self];
[self addChildViewController:[self.storyboard instantiateViewControllerWithIdentifier:#"View1"]];
[self addChildViewController:[self.storyboard instantiateViewControllerWithIdentifier:#"View2"]];
[self addChildViewController:[self.storyboard instantiateViewControllerWithIdentifier:#"View3"]];
}
"View1" is where the label i'm trying to change is, and "View2" is where my button is.
This is happening because you are not sending any data to another view. Download Master detail sample code form Xcode (Its in add new project). Look for how they transfer data to another view controller and then display the text it in your label
You need to use setText
[_eventsCurrentDate setText:#"hello"];

Detect change of UILabel's text

I have a time that repeats itself every .1 seconds (I do this because I need to constantly look for a change on the website's text I am pulling from). My problem is that I need to be able to tell when the text changes from one word to another. So when the user open the app and let's say the text is displaying one song title but then it changes to a different one, Where it changes, I need to be able to detect it and preform an action.
Also it has to be when the app first loads it will not preform the action but when the text changes it will display it(It has to be this because I am pulling song titles). So I need to be able to change the duration of each song, but if a song is playing and they open the app in the middle of it, the duration will be mixed up. so I have two labels, one that shows text and another that shows the time. When the app first loads up, I want it to display nothing until the text in the song title changes, then pulls the duration from a if statement (below). It might be a little confusing but ask any question you need and I will try to explain.
Here is my code for pulling from the website and anything else that might help:
- (void)viewDidLoad {
[super viewDidLoad];
timer = [NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:#selector(recentTracksText) userInfo:nil repeats:YES];
}
-(void)recentTracksText {
textForBlog = [webViewForRecents stringByEvaluatingJavaScriptFromString:#"document.getElementById('current_song').textContent;"];
self.strippedTextForBlog = [self stringByStrippingHTMLFromString:textForBlog];
continuousLabel.text = self.strippedTextForBlog;
}
if ([continuousLabel.text isEqual: #"Lady Gaga - Gypsy"]) {
imageView1.image = [UIImage imageNamed:#"Lady Gaga - Gypsy.jpg"];
imageView.hidden = YES;
[self.view insertSubview:toolbar belowSubview:imageView];
[toolbar insertSubview:darkView belowSubview:imageView];
[self.view insertSubview:backgroundImage belowSubview:toolbar];
if([darkView.subviews containsObject:continuousLabel]) {
} else{
dispatch_queue_t queue = dispatch_get_global_queue(0,0);
dispatch_async(queue, ^{
[darkView addSubview:Label];
[Label setText:#"1:30"];
[darkView addSubview:continuousLabel];
});
}
}
UPDATE
-(void)recentTracksText {
textForBlog = [webViewForRecents stringByEvaluatingJavaScriptFromString:#"document.getElementById('current_song').textContent;"];
self.strippedTextForBlog = [self stringByStrippingHTMLFromString:textForBlog];
if (![self.strippedTextForBlog isEqualToString:continuousLabel.text])// this does not find when the text changes {
[self.pLabel setHidden:NO];
[self.pLabel setProgress:1
timing:TPPropertyAnimationTimingEaseOut
duration:150.0
delay:0.0]; //This does not go smoothy.
// this does not
}
continuousLabel.text = self.strippedTextForBlog;
}
Use Key-value observing if you want to observe the property directly
[continuousLabel addObserver:self
forKeyPath:#"text"
options:0
context:NULL];
Make sure you implement the observing method
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context
{
// Insert your code here to deal with the change.
}
and a little bit of ARC management to taste:
- (void)dealloc {
NSLog(#"dealloc %#", [self class]);
if (continuousLabel) {
[continuousLabel removeObserver:self forKeyPath:#"text"];
}
}
Well, you already have the NSTimer that fires each 0.1 seconds, so you just have to check if the text wasn't changed.
-(void)recentTracksText {
textForBlog = [webViewForRecents stringByEvaluatingJavaScriptFromString:#"document.getElementById('current_song').textContent;"];
self.strippedTextForBlog = [self stringByStrippingHTMLFromString:textForBlog];
if (![self.self.strippedTextForBlog isEqualToString:continuousLabel.text]) {
//The text isn't the same. Do something.
}
continuousLabel.text = self.strippedTextForBlog;
}

Method called only in viewDidApper but not in viewDidLoad, why?

I created a method that sets the title of a button based on a value.
This method needs to be called when opening the viewController and maybe refreshed when the controller appears again.
So i created the method and I called that method in viewDidLoad and viewDidApper but it seems to be called only when I change page and turn back to the view controller.
Why?
My code is
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
[self controlloRichieste];
......
}
-(void)viewDidAppear:(BOOL)animated{
[self controlloRichieste];
}
-(void)controlloRichieste{
//Numero richieste di contatto
NSString *numeroRichieste = #"1";
if([numeroRichieste isEqual:#"0"]){
[_labelRequestNumber setTitle:#"Nessuna" forState:UIControlStateNormal];
} else {
_labelRequestNumber.titleLabel.text = numeroRichieste;
_labelRequestNumber.tintColor = [UIColor redColor];
}
//Fine Numero richieste di contatto
}
You can also move that code to viewWillAppear so that it gets called each time it appears.
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[self controlloRichieste];
}
I see the problem now, try the other way around
-(void)controlloRichieste{
//Numero richieste di contatto
NSString *numeroRichieste = #"1";
if([numeroRichieste isEqual:#"0"]){
[_labelRequestNumber setTitle:#"Nessuna" forState:UIControlStateNormal];
} else {
_labelRequestNumber.tintColor = [UIColor redColor];
[[_labelRequestNumber titleLabel]setText:numeroRichieste];
}
//Fine Numero richieste di contatto
}
Change set the button color, before you change its titleLabel's text
I created a demo PROJECT for you, hope it's helpful!
When you open view first time the viewDidLoad is called and the viewDidAppeare.
The viewDidAppeare is called every time when the view is opened, when you push or present other view controller and go back to the maine one viewDidAppeare is called.
You should call:
[super viewDidAppear:animated];
The viewDidLoad is called just when the view is loaded and after that when it's deallocated and it needs to be allocated again. So mostly when you push or present other view controller and go back to the maine one viewDidLoad is not called.

UIScrollView: subview added but not showing up

I have a UIScrollView called scroller. It has subviews which are custom UIButtons..I try to print the number of subviews before and after adding like as follows
NSLog(#"Adding %d b4 %d",buttonno, [[self.scroller subviews] count]);
[self.scroller addSubview:menuBtn];
NSLog(#"Adding %d after %d",buttonno, self.scroller.subviews.count);
The subview is not showing up. But the number of subviews increments for example the output of this is like
2014-03-11 17:53:49.863 PC2[4519:60b] Adding 1 b4 10
2014-03-11 17:53:49.872 PC2[4519:60b] Adding 1 after 11
But when I try to print the number of subviews using a test button which runs the code
NSLog(#"Buttons subviews: %d",self.scroller.subviews.count);
I can see that no subview is added..I can also see that no subview is added when I see the variables in the debugger.
btw I'm adding buttons based on timer [NSTimer scheduledTimerWithTimeInterval:0.2 target:self selector:#selector(checkForTiles) userInfo:nil repeats:YES];
Update:: The code to add subview
if (!alreadyExists)
{
NSLog(#"Adding %d b4 %d",buttonno, [[self.scroller subviews] count]);
//AudioServicesPlayAlertSound(kSystemSoundID_Vibrate);
pccButtonViewController *menuBtn=nil;
menuBtn=[pccButtonViewController alloc ];
menuBtn = [pccButtonViewController buttonWithType:UIButtonTypeCustom];
menuBtn.no=buttonno;
menuBtn.slotNo=buttonCount;
menuBtn.frame = [[slotFrames objectAtIndex:menuBtn.slotNo] CGRectValue];
[menuBtn setBackgroundImage:[UIImage imageNamed:[[NSString alloc] initWithFormat:#"L%02d_Tile.jpg",buttonno]] forState:UIControlStateNormal];
[menuBtn addTarget:self action:#selector(miniButtons:) forControlEvents:UIControlEventTouchUpInside];
menuBtn.no=buttonno;
menuBtn.alpha=0.0f;
[self.scroller addSubview:menuBtn];
NSLog(#"Adding %d after %d",buttonno, self.scroller.subviews.count);
//NSLog(#"Subview Added %d btn no:%#",buttonno,[slotFrames objectAtIndex:menuBtn.slotNo]);
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:1.0f];
//self.menuBtn.frame = CGRectMake(8, 10, 50, 60);
menuBtn.alpha=1.0f;
[UIView commitAnimations];
[buttons addObject:menuBtn];
buttonCount++;
}
buttonclicked=0;
[self.scroller setContentSize:CGSizeMake([self.scroller bounds].size.width,(ceil(buttonCount/tilesPerRow)+1)*[self.scroller bounds].size.width/tilesPerRow/2*3)];
appdelegate.Sharevar.vidsyncButtons=self.scroller.subviews.copy;//Used to maintain State when the view is presented for the second time
This works fine when the View runs for the first time.The problems arise when it is run for the second time..I remove the view from appdelegate like this
-(void)removeAnyView
{
NSLog(#"Removing");
if (viewonScreen)
{
viewonScreen=false;
[self.inPort prepareToBeDeleted];
self.inPort=Nil;
[self.window.rootViewController dismissViewControllerAnimated:NO completion:^{[self presentView];}];
}
}
It's been a long time.. I forgot about this question.. The error was caused by not invalidating the NSTimer.. Got to be careful with these..
Any updates to the UI have to be on the main thread, which your NSTimer won't be, so you need to do something like this...
dispatch_sync(dispatch_get_main_queue(), ^{
[self.scroller addSubview:menuBtn];
});
Change this
menuBtn=[pccButtonViewController alloc ];
to this
menuBtn=[[pccButtonViewController alloc ] init];
From documenation:
buttonWithType:
This method is a convenience constructor for creating button objects
with specific configurations. If you subclass UIButton, this method
does not return an instance of your subclass. If you want to create an
instance of a specific subclass, you must alloc/init the button
directly.

Resources