2017-03-26 1 views
0

J'ai commencé à créer mon premier projet Open Source que j'aimerais partager avec le monde entier. C'est une simple vue Audio Player qui fonctionnera avec le protocole AudioProvidable, puis jouera une donnée audio après sa réception.iOS Contrôle personnalisé avec éléments dynamiques

Pour l'instant, j'ai une classe IBDesignable hérite de UIView qui se construit en définissant des contraintes pour ses sous-vues comme celle-ci:

override init(frame: CGRect) { 
     super.init(frame: frame) 
#if !TARGET_INTERFACE 
     translatesAutoresizingMaskIntoConstraints = false 
#endif 
     prepareView() 
     updateUI() 
    } 

    required init?(coder aDecoder: NSCoder) { 
     super.init(coder: aDecoder) 
     prepareView() 
     updateUI() 
    } 

    func prepareView() { 
      //Necessary in order to set our own constraints 
      //btnPlay is already doing the same inside buttonWithImage(_:) 
      spinner.translatesAutoresizingMaskIntoConstraints = false 
      sliderAudioProgress.translatesAutoresizingMaskIntoConstraints = false 

      //Add necessary subviews to start setting up layout 
      addSubview(sliderAudioProgress) 
      addSubview(lblAudioDuration) 
      addSubview(lblAudioProgress) 
      addSubview(spinner) 
      addSubview(btnPlay) 
      addSubview(lblTrackNumber) 
      addSubview(btnNextTrack) 
      addSubview(btnPreviousTrack) 

      //Height of play & next/previous track buttons defines AudioPlayerView height 
      let viewHeight:CGFloat = 48 * 2 

      //Setup UI layout using constraints 
      NSLayoutConstraint.activate([ 

       heightAnchor.constraint(equalToConstant: viewHeight), 

       //Play button constraints 
       btnPlay.topAnchor.constraint(equalTo: topAnchor), 
       btnPlay.leadingAnchor.constraint(equalTo: leadingAnchor), 

       //Spinner constraints 
       spinner.centerYAnchor.constraint(equalTo: btnPlay.centerYAnchor), 
       spinner.centerXAnchor.constraint(equalTo: btnPlay.centerXAnchor), 

       //Progress label constraints 
       lblAudioProgress.leadingAnchor.constraint(equalTo: btnPlay.trailingAnchor), 
       lblAudioProgress.centerYAnchor.constraint(equalTo: btnPlay.centerYAnchor), 

       //Duration label constraints 
       lblAudioDuration.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -spacing), 
       lblAudioDuration.centerYAnchor.constraint(equalTo: btnPlay.centerYAnchor), 

       //Audio Progress Slider constraints 
       sliderAudioProgress.leadingAnchor.constraint(equalTo: lblAudioProgress.trailingAnchor, constant: spacing), 
       sliderAudioProgress.trailingAnchor.constraint(equalTo: lblAudioDuration.leadingAnchor, constant: -spacing), 
       sliderAudioProgress.centerYAnchor.constraint(equalTo: btnPlay.centerYAnchor), 

       lblTrackNumber.centerXAnchor.constraint(equalTo: centerXAnchor), 

       btnPreviousTrack.topAnchor.constraint(equalTo: btnPlay.bottomAnchor), 
       btnPreviousTrack.trailingAnchor.constraint(equalTo: lblTrackNumber.leadingAnchor, constant: -spacing), 

       btnNextTrack.topAnchor.constraint(equalTo: btnPlay.bottomAnchor), 
       btnNextTrack.leadingAnchor.constraint(equalTo: lblTrackNumber.trailingAnchor, constant: spacing), 

       lblTrackNumber.centerYAnchor.constraint(equalTo: btnNextTrack.centerYAnchor) 
      ]) 

      //Spinner setup: 
      spinner.hidesWhenStopped = true 

      //btnPlay setup: 
      btnPlay.addTarget(self, action: #selector(AudioPlayerView.playButtonPressed), for: .touchUpInside) 

      //Slider setup 
      sliderAudioProgress.thumbTintColor = UIColor(keyFromAppColorPalette: "secondary_color") 
      sliderAudioProgress.addTarget(self, action: #selector(AudioPlayerView.durationSliderValueChanged), for: .valueChanged) 
      sliderAudioProgress.addTarget(self, action: #selector(AudioPlayerView.durationSliderReleased), for: .touchUpInside) 
      sliderAudioProgress.isEnabled = false 
      sliderAudioProgress.isContinuous = true 
      sliderAudioProgress.semanticContentAttribute = .playback 

      //View's UI effects to make it look better 
      layer.cornerRadius = 6 
      layer.masksToBounds = true 
      layer.borderColor = UIColor.black.cgColor 
      layer.borderWidth = 2 
      semanticContentAttribute = .playback 

      spinner.startAnimating() 
      btnPlay.isHidden = true 
     } 

En conséquence qui est ce que nous avons lorsque l'audio reçoit:

enter image description here

Après l'audio reçu:

enter image description here

Dans le cas où il n'y a qu'un seul son pour lire la partie inférieure, les commutations entre les pistes sont redondantes. Où dois-je mettre un code de vérification des pistes audio pour redéfinir mon interface utilisateur? Je ne veux pas appeler prepareView() dans didSet {} de tableau de source audio, parce que je ne veux pas que les éléments existants soient ajoutés deux fois. Cordialement.

+1

Je ne suis pas clair sur ce vous demandez. (1) IBDesignable est un outil * design-time *, donc vous ne pouvez pas savoir à ce moment-là combien de pistes il y a. Ce morceau (du moins avec ce que vous avez dit) * doit * faire partie du code * run-time *. (2) En supposant cela, placez les vues de dessous dans une sorte de vue de conteneur (ce peut être un simple UIView) et au moment approprié dans le traitement - probablement une sorte de fermeture après "réception de l'audio" - cacher le tout. (3) Donc, faites en sorte que ce contrôle de conteneur ait une apparence * légèrement * différente dans IB pour indiquer qu'il sera caché s'il y a une piste à jouer. – dfd

+0

@dfd, merci pour votre réponse et une explication claire des choses. Je n'ai pas entièrement compris (3) d clause. Comment ce comportement pourrait être implémenté afin que 'UIView' ait une apparence différente dans IB plutôt que dans _run-time_. Merci. – GeRyCh

+0

Bonne question - Je n'y ai pas réfléchi. :-) Mon image mentale était quelque chose comme un look légèrement "plus léger" dans IB, en le modifiant dans le code, mais après un examen plus approfondi c'est déroutant pour les autres développeurs. Deux autres idées: (1) Considérant qu'il n'y a rien de vraiment * faux * avec l'affichage du bas s'il n'y a qu'une seule piste, pourquoi ne pas exposer une propriété comme * hideTrackCount * dans IB? (2) Au lieu de légèrement modifier l'apparence du fond, brouiller. Il utilise le même style (police, couleur, arrière-plan) que le reste. [De ces deux idées, j'aime mieux le premier.] – dfd

Répondre

1

Vous pouvez ajouter un drapeau bool pour voir si le code est en cours d'exécution pour la première fois, ou, ce que je trouve plus facile et peut-être un peu plus logique ...

func prepareView() { 

     //Necessary in order to set our own constraints 
     //btnPlay is already doing the same inside buttonWithImage(_:) 
     spinner.translatesAutoresizingMaskIntoConstraints = false 
     sliderAudioProgress.translatesAutoresizingMaskIntoConstraints = false 

     // if our subviews have not yet been added 
     if sliderAudioProgress.superview == nil { 

      //Add necessary subviews to start setting up layout 
      addSubview(sliderAudioProgress) 
      addSubview(lblAudioDuration) 
      ... etc 

     } 

     // continue here with any "every time" setup tasks 
    } 
+0

Merci! Pour sûr, il pourrait être acceptable comme l'une des solutions possibles – GeRyCh