Subview's image not displaying

Discussion in 'iOS Programming' started by moonman239, Feb 27, 2017.

  1. moonman239, Feb 27, 2017
    Last edited: Feb 27, 2017

    moonman239 macrumors 68000

    Joined:
    Mar 27, 2009
    #1
    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.
     
  2. moonman239, Feb 28, 2017
    Last edited: Mar 1, 2017

    moonman239 thread starter macrumors 68000

    Joined:
    Mar 27, 2009
    #2
    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?
     
  3. chown33 macrumors 604

    Joined:
    Aug 9, 2009
    #3
    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.
     
  4. moonman239, Mar 1, 2017
    Last edited: Mar 1, 2017

    moonman239 thread starter macrumors 68000

    Joined:
    Mar 27, 2009
    #4
    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:
    --- Post Merged, Mar 1, 2017 ---
    UPDATE: When I put a breakpoint after addSubview in my didSet method, I get these logging statements from all the menu item views:
    --- Post Merged, Mar 1, 2017 ---
    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.
     
  5. moonman239 thread starter macrumors 68000

    Joined:
    Mar 27, 2009
    #5
    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);
    
       
    
        }
    
    
     
  6. chown33 macrumors 604

    Joined:
    Aug 9, 2009
    #6
    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
     
  7. bjet767 macrumors 6502a

    Joined:
    Oct 2, 2010
    #7
    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
    --- Post Merged, Mar 3, 2017 ---
    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:

    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).
     
  8. moonman239, Apr 21, 2017
    Last edited: Apr 24, 2017

    moonman239 thread starter macrumors 68000

    Joined:
    Mar 27, 2009
  9. PhoneyDeveloper macrumors 68030

    PhoneyDeveloper

    Joined:
    Sep 2, 2008
    #9
    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.
     
  10. bjet767 macrumors 6502a

    Joined:
    Oct 2, 2010
    #10
    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.
    --- Post Merged, Apr 23, 2017 ---
    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
     
  11. moonman239 thread starter macrumors 68000

    Joined:
    Mar 27, 2009

Share This Page