Search code examples
iosxcodeautolayoutuiwindowairplay

External Display Crashing On Adding Subview


In my app, there is one small section that displays lyrics to songs (PDFs loaded in a WKWebView). What I want to be able to do, is when you mirror that screen to an Apple TV, still control it with the iPhone/iPad, but have it be the full screen, optimized for a TV display. I have the following code set up in the Class for viewing the songs, but it keeps crashing with autoLayoutConstraint errors when I try loading it. Anyone have an idea what's going on?

Songs.h:
@interface Songs : UIViewController <UIGestureRecognizerDelegate>{
    IBOutlet WKWebView *savedweb;
    NSString *selectedCountry;
    IBOutlet UIActivityIndicatorView *activity;
    NSTimer *timer;
}

@property (nonatomic, retain) NSString *selectedSong;
@property (nonatomic, retain) UIActivityIndicatorView *activity;
@property (nonatomic, retain) UIWindow *secondWindow;
@end

Songs.m

- (void)viewDidLoad {
    if ([UIScreen screens].count > 1) {
        [self setUpSecondWindowForScreen:[UIScreen screens][1]];
      }

      NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
      [center addObserver:self selector:@selector(handleScreenDidConnectNotification:)
                     name:UIScreenDidConnectNotification object:nil];
      [center addObserver:self selector:@selector(handleScreenDidDisconnectNotification:)
                     name:UIScreenDidDisconnectNotification object:nil];
    
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
       NSString *documentsDirectory = [paths objectAtIndex:0];
    NSString *pdfPath = [[documentsDirectory stringByAppendingPathComponent:selectedSong] stringByAppendingString:@".pdf"];
   
        NSURL *url = [NSURL fileURLWithPath:pdfPath];
        NSLog(@"%@",url);
        NSURLRequest *request = [NSURLRequest requestWithURL:url];
        [savedweb loadRequest:request];
        UITapGestureRecognizer *gesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(toggleNavBar:)];
        [self.view addGestureRecognizer:gesture];
        gesture.delegate = self;
        [gesture release];
        timer = [NSTimer scheduledTimerWithTimeInterval:(1.0/2.0) target:self selector:@selector(tick) userInfo:nil repeats:YES];
        
    
   
}
- (void)handleScreenDidConnectNotification:(NSNotification*)notification {
  if (!self.secondWindow) {
    [self setUpSecondWindowForScreen:[notification object]];
  }
}

- (void)handleScreenDidDisconnectNotification:(NSNotification*)notification {
  if (self.secondWindow) {
    self.secondWindow = nil;
  }
}
- (void)setUpSecondWindowForScreen:(UIScreen*)screen {
  self.secondWindow = [[UIWindow alloc] init];
  self.secondWindow.screen = screen;
  self.secondWindow.screen.overscanCompensation = UIScreenOverscanCompensationNone;

  UIViewController *viewController = [[UIViewController alloc] init];

    WKWebView *theSongView = [[WKWebView alloc] initWithFrame:viewController.view.frame];
    [theSongView setTranslatesAutoresizingMaskIntoConstraints:NO];
    theSongView.frame = CGRectMake(0, 0, viewController.view.bounds.size.width, viewController.view.bounds.size.height);
       [viewController.view addSubview:theSongView];
       
//This is where the error occurs
[viewController.view addConstraint:[NSLayoutConstraint constraintWithItem:theSongView attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:viewController.view.safeAreaLayoutGuide.bottomAnchor attribute:NSLayoutAttributeTop multiplier:1.0 constant:0]];
           [viewController.view addConstraint:[NSLayoutConstraint constraintWithItem:theSongView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:viewController.view.safeAreaLayoutGuide.topAnchor attribute:NSLayoutAttributeBottom multiplier:1.0 constant:0]];
           [viewController.view addConstraint:[NSLayoutConstraint constraintWithItem:theSongView attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:viewController.view attribute:NSLayoutAttributeLeft multiplier:1.0 constant:0]];
           [viewController.view addConstraint:[NSLayoutConstraint constraintWithItem:theSongView attribute:NSLayoutAttributeRight relatedBy:NSLayoutRelationEqual toItem:viewController.view attribute:NSLayoutAttributeRight multiplier:1.0 constant:0]];
        self.secondWindow.rootViewController = viewController;
    
        [self.secondWindow makeKeyAndVisible];
       
        NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
           NSString *documentsDirectory = [paths objectAtIndex:0];
        NSString *pdfPath = [[documentsDirectory stringByAppendingPathComponent:selectedSong] stringByAppendingString:@".pdf"];
       
            NSURL *url = [NSURL fileURLWithPath:pdfPath];
            NSLog(@"%@",url);
            NSURLRequest *request = [NSURLRequest requestWithURL:url];
            [theSongView loadRequest:request];
          
    }

Solution

  • The error message I get with your code includes:

    Constraint items must each be a view or layout guide.
    

    So, it's pretty clear where to look.

    Formatting your first constraint for readability:

    [viewController.view addConstraint:[
        NSLayoutConstraint constraintWithItem:theSongView
        attribute:NSLayoutAttributeBottom
        relatedBy:NSLayoutRelationEqual
        toItem:viewController.view.safeAreaLayoutGuide.bottomAnchor
        attribute:NSLayoutAttributeTop
        multiplier:1.0
        constant:0]];
    

    You're trying to set the theSongView's Bottom constraint equal to a .bottomAnchor's Top constraint. Which is, of course, not what you want to do.

    If you change that constraint (and fix the others) like this:

    [viewController.view addConstraint:[
        NSLayoutConstraint constraintWithItem:theSongView
        attribute:NSLayoutAttributeBottom
        relatedBy:NSLayoutRelationEqual
        toItem:viewController.view.safeAreaLayoutGuide
        attribute:NSLayoutAttributeBottom
        multiplier:1.0
        constant:0]];
    

    You should be on your way.

    I would strongly suggest, though, that you start using the more modern syntax for layout constraints:

    UILayoutGuide *safeG = [viewController.view safeAreaLayoutGuide];
    
    [NSLayoutConstraint activateConstraints:@[
        [theSongView.topAnchor constraintEqualToAnchor:safeG.topAnchor],
        [theSongView.leadingAnchor constraintEqualToAnchor:safeG.leadingAnchor],
        [theSongView.trailingAnchor constraintEqualToAnchor:safeG.trailingAnchor],
        [theSongView.bottomAnchor constraintEqualToAnchor:safeG.bottomAnchor],
    ]];