Застрял с tvOS и Focus Guides - PullRequest
       105

Застрял с tvOS и Focus Guides

1 голос
/ 04 августа 2020

Я действительно застрял в этой проблеме. Я пытаюсь добавить кнопку поверх AVPlayerViewController с надписью «Пропустить вступление» (аналогично кнопке, которую Netflix имеет в своем содержимом).

Я добавил 4 или 5 кнопок по горизонтали, начиная чуть выше левая сторона панели поиска и каждая кнопка разнесены примерно на 10 пунктов. Мне нужна только одна кнопка, но на данный момент я не могу понять, почему я вообще не могу нажать ни одну из этих кнопок:

enter image description here

I've declared the button and the focus guide variables as private:

введите описание изображения здесь

Вот метод viewDidLoad:

- (void)viewDidLoad
{
    [super viewDidLoad];
    
    self.avPlayerViewController = [[AVPlayerViewController alloc] init];
    [self addChildViewController:self.avPlayerViewController];
    [self.view addSubview:self.avPlayerViewController.view];
    self.avPlayerViewController.showsPlaybackControls = YES;

    self.avPlayerViewController.view.translatesAutoresizingMaskIntoConstraints = NO;
    
    NSLayoutConstraint *height =  [NSLayoutConstraint constraintWithItem:self.avPlayerViewController.view
                                                               attribute:NSLayoutAttributeHeight
                                                               relatedBy:NSLayoutRelationEqual
                                                                  toItem:self.view
                                                               attribute:NSLayoutAttributeHeight
                                                              multiplier:1.0
                                                                constant:0];
    
    NSLayoutConstraint *width =  [NSLayoutConstraint constraintWithItem:self.avPlayerViewController.view
                                                              attribute:NSLayoutAttributeWidth
                                                              relatedBy:NSLayoutRelationEqual
                                                                 toItem:self.view
                                                              attribute:NSLayoutAttributeWidth
                                                             multiplier:1.0
                                                               constant:0.0];
    
    NSLayoutConstraint *top =  [NSLayoutConstraint constraintWithItem:self.avPlayerViewController.view
                                                            attribute:NSLayoutAttributeTop
                                                            relatedBy:NSLayoutRelationEqual
                                                               toItem:self.view
                                                            attribute:NSLayoutAttributeTop
                                                           multiplier:1.0
                                                             constant:0.0];
    
    NSLayoutConstraint *leading =  [NSLayoutConstraint constraintWithItem:self.avPlayerViewController.view
                                                                attribute:NSLayoutAttributeLeading
                                                                relatedBy:NSLayoutRelationEqual
                                                                   toItem:self.view
                                                                attribute:NSLayoutAttributeLeading
                                                               multiplier:1.0
                                                                 constant:0.0];
    
    [self.view addConstraint:height];
    [self.view addConstraint:width];
    [self.view addConstraint:top];
    [self.view addConstraint:leading];
    
    [self.avPlayerViewController didMoveToParentViewController:self];
    [self setupBreakButtonWithWidth:225 height:50 xCoordinate:100 yCoordinate:850];
    [self setupBreakButtonWithWidth:225 height:50 xCoordinate:335 yCoordinate:850];
    [self setupBreakButtonWithWidth:225 height:50 xCoordinate:570 yCoordinate:850];
    [self setupBreakButtonWithWidth:225 height:50 xCoordinate:805 yCoordinate:850];
    
    [self setupFocusGuide];
    [self createBannerAdView];
    [self resetUpNextState];
}

Как видите, я просто добавляю кнопки в нижнюю часть экрана (над полосой поиска), а затем настраиваю руководство по фокусу и предпочтительные средыFocusEnvironments:

- (NSArray<id<UIFocusEnvironment>> *)preferredFocusEnvironments {

    NSLog(@"**** BMNativeVideoPlayerViewController -> preferredFocusEnvironments");
    return @[self.skipBreakButton];
}

- (void)setupFocusGuide
{
    NSLog(@"**** BMNativeVideoPlayerViewController -> setupFocusGuide");
     
    // allocate focus guide and add it to the view
    self.focusGuide = [[UIFocusGuide alloc] init];
    [self.view addLayoutGuide:self.focusGuide];
     
    // define constraints
    [self.focusGuide.leftAnchor constraintEqualToAnchor:self.view.leftAnchor].active = YES;
    [self.focusGuide.rightAnchor constraintEqualToAnchor:self.view.rightAnchor].active = YES;
    
    [self.focusGuide.topAnchor constraintEqualToAnchor:_skipBreakButton.topAnchor].active = YES;
    [self.focusGuide.bottomAnchor constraintEqualToAnchor:_skipBreakButton.bottomAnchor].active = YES;
    
    // select the default focusable view
    self.focusGuide.preferredFocusEnvironments = @[self.skipBreakButton];
}

Вот метод, в котором настраивается кнопка, а также метод обработчика кнопки и метод didUpdateFocusInContext (хотя я не знаю, делает ли он то, что должен ):

- (void)didUpdateFocusInContext:(UIFocusUpdateContext *)context withAnimationCoordinator:(UIFocusAnimationCoordinator *)coordinator
{
    self.focusGuide.preferredFocusEnvironments = @[self.skipBreakButton];
}

- (void)setupBreakButtonWithWidth:(CGFloat)width height:(CGFloat)height xCoordinate:(CGFloat)x yCoordinate:(CGFloat)y
{
    NSLog(@"**** BMNativeVideoPlayerViewController -> setupBreakButton");
    _skipBreakButton = [[UIButton alloc] init];
    _skipBreakButton.backgroundColor = [UIColor whiteColor];
    [self.skipBreakButton setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
    _skipBreakButton.alpha = 1.0f;
    _skipBreakButton.layer.cornerRadius = 3;
    
    _skipBreakButton.translatesAutoresizingMaskIntoConstraints = NO;
    [self.avPlayerViewController.contentOverlayView addSubview:_skipBreakButton];
    [self.avPlayerViewController.contentOverlayView bringSubviewToFront:_skipBreakButton];
    
    NSLayoutConstraint *xConstraint = [NSLayoutConstraint
                                       constraintWithItem:_skipBreakButton attribute:NSLayoutAttributeLeading
                                       relatedBy:NSLayoutRelationEqual toItem:self.view attribute:
                                       NSLayoutAttributeLeading multiplier:1.0 constant:x];
    
    NSLayoutConstraint *yConstraint = [NSLayoutConstraint
                                       constraintWithItem:_skipBreakButton attribute:NSLayoutAttributeTop
                                       relatedBy:NSLayoutRelationEqual toItem:self.view attribute:
                                       NSLayoutAttributeTop multiplier:1.0f constant:y];
    
    NSLayoutConstraint *widthConstraint = [NSLayoutConstraint constraintWithItem:_skipBreakButton
                                                                       attribute:NSLayoutAttributeWidth
                                                                       relatedBy:NSLayoutRelationEqual
                                                                          toItem:nil
                                                                       attribute:NSLayoutAttributeNotAnAttribute
                                                                      multiplier:1.0
                                                                        constant:width];
    
    NSLayoutConstraint *heightConstraint = [NSLayoutConstraint constraintWithItem:_skipBreakButton
                                                                        attribute:NSLayoutAttributeHeight
                                                                        relatedBy:NSLayoutRelationEqual
                                                                           toItem:nil
                                                                        attribute:NSLayoutAttributeNotAnAttribute
                                                                       multiplier:1.0
                                                                         constant:height];
    
    [self.view addConstraints:@[xConstraint, yConstraint, widthConstraint, heightConstraint]];
    [self.skipBreakButton setUserInteractionEnabled:YES];
    [self.skipBreakButton setTitle:@"Skip Intro" forState:UIControlStateNormal];
    [self.skipBreakButton.titleLabel setFont:[UIFont fontWithName:@"Helvetica-Bold" size:26.0]];
    
    NSLog(@"**** Can the skip break button be focused? %d", self.skipBreakButton.canBecomeFocused);
    
    [self.skipBreakButton addTarget:self action:@selector(skipBreakButtonClicked:) forControlEvents:UIControlEventPrimaryActionTriggered];
}

- (void)skipBreakButtonClicked:(UIButton *) sender {
    NSLog(@"**** BMNativeVideoPlayerViewController -> skipBreakButtonClicked");
}

Я не могу нажать ни на одну из этих кнопок. Мне нужна помощь, чтобы понять, как заставить его работать.

Кажется, над этой кнопкой отображается вид, но я не уверен, как это исправить:

(lldb) po [UIFocusDebugger checkFocusabilityForItem: 0x7ff86fe60510]
The following issues were found that would prevent this item from being focusable:
 - ISSUE: One or more ancestors have issues that may be preventing this item from being focusable. Details:
    <_AVPlayerViewControllerContainerView 0x7ff86fc29eb0>:
         - ISSUE: This view returns YES from -canBecomeFocused, which will prevent its subviews from being focusable.

Посоветуйте, пожалуйста?

Ответы [ 2 ]

0 голосов
/ 20 августа 2020

Вы можете добиться такого поведения, представив контроллер модального представления (с кнопкой пропуска на нем) как наложение поверх контекста проигрывателя.

Затем вы можете закрыть контроллер модального представления через несколько секунд ( как это сделано в примере ниже), или программно, если вы знаете, когда действительно заканчивается вступление.

import UIKit
import AVFoundation
import AVKit

class ThePlayerViewController: AVPlayerViewController {
    
    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        play(stream: URL(string: "https://devstreaming-cdn.apple.com/videos/streaming/examples/img_bipbop_adv_example_ts/master.m3u8")!)
        presentSkipOverlay()
    }
    
    // MARK: - Private
    
    private func play(stream: URL) {
        let asset = AVAsset(url: stream)
        let playetItem = AVPlayerItem(asset: asset)
        player = AVPlayer(playerItem: playetItem)
        player?.play()
    }
    
    private func presentSkipOverlay() {
        let skipOverlayViewController = SkipOverlayViewController()
        
        skipOverlayViewController.onSkip = {
            [weak self] in
            
            // Skip the intro here
            self?.player?.seek(to: CMTime(seconds: 60.0, preferredTimescale: 1))
        }
        
        skipOverlayViewController.modalPresentationStyle = .overCurrentContext
        skipOverlayViewController.accessibilityViewIsModal = true
        present(skipOverlayViewController, animated: true, completion: {

            // Dismiss the overlay automatically after 3 seconds
            DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
                skipOverlayViewController.dismiss(animated: true, completion: nil)
            }
        })
    }
}

И это было бы наивной реализацией контроллера представления Overlay:

final class SkipOverlayViewController: UIViewController {
    
    var onSkip: (() -> Void)?
    
    override func viewDidLoad() {
        super.viewDidLoad()
        setUpView()
    }
    
    // MARK: - Private
    
    private lazy var skipButton: UIButton = {
        let skipButton = UIButton()
        skipButton.backgroundColor = .white
        skipButton.setTitleColor(.black, for: .normal)
        skipButton.setTitle("Skip Intro", for: .normal)
        skipButton.addTarget(self, action: #selector(skipButtonWasPressed), for: .primaryActionTriggered)
        return skipButton
    }()
    
    private func setUpView() {
        view.addSubview(skipButton)
        
        skipButton.translatesAutoresizingMaskIntoConstraints = false
        skipButton.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor).isActive = true
        skipButton.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -200).isActive = true
        skipButton.widthAnchor.constraint(equalToConstant: 225).isActive = true
    }
    
    // MARK: - Actions
    
    @objc
    func skipButtonWasPressed() {
        print("Skip button was Pressed")
        onSkip?()
    }
}

И вот результат:

введите описание изображения здесь

0 голосов
/ 13 августа 2020

Контроллер представления AVPlayer крадет все входные данные представления, когда он находится в полноэкранном режиме. По моему опыту, что вам нужно сделать, так это сделать так, чтобы проигрыватель был на 1 пиксель меньше, чем в полноэкранном режиме, когда у вас есть пользовательские элементы, а затем, когда ваши пользовательские элементы не на экране, вы можете сделать его полноразмерным. Если вы установите точки останова в своем коде переопределения фокуса, вы увидите, что он не попадает, и это связано с тем, что Apple контролирует входы на своем AVPlayerViewController.

Если вы попробуете вышеуказанное, это должно решить проблему.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...