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
I have 8 UIViews that represent levels in my app. For each such view:
If the user has unlocked, but not completed the level, the view displays a button.
If the user has unlocked and completed the level, the view displays an image.
Otherwise, the view displays nothing.

At least, that's how it's supposed to work. In reality, there is one view that displays nothing even if I complete the level it represents. The other views work just well. However, this is the only one of the 8 views that I created in code (due to a prior problem with the view's button, which went away when I switched to using code).
Here is the view's code:
Code:
class MenuItemView: UIView {

    var currentStatus : ActivityStatus = ActivityStatus.NotThereYet

    var starNum : Int = 1

    let button: UIButton! = UIButton()

    @IBOutlet var starImageView: UIImageView? {

        didSet {

          

            // Load star.

            let mainBundle = Bundle(for: MenuItemView.self)

            let starURL = mainBundle.path(forResource: "\(starNum)star", ofType: "png", inDirectory:"pictures")

            let starImage = UIImage(contentsOfFile: starURL!)

            starImageView!.image = starImage!

            button.bringSubview(toFront: button)

            // Make sure star image view is added

            self.addSubview(self.starImageView!)

            // Hide view

            self.starImageView?.isHidden = true

        }

    }/** Star image view. Defined in storyboard **/

    func buttonPressed() {

        (superview as! ExerciseMenuView).buttonPressed(sender: self)

    }

    func addPlayButton() // Adds the "Play" button

    {

        // Give the button its image.

        let mainBundle = Bundle(for: MenuItemView.self)

        let playURL = mainBundle.path(forResource: "play-button", ofType: "png", inDirectory:"pictures")

      

        let playImage = UIImage(contentsOfFile: playURL!)

        button.setBackgroundImage(playImage, for: UIControlState.normal)

      

        // Add button to self.

        self.addSubview(button)

        // Respond to button taps

        button.addTarget(self, action: #selector(MenuItemView.buttonPressed), for: UIControlEvents.touchUpInside)

        // Make sure button doesn't accidentally resize

        button.translatesAutoresizingMaskIntoConstraints = true

        // Size button to fill view.

        button.frame = self.bounds

        // Size button image to fit

        button.contentMode = UIViewContentMode.scaleAspectFit

    }

    required init?(coder aDecoder: NSCoder) {

        super.init(coder: aDecoder)

      addPlayButton()

    }

    override init(frame: CGRect) { // Create the missing view in level 3

        super.init(frame: frame)

        addPlayButton()

        // Initialize the star image view.

        starImageView = UIImageView(frame: self.bounds)

    }

    func setActivityStatus(newStatus : ActivityStatus) {

        self.isHidden = false

        currentStatus = newStatus

        switch newStatus {

        case .Next:

            button.isHidden = false

            button.isUserInteractionEnabled = true

        case .Completed:

            starImageView!.isHidden = false

            button.isHidden = true

        default:

            button.isHidden = true

            starImageView!.isHidden = true

            break

          

        }

        // Log

        if (self.tag == 0)

        {

        print("View with tag \(self.tag) button \(button) imageview: \(starImageView)")

        }

    }

    /*

    // Only override drawRect: if you perform custom drawing.

    // An empty implementation adversely affects performance during animation.

    override func drawRect(rect: CGRect) {

        // Drawing code

    }

    */

}

A few things I've checked:
1) The image view's frame - it's what it should be
2) If the image view's set to hidden when it shouldn't be - it's not.
Edit: Also of note, the image URL is unwrapped with a ! so it's clearly not that.
 
Last edited:
Interestingly:

1) When I go to the view debugger, the problem image view doesn't show up, either in the view screen or in the left pane.
2) When I check the image view's superview from update(), I get "nil"
Edit: I do this to add the superview:
Code:
@IBOutlet var starImageView: UIImageView? {

        didSet {

           

            // Load star.

            let mainBundle = Bundle(for: MenuItemView.self)

            let starURL = mainBundle.path(forResource: "\(starNum)star", ofType: "png", inDirectory:"pictures")

            let starImage = UIImage(contentsOfFile: starURL!)

            starImageView!.image = starImage!

            button.bringSubview(toFront: button)

            // Make sure star image view is added

            self.addSubview(self.starImageView!)

            // Hide view

            self.starImageView!.isHidden = true

        }
Should I show you my viewWillAppear method?
 
Last edited:
Interestingly:

1) When I go to the view debugger, the problem image view doesn't show up, either in the view screen or in the left pane.
2) When I check the image view's superview from update(), I get "nil"
Should I show you my viewWillAppear method?
Given what you posted in another thread:
https://forums.macrumors.com/threads/subviews-button-loading-but-not-showing.2031962/#post-24350118

I suggest a first step is to add some logging to your viewWillAppear method, and ensure that the text you log actually shows up as output. In short, confirm your expectation.

If this seems obvious, and you already have confirmation that the view is actually being added, then break the problem down into smaller steps. Add logging (or some other confirmable result) in each function that should be getting called. Then confirm that each function is called, by looking for the logged output.

Another strategy is to use the debugger and set a breakpoint on the custom view's viewWillAppear func. If the breakpoint is hit, look at the context. If the breakpoint isn't hit, think of other ways you might confirm that the custom view is getting added correctly.

What you're doing with the logging or breakpoints is applying the Break It Down principle to the time-ordered sequence of expected function calls. There are several funcs that are expected to be called. Confirm that the earliest of these is actually being called. If it's not, work backwards in the calling sequence to another point you expect something to happen. Put a confirmation there (breakpoint or log). Repeat, moving earlier in the sequence of calls, until you get at least one confirmation. Then move later in the expected sequence.
 
I just added a logging statement to viewWillAppear:

Code:
- (void)viewWillAppear:(BOOL)animated

{

    [super viewWillAppear:animated];

    // Get place entity for current level

    entity = [[UserEntity sharedEntity] placeEntityForLevel:[LevelObject currentLevel]];

    elements = [self.element elementChildren];

    NSAssert([[elements.firstObject elementName] isEqualToString:@"wordFamily"], @"Expected word families.");

    // Pass relevant information to levelView

    levelView.wordFamilyIndex = entity.familyNum;

    levelView.activityIndex = entity.activityNum;

    levelView.viewController = self;

    [levelView update];

    // Logging statements for self-made view

    MenuItemView *item3view = [self.view.subviews.firstObject viewWithTag:2];

    UIImageView *starImageView = [item3view starImageView];

    NSLog(@"For view #3, star image view superview: %@, frame: %@, hidden: %hhd, ",starImageView.superview,NSStringFromCGRect(starImageView.frame),starImageView.hidden);

 

    }

And I got this message:
For view #3, star image view superview: (null), frame: {{0, 0}, {0, 0}}, hidden: 0,
[doublepost=1488420119][/doublepost]UPDATE: When I put a breakpoint after addSubview in my didSet method, I get these logging statements from all the menu item views:
(UIView?) $R0 = 0x16ed5b40 {

UIKit.UIResponder = {

ObjectiveC.NSObject = {}

}

}

(UIView?) $R1 = 0x16ed7900 {

UIKit.UIResponder = {

ObjectiveC.NSObject = {}

}

}

(UIView?) $R2 = 0x16eda130 {

UIKit.UIResponder = {

ObjectiveC.NSObject = {}

}

}

(UIView?) $R3 = 0x16edc3b0 {

UIKit.UIResponder = {

ObjectiveC.NSObject = {}

}

}

(UIView?) $R4 = 0x16da72f0 {

UIKit.UIResponder = {

ObjectiveC.NSObject = {}

}

}

(UIView?) $R5 = 0x16da9850 {

UIKit.UIResponder = {

ObjectiveC.NSObject = {}

}

}

(UIView?) $R6 = 0x16dabff0 {

UIKit.UIResponder = {

ObjectiveC.NSObject = {}

}

}

(UIView?) $R7 = 0x16dae790 {

UIKit.UIResponder = {

ObjectiveC.NSObject = {}

}

}

(UIView?) $R8 = 0x16db0f30 {

UIKit.UIResponder = {

ObjectiveC.NSObject = {}

}

}

(UIView?) $R9 = 0x16edeba0 {

UIKit.UIResponder = {

ObjectiveC.NSObject = {}

}

}

(UIView?) $R10 = 0x16ee1500 {

UIKit.UIResponder = {

ObjectiveC.NSObject = {}

}

}

(UIView?) $R11 = 0x16ee3830 {

UIKit.UIResponder = {

ObjectiveC.NSObject = {}

}

}
[doublepost=1488421014][/doublepost]Fixed it! Lesson learned: If I'm going to create and configure a view manually, it's best to do both the creating and the configuring in one function. In other words: Don't use didSet to add a subview to a view (in this case, that subview is the image view), initWithFrame to create the subview, etc.
 
Last edited:
I just added a logging statement to viewWillAppear:

Code:
- (void)viewWillAppear:(BOOL)animated

{

    [super viewWillAppear:animated];

    // Get place entity for current level

    entity = [[UserEntity sharedEntity] placeEntityForLevel:[LevelObject currentLevel]];

    elements = [self.element elementChildren];

    NSAssert([[elements.firstObject elementName] isEqualToString:@"wordFamily"], @"Expected word families.");

    // Pass relevant information to levelView

    levelView.wordFamilyIndex = entity.familyNum;

    levelView.activityIndex = entity.activityNum;

    levelView.viewController = self;

    [levelView update];

    // Logging statements for self-made view

    MenuItemView *item3view = [self.view.subviews.firstObject viewWithTag:2];

    UIImageView *starImageView = [item3view starImageView];

    NSLog(@"For view #3, star image view superview: %@, frame: %@, hidden: %hhd, ",starImageView.superview,NSStringFromCGRect(starImageView.frame),starImageView.hidden);

 

    }

And I got this message:

[doublepost=1488420119][/doublepost]UPDATE: When I put a breakpoint after addSubview in my didSet method, I get these logging statements from all the menu item views:

[doublepost=1488421014][/doublepost]Fixed it! Lesson learned: If I'm going to create and configure a view manually, it's best to do both the creating and the configuring in one function. In other words: Don't use didSet to add a subview to a view (in this case, that subview is the image view), initWithFrame to create the subview, etc.

OK, back to square 1 because I code and don't remember what I did.

Here's my new viewWillAppear code:
Code:
- (void)viewWillAppear:(BOOL)animated

{

    [super viewWillAppear:animated];

    // Get place entity for current level

    entity = [[UserEntity sharedEntity] placeEntityForLevel:[LevelObject currentLevel]];

    elements = [self.element elementChildren];

    NSAssert([[elements.firstObject elementName] isEqualToString:@"wordFamily"], @"Expected word families.");

    // Pass relevant information to levelView

    levelView.wordFamilyIndex = entity.familyNum;

    levelView.activityIndex = entity.activityNum;

    levelView.viewController = self;

    [levelView update];

    // Create missing menu item views

    MenuItemView *menuItemView = [[MenuItemView alloc] initWithFrame:CGRectMake(764, 543, 211, 128)];

    menuItemView.tag = 2;

    // Add star image view and button

    menuItemView.starImageView = [[UIImageView alloc] initWithFrame:menuItemView.bounds];

    [menuItemView addPlayButton];

    [menuItemView addSubview:menuItemView.starImageView];

    // Logging statements for self-made view

    MenuItemView *item3view = [self.view.subviews.firstObject viewWithTag:2];

    UIImageView *starImageView = [item3view starImageView];

    NSLog(@"For view #3, star image view superview: %@, frame: %@, hidden: %hhd, ",starImageView.superview,NSStringFromCGRect(starImageView.frame),starImageView.hidden);

   

    }
 
OK, back to square 1 because I code and don't remember what I did.

Here's my new viewWillAppear code:
Code:
- (void)viewWillAppear:(BOOL)animated

{

    [super viewWillAppear:animated];

    // Get place entity for current level

    entity = [[UserEntity sharedEntity] placeEntityForLevel:[LevelObject currentLevel]];

    elements = [self.element elementChildren];

    NSAssert([[elements.firstObject elementName] isEqualToString:@"wordFamily"], @"Expected word families.");

    // Pass relevant information to levelView

    levelView.wordFamilyIndex = entity.familyNum;

    levelView.activityIndex = entity.activityNum;

    levelView.viewController = self;

    [levelView update];

    // Create missing menu item views

    MenuItemView *menuItemView = [[MenuItemView alloc] initWithFrame:CGRectMake(764, 543, 211, 128)];

    menuItemView.tag = 2;

    // Add star image view and button

    menuItemView.starImageView = [[UIImageView alloc] initWithFrame:menuItemView.bounds];

    [menuItemView addPlayButton];

    [menuItemView addSubview:menuItemView.starImageView];

    // Logging statements for self-made view

    MenuItemView *item3view = [self.view.subviews.firstObject viewWithTag:2];

    UIImageView *starImageView = [item3view starImageView];

    NSLog(@"For view #3, star image view superview: %@, frame: %@, hidden: %hhd, ",starImageView.superview,NSStringFromCGRect(starImageView.frame),starImageView.hidden);

  

    }
Exactly what is your question or problem?

What did you expect to happen? What actually happened?
In short, describe your expectation and your confirmation.


In general, do not create, init, or add subviews in the viewWillAppear method. Only create, init, or add subviews within a create or init method.

When you create, init, or add subviews in viewWillAppear, then you become responsible for the life-cycle calls to those subviews. In particular, you become responsible for calling viewWillAppear on the subviews.

I suggest the google search terms: ios view life cycle
 
Moonman

This is the second thread you've asked essentially the same question. From what I see I think you don't really understand the iOS view methods or how they are called and stacked in their que as subviews.

From reading you question I found it rather easy, using story board, to code in some buttons using subviews place on the main view. Here's the code for the ViewController (Objective C).
Code:
//

//  ViewController.m

//  SubView Buttons

//

//  Created by xxxxx on 3/3/17.

//  Copyright © 2017 xxxxx. All rights reserved.

//


#import "ViewController.h"


@interface ViewController ()


[USER=2234]@end[/USER]


@implementation ViewController


- (void)viewDidLoad {

    [super viewDidLoad];

    // Do any additional setup after loading the view, typically from a nib.

}


-(void)viewDidAppear:(BOOL)animated{

  

    button1 = [[UIButton alloc]initWithFrame:_subView1.bounds];

    button2 = [[UIButton alloc]initWithFrame:_subView2.bounds];

    button3 = [[UIButton alloc]initWithFrame:_subView3.bounds];

    button4 = [[UIButton alloc]initWithFrame:_subView4.bounds];

  

    [_subView1 addSubview:button1];

    [_subView2 addSubview:button2];

    [_subView3 addSubview:button3];

    [_subView4 addSubview:button4];

  

    [button1 setImage:[UIImage imageNamed:mad:"Home_000000_25.png"] forState:UIControlStateNormal];

    [button2 setImage:[UIImage imageNamed:mad:"Plus_000000_25.png"] forState:UIControlStateNormal];

    [button3 setImage:[UIImage imageNamed:mad:"Cancel_000000_25.png"] forState:UIControlStateNormal];

    [button4 setImage:[UIImage imageNamed:mad:"big_button.jpg"] forState:UIControlStateNormal];

  

    [button1 addTarget:self action:mad:selector(button1Action) forControlEvents:UIControlEventTouchUpInside];

    [button2 addTarget:self action:mad:selector(button2Action) forControlEvents:UIControlEventTouchUpInside];

    [button3 addTarget:self action:mad:selector(button3Action) forControlEvents:UIControlEventTouchUpInside];

    [button4 addTarget:self action:mad:selector(button4Action) forControlEvents:UIControlEventTouchUpInside];

}



- (void)didReceiveMemoryWarning {

    [super didReceiveMemoryWarning];

    // Dispose of any resources that can be recreated.

}


-(void)button1Action

{

    NSLog(@"Button 1 Pressed");

  

}


-(void)button2Action

{

    NSLog(@"Button 2 Pressed");

  

}


-(void)button3Action

{

    NSLog(@"Button 3 Pressed");

  

}


-(void)button4Action

{

    NSLog(@"Button 4 Pressed");

  

}


And the screen shot of the iPad Sim:

Screen Shot 2017-03-03 at 6.31.49 PM.png

[doublepost=1488592062][/doublepost]I apologize for the emoticons they came automatically with the OC code.

I just think you are missing the ease of building these things. Oh and this:

When you create, init, or add subviews in viewWillAppear, then you become responsible for the life-cycle calls to those subviews. In particular, you become responsible for calling viewWillAppear on the subviews.

really isn't completely true. The subviews remain attached to the view and will update when the view is called and will die when the main view is killed (unless you retain the subview). iOS takes care of this stuff.

"When the actual content of your view changes, it is your responsibility to notify the system that your view needs to be redrawn. You do this by calling your view’s setNeedsDisplay() or setNeedsDisplay(_:) method of the view. These methods let the system know that it should update the view during the next drawing cycle. Because it waits until the next drawing cycle to update the view, you can call these methods on multiple views to update them at the same time." From Apple documentation

Essentially the adding of a subview causes it to appear, but don't forget there may be more than one subview and only the one on top is in view (unless it is also transparent in color, which I also use).
 
Not sure but: awakeFromNib() or initWithCoder after calling super is probably the time to start messing with subviews. Using didSet on an IBOutlet might be too early.
 
OK I went back to the beginning of this thread and I believe what you want to do is a game with an intial menu which opens up a new UIView as one progresses through the levels, like "Angry Birds."

So why aren't you using a simpler method than building buttons from code?

I would have maintained a user file (NSUserDefaults) that simply saves a Boolean value for each level in you app that is achieved. As each level is reached just enable the UIButton associated with the next view. Use messaging to inform and update the user defaults and button when the level is completed and the ready to move to the next.

Each UIButton on your inital UIView can easily be linked (segued) to the next UIViewController (and associated UIView) of the level. Of you're worried about memory control this method is managed much better by the API than building each UIView and UIButton on the fly.

The best part of the "segued" system is you can give the segue and identifier and then call it later with
Code:
"[self performSegueWithIdentifier:@"yourSegueIdentifer" sender:self];"

If you are using a tabbar you can also call the tab from other UIViews by getting the tabbarcontroller and then selecting the index of the needed tab item.

Code:
MyTabBarController *vc = [segue destinationViewController];

 vc.selectedIndex = 0; //where the index is the desired tab item.

Good luck, and yes I like using the 'Storyboard' when ever possible.
[doublepost=1492963701][/doublepost]
Not sure but: awakeFromNib() or initWithCoder after calling super is probably the time to start messing with subviews. Using didSet on an IBOutlet might be too early.

BTW I believe this is correct.

In my opinion it's best to add subviews after the parent/super view is displayed(or at least initialized, viewDidLoad,) and in looking over Apple's documentation I believe it show the same.

Example questions:

http://stackoverflow.com/questions/13786251/how-to-use-a-custom-uibutton-with-interface-builder

https://forums.developer.apple.com/thread/8375
 
Register on MacRumors! This sidebar will go away, and you'll see fewer ads.