Become a MacRumors Supporter for $50/year with no ads, ability to filter front page stories, and private forums.

moonman239

Cancelled
Original poster
Mar 27, 2009
1,541
32
OK, so here's my newest problem. I have a bunch of UIButtons, all laid out in the same XIB file. Each one has a superview with a unique tag. This tag is used to decide whether the button shows up. At any one time, there should be one, and only one such button on the screen. Their superviews are hidden in viewDidLoad, then un-hidden on an as-needed basis.
What I'm finding thus far is that sometimes, after clicking the button and segueing to a different view controller, when that view controller then segues back to the screen, there are two buttons. When I then close the app and go back to that screen, I go back to seeing only one.
Furthermore, in one related case - when I load the app and go directly to the screen - the wrong UIButton shows up!

My numbering system appears to be adequate for the task at hand. One user suggested using NSUserDefaults, but - for a reason I won't be mentioning - I'm using Core Data to identify which button should be showing.

Here's my toggle code:
Code:
-(void)viewDidAppear:(BOOL)animated

{

    [super viewDidAppear:animated];

    // Get an array of all menu item views for activities up to, and including, the current activity.

    NSInteger placeTag = entity.familyNum * 10 + entity.activityNum;

    NSPredicate *filteringPredicate = [NSPredicate predicateWithBlock:^BOOL(id obj1,NSDictionary *dictionary) {

        return [obj1 isKindOfClass:[MenuItemView class]];

    }];

    NSArray *filteredViews = [levelView.subviews filteredArrayUsingPredicate:filteringPredicate];

    // Loop through all views for activities up to, and including, current activity.

    for (int representedFamily = 0; representedFamily <= entity.familyNum; representedFamily++) {

        for (int representedActivity = 0; representedActivity <= entity.activityNum; representedActivity++)

        {

            NSInteger thisViewTag = representedFamily * 10 + representedActivity; // Tag of the view corresponding to represented family and activity.

            // Find view with thisViewTag

            MenuItemView *thisView = [[filteredViews filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(MenuItemView *view, NSDictionary *dictionary) {

                return view.tag == thisViewTag;

            }]] firstObject];

            NSAssert(thisView != levelView, @"levelView is thisView - change levelView's tag");

            NSAssert(thisView != nil,@"Nil levelView!");

            thisView.hidden = false;

            if (thisViewTag == placeTag) {

                // Current activity

                currentActivityTag = thisViewTag;

                thisView.userInteractionEnabled = true;

                thisView.button.hidden = false;

                thisView.button.userInteractionEnabled = true;

                [thisView.button addTarget:self action:@selector(activityButtonPressed:) forControlEvents:UIControlEventTouchDown];

            }

            else

            {

                NSAssert(thisView.button != nil, @"Nil view button!");

                // Past activity
//Breakpoint here fires only once

                thisView.button.hidden = true;

                thisView.button.userInteractionEnabled = false;

                thisView.starImageView.hidden = false;

            }

        }

    }

}
 
Last edited:
Your code is incredibly complex for such a simple problem. My only advice is to completely rewrite this. Don't use tags, use outlets for your buttons. Don't use Core Data use either a static variable or NSUserDeafults. It's added a TON of unnecessary code to simply determine which button to show.
 
  • Like
Reactions: Mascots
Your code is incredibly complex for such a simple problem. My only advice is to completely rewrite this. Don't use tags, use outlets for your buttons. Don't use Core Data use either a static variable or NSUserDeafults. It's added a TON of unnecessary code to simply determine which button to show.
Thanks, but there's only one problem: My app is to be built for multiple users on one device, which is why I'm using core data. Then again, I could use Core Data + a static variable, just load it before hand.

My question is, why doesn't my code work as written?
 
Thanks, but there's only one problem: My app is to be built for multiple users on one device, which is why I'm using core data. Then again, I could use Core Data + a static variable, just load it before hand.

My question is, why doesn't my code work as written?
Core Data is not designed to be a solution for multiple users. It's a SQLite persistent storage framework.

I don't know why your code doesn't work, there isn't enough code given, it could be anything. The way you calculate view tags is stupid so it could calculate the wrong tag or you could be pulling the wrong entity and Core Data returns 0 users.

Please, take my advice. Rewrite this completely. It's not the correct way to do anything.
 
I took your suggestion to use outlets and avoid tags. Now, the menu item views have tro properties that do the same thing. However, I now see the same problem on a different but similar screen. Basically same setup, just a different XIB file.
Code:
-(void)viewDidAppear:(BOOL)animated

{

    [super viewDidAppear:animated];

    // Get an array of all buttons.

    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];

    // NSDictionary that stores the current activity.

    NSDictionary *currentActivity = [defaults valueForKey:[NSString stringWithFormat:@"%d",[LevelObject currentLevel]]];

    // Activities are members of a dictionary with keys "familyNum" and "activityNum".

    // Remember to unhide the menu item views before using them.

    familyNum = [[currentActivity valueForKey:@"familyNum"] integerValue];

    activityNum = [[currentActivity valueForKey:@"activityNum"] integerValue];

   

    for (MenuItemView *view in levelView.subviews) {

        if ([view isKindOfClass:[MenuItemView class]]) {

            if (view.familyNum == familyNum && view.activityNum == activityNum)

            {

                // current activity

                view.hidden = false;

                view.button.hidden = false;

                view.userInteractionEnabled = true;

            }

            else if (view.familyNum < familyNum || (view.familyNum == familyNum && view.activityNum < activityNum)) {

                // past activity

                view.hidden = false;

                view.button.hidden = true;

                view.button.userInteractionEnabled = false;

                view.starImageView.hidden = false;

            }

        }

    }

}

What ends up happening is, I'll get to the point in the app where familyNum == 0 and activityNum == 1. Then, I go past that point so either activityNum becomes 2 or familyNum becomes 1, yet the button for a familyNum of 0 and activityNum of 1 still shows up but I can't select it.
 
If the problem is that some views are visible when they shouldn't be, then the answer is that somehow your code is failing to make all the views invisible.

The code's goal can be broken into 2 parts:
1. Make all the views invisible.
2. Make one view visible.

If part 1 is done incorrectly, then more than one view will be visible at the end of part 2.

As to finding where the problem lies, break it down logically. It's either in part 1 (making all views invisible) or in part 2 (making one view visible).

Rewrite your code more simply. Make all views invisible. Do that, and only that. Run the code. Confirm it works in all the expected situations.

Now add code outside the loop that makes views invisible that makes one view visible. Run it. Confirm it works.

If it all works, confirm it continues to work when you make different views visible and invisible.

If it's all still working, then and only then should you attempt to optimize or improve it.

If at any point the simplified code doesn't work, then stop, post the code, and:
1. Describe what you expected to happen.
2. Describe what actually happened.
 
If the problem is that some views are visible when they shouldn't be, then the answer is that somehow your code is failing to make all the views invisible.

The code's goal can be broken into 2 parts:
1. Make all the views invisible.
2. Make one view visible.

If part 1 is done incorrectly, then more than one view will be visible at the end of part 2.

As to finding where the problem lies, break it down logically. It's either in part 1 (making all views invisible) or in part 2 (making one view visible).

Rewrite your code more simply. Make all views invisible. Do that, and only that. Run the code. Confirm it works in all the expected situations.

Now add code outside the loop that makes views invisible that makes one view visible. Run it. Confirm it works.

If it all works, confirm it continues to work when you make different views visible and invisible.

If it's all still working, then and only then should you attempt to optimize or improve it.

If at any point the simplified code doesn't work, then stop, post the code, and:
1. Describe what you expected to happen.
2. Describe what actually happened.

Part 1 is already done. I even commented out my code for part 2 and re-run it.

In greater detail, here's what happens:
1) I get to the point where familyNum = 0 and activityNum = 0. All is well and good.
2) I get to the point where familyNum = 0 and activityNum = 1. Still good.
3) I get to the point where familyNum = 0 and activityNum = 2. At this point, the button for step #2 is still visible, and this is my problem. To make matters worse, my own analysis of my new viewDidAppear method indicates this block of code should be working:
Code:
 if (view.familyNum == familyNum && view.activityNum == activityNum)

            {

                // current activity

                view.hidden = false;

                view.button.hidden = false;

                view.userInteractionEnabled = true;

            }
 else if (view.familyNum < familyNum || (view.familyNum == familyNum && view.activityNum < activityNum)) {

                // past activity

                view.hidden = false;

                view.button.hidden = true;

                view.button.userInteractionEnabled = false;

                view.starImageView.hidden = false;

            }
Now, I know for a fact that there is only 1 UIView per familyNum/activityNum combination.
Since the code for setting familyNum and activityNum comes before the posted for loop, it appears that some weird thing must be occurring.
Thus, the if block should only run once. There is, therefore, only one logical conclusion: My code is not at fault.
Could it be because I'm running a release build, rather than a debug build?
 
A. Simplify more.

Make the loop only print the familyNum and activityNum of each view. Run it. Confirm the output is correct. If the output is correct, then the loop is correctly traversing every view expected.

Correct means every pair is listed exactly once: no repeats, none missing.

If some values are missing or repeated in the printed list, you need to figure out why that's happening. The obvious place to look next is one of these:
Code:
    for (MenuItemView *view in levelView.subviews) {

        if ([view isKindOfClass:[MenuItemView class]]) {
That is, either the expected view isn't in levelView.subviews, or the expected class isn't MenuItemView. You will probably need to make simplified versions of the loop and/or class checking to discover what's going on there.


B. Explain what this code is expected to accomplish:
Code:
 else if (view.familyNum < familyNum || (view.familyNum == familyNum && view.activityNum < activityNum)) {
As I understand what the overall loop is trying to accomplish, there are exactly 2 cases, and every view will fall into only one of these:
1. The view should be unhidden.
2. The view should be hidden.

The condition for case 1 is that the tested view has the same family and activity numbers as the currentActivity object. That condition is evaluated with this code:
Code:
 if (view.familyNum == familyNum && view.activityNum == activityNum)
{
   // unhide the view here
}

The condition for case 2 is simply that the tested view DOES NOT have the same family and activity numbers as the currentActivity object. In other words, it's a simple ELSE, which would be this code:
Code:
else
{
   // hide the view here
}
In other words, every view should fall into either the IF block or the ELSE block, never both, and never neither.

But instead of that simple ELSE, you have this ELSE-IF, which represents a 3rd case:
Code:
 else if (view.familyNum < familyNum || (view.familyNum == familyNum && view.activityNum < activityNum)) {
I have no idea what this is expected to accomplish, unless there are additional conditions or cases you haven't explained.

If there are additional conditions, then you'll still need a simple ELSE clause to catch all the cases for 2. In other words, if the view should be hidden, you need to make sure that is actually done to all views that don't fall into another case.

What happens to a view that doesn't fall into your first if or your else if? As currently written, nothing happens to the view, so if it was visible before, it remains visible.


C. Are you sure this should be in viewDidAppear?
Wouldn't it make more sense to be in viewWillAppear?
 
Last edited:
A. Simplify more.

Make the loop only print the familyNum and activityNum of each view. Run it. Confirm the output is correct. If the output is correct, then the loop is correctly traversing every view expected.

Correct means every pair is listed exactly once: no repeats, none missing.

If some values are missing or repeated in the printed list, you need to figure out why that's happening. The obvious place to look next is one of these:
Code:
    for (MenuItemView *view in levelView.subviews) {

        if ([view isKindOfClass:[MenuItemView class]]) {
That is, either the expected view isn't in levelView.subviews, or the expected class isn't MenuItemView. You will probably need to make simplified versions of the loop and/or class checking to discover what's going on there.


B. Explain what this code is expected to accomplish:
Code:
 else if (view.familyNum < familyNum || (view.familyNum == familyNum && view.activityNum < activityNum)) {
As I understand what the overall loop is trying to accomplish, there are exactly 2 cases, and every view will fall into only one of these:
1. The view should be unhidden.
2. The view should be hidden.

The condition for case 1 is that the tested view has the same family and activity numbers as the currentActivity object. That condition is evaluated with this code:
Code:
 if (view.familyNum == familyNum && view.activityNum == activityNum)
{
   // unhide the view here
}

The condition for case 2 is simply that the tested view DOES NOT have the same family and activity numbers as the currentActivity object. In other words, it's a simple ELSE, which would be this code:
Code:
else
{
   // hide the view here
}
In other words, every view should fall into either the IF block or the ELSE block, never both, and never neither.

But instead of that simple ELSE, you have this ELSE-IF, which represents a 3rd case:
Code:
 else if (view.familyNum < familyNum || (view.familyNum == familyNum && view.activityNum < activityNum)) {
I have no idea what this is expected to accomplish, unless there are additional conditions or cases you haven't explained.

If there are additional conditions, then you'll still need a simple ELSE clause to catch all the cases for 2. In other words, if the view should be hidden, you need to make sure that is actually done to all views that don't fall into another case.

What happens to a view that doesn't fall into your first if or your else if? As currently written, nothing happens to the view, so if it was visible before, it remains visible.


C. Are you sure this should be in viewDidAppear?
Wouldn't it make more sense to be in viewWillAppear?

OK, I've checked the log and there are no missing values and no duplicates.

About the "if" block, I should have been more specific. There's a third possibility, where either view.familyNum > familyNum or (view.familyNum == familyNum and view.activityNum > activityNum). In this case, nothing is programmed to happen.

Other than that, your explanation is on par with my own understanding.
 
OK, I've checked the log and there are no missing values and no duplicates.

About the "if" block, I should have been more specific. There's a third possibility, where either view.familyNum > familyNum or (view.familyNum == familyNum and view.activityNum > activityNum). In this case, nothing is programmed to happen.
If that description is correct, then it doesn't match your code. If you understand boolean algebra, you've incorrectly negated at least the OR and the > operations. The negation of boolean OR is AND, and the negation of > is <= .

I suggest that you code the do-nothing case as an actual do-nothing block, with the condition exactly as you described it above. Then add an else block that contains the code to hide the view. In short:
Code:
else if ( /*write the exact conditional here*/ )
{
  // do nothing
}
else
{
  // code goes here that hides the view
}
 
If that description is correct, then it doesn't match your code. If you understand boolean algebra, you've incorrectly negated at least the OR and the > operations. The negation of boolean OR is AND, and the negation of > is <= .

I suggest that you code the do-nothing case as an actual do-nothing block, with the condition exactly as you described it above. Then add an else block that contains the code to hide the view. In short:
Code:
else if ( /*write the exact conditional here*/ )
{
  // do nothing
}
else
{
  // code goes here that hides the view
}
That didn't help me, unfortunately.
Edit: Before anyone mentions UICollectionView, I've looked into that. Since I need to do my layout in an XIB file, I decided my way was better.

Edit #2: I just deleted the app and reinstalled it. After tapping the button for familyNum = 0, activityNum = 2, I was taken back to the problem screen, from which I was able to tap the "problem" button and go on. However, when I returned to that screen after that point, I could no longer tap the button.
 
Last edited:
Edit #2: I just deleted the app and reinstalled it. After tapping the button for familyNum = 0, activityNum = 2, I was taken back to the problem screen, from which I was able to tap the "problem" button and go on. However, when I returned to that screen after that point, I could no longer tap the button.
To me, that sounds like you've disabled or removed a button or view without doing a relayout. See question C of post #8.


At this point, I'm out of ideas, except for one: Look more closely.

First, look more closely at exactly what views are hidden/shown or enabled/disabled at the different points in time.

Also look more closely at the "else if" conditional block, maybe logging all the views that match the condition, and confirming they're what you them to be. I don't understand what your intent is with this, since it seems strange to me to have views left in a prior state if they should definitely be in either a hidden or showing state. Since you haven't described what this is for, it's impossible to check your description of the logic with what the desired screen behavior should be. In other words, we don't know what you expect to happen, nor why, so we can't determine if your description is correct.

Most bugs are a result of expectations not matching reality, or of a mistaken expectation. Looking closely means looking both at the code and the expectation itself. When I taught programming classes, I would tell students to first describe what they wanted to happen in plain simple English steps, then give those steps to a fellow student to walk through and perform manually. If some steps were ambiguous, unclear, incomplete, or mistaken, then the person who wrote them would see it pretty quickly. In other words, they were looking closely at both their expectation of what should happen, and the reality of what they wrote as steps.
 
  • Like
Reactions: millerj123
Since you haven't described what this is for, it's impossible to check your description of the logic with what the desired screen behavior should be. In other words, we don't know what you expect to happen, nor why, so we can't determine if your description is correct.

The app I'm building is a game. The user is supposed to play one level at a time. Once the user has finished a level, a star indicates this and they should not be allowed to go back to a previously-completed level.

I tried moving the code to viewWillAppear. That didn't help.
 
The app I'm building is a game. The user is supposed to play one level at a time. Once the user has finished a level, a star indicates this and they should not be allowed to go back to a previously-completed level.
How does the level relate to familyNum and activityNum?

I tried moving the code to viewWillAppear. That didn't help.
Post the code.

Did you add anything that looks more closely? In particular, at the views where your "else if" condition applies, so the view's state is unchanged.

Did you do anything to examine the view hierarchy at the time of viewWillAppear or viewDidAppear? In other words, does the displayed button that isn't tappable correctly represent the expected view hierarchy at that time? Is the button actially in the view hierarchy and visible? Is some other view overlaying or overlapping it and intercepting taps or touches intended for it?
 
How does the level relate to familyNum and activityNum?


Post the code.

Did you add anything that looks more closely? In particular, at the views where your "else if" condition applies, so the view's state is unchanged.

Did you do anything to examine the view hierarchy at the time of viewWillAppear or viewDidAppear? In other words, does the displayed button that isn't tappable correctly represent the expected view hierarchy at that time? Is the button actially in the view hierarchy and visible? Is some other view overlaying or overlapping it and intercepting taps or touches intended for it?
Basically, each time you level up, either activityNum increases or familyNum does.

I put in a logging statement in each of the "if" blocks to check each view's "hidden" property. Oddly enough, the problem view's button registers as "hidden" even though it's not.

I have not yet examined the view hierarchy, but I looked at the XIB and there should only be one overlapping view - an image view, which at this point isn't showing up like it's supposed to.

Edit: Well, this is interesting. I went into the view debugger at viewDidAppear and the problem button's hidden property is "OFF". Looking through my entire project, however, I didn't see any functioning code to hide the button. There was one line in a function that I never call. That function has since been removed, but that has no effect on the end result.

Edit #2: Let me go ahead and try hiding the button in IB.
 
Last edited:
Can you please post your entire class? The bits and pieces you're posting aren't helpful because it shows a small piece of the puzzle. I'm guessing it has to do with the logic to show buttons as whole isn't correct.
 
Can you please post your entire class? The bits and pieces you're posting aren't helpful because it shows a small piece of the puzzle. I'm guessing it has to do with the logic to show buttons as whole isn't correct.

Edit: Redacted due to client copyright.
 
Last edited:
I forgot to add: Hiding the button in IB only changed the problem - now, instead of it showing up when it's not supposed to, it's not showing up when it is.
 
Forgot to add: Before I made the change, I did check the view hierarchy. Of note - in the view debugger, the problem button's Hidden flag was "OFF" - bear in mind, this was sometime after the view appeared.
 
Needless to say, a button without any content isn't visible, regardless of its isHidden property. Does your button have some content?
 
Needless to say, a button without any content isn't visible, regardless of its isHidden property. Does your button have some content?

Yes. To recap:

When Hidden is OFF in the XIB, everything works as intended until I get past activityNum=0, familyNum=2, at which point the button stays visible despite my instruction to hide it.
When Hidden is ON (for the button, IIRC) in the XIB, everything works as intended until I get to activityNum=0, familyNum=2, at which point the button does not even show up.

Edit to add: Per viewDidLoad, all MenuItemViews start out hidden.
 
I'm trying to find a tutorial on how to build something that looks like the Angry Birds level menu. Can someone please point to a tutorial like that?
 
Moonman

What did you program before this attemp?

Building an Angry Birds style menu is not difficult.
 
Moonman

What did you program before this attemp?

Building an Angry Birds style menu is not difficult.
So I thought, until I actually tried it out myself. Before, though, I did have to add the views manually.

I just think if I can start over somewhat, following a tutorial of some sort, I could get ti right.
 
Register on MacRumors! This sidebar will go away, and you'll see fewer ads.