wtorek, 3 maja 2016

[DSP2016] iOS prosto z poligonu odc.1 (UINavigationBar, UIToolbar, pliki z Music Library, systemMusicPlayer)

Trzeba już pisać app-kę na iOS-a. Dziś przedstawię pierwsze kroki poczynione w interfejsie użytkownika i przy odtwarzaniu plików audio.

Obecna wersja zawiera następujący ekran:

IMG_0021

Naciśnięcie ikony Search na pasku nawigacyjnym spowoduje otwarcie systemowego widoku, z poziomu którego możemy wybrać utwory do odtwarzania:

IMG_0022  IMG_0023

Jeśli jakiś utwór jest odtwarzany lub jesteśmy w trakcie pauzy na dole pierwszego czarnego ekranu znajdziemy toolbar pozwalający włączyć odtwarzanie/pauzę.  Większość jego obszaru zajmuje tytuł utworu, którego dotyczą te operacje. Muzyka odtwarzana jest także w tle, kiedy opuścimy aplikację.

Jak to osiągnęliśmy?

Jeśli chodzi o UI przełożyło się to na kilka chwytów.

Przy pasku nawigacyjnym może okazać się pomocny tutorial https://developer.apple.com/library/ios/referencelibrary/GettingStarted/DevelopiOSAppsSwift/Lesson8.html.  Dodatkowo ustawiłem jego styl na Black. Zdarzenie kliknięcia robimy tradycyjnie jak na Xcode, czyli trzymając CTRL przeciągamy na plik z kodem kontrolera i wybieramy w oknie, że chcemy akcję.

Przy pasku narzędziowym przydatny okazał się przykład z rozdziału 12-tego książki More iPhone Development with Swift, Exploring the iOS SDK, który przede wszystkim jest bardzo dobrym przewodnikiem po multimediach w iOS (po tych wysokopoziomowych). Wzorując się na aplikacji Music zapragnąłem jednak w toolbarze umieścić zwykłą labelkę jako tytuł granej piosenki. Aby to zrobić posłużyłem się sztuczką ze strony https://www.depicus.com/blog/adding-a-uilabel-to-a-uitoolbar-the-easy-way/.  Dzięki niej robimy wszystko za pomocą designera, bez dopisywania linijki kodu. Pamiętam, że tutaj były jeszcze różne przypadłości z layoutem, z którymi uporałem się kontrolując warunki w designerze Xcode (widzimy je w strukturze po lewej w designerze Storyboard, na dole ekranu część ikon także jest przydatna - Align i Pin). Akcję do przycisku w toolbarze podpinamy tak jak do każdego innego przycisku. Dodatkowo zrobiłem outlet (referencję) na labelkę z poziomu kodu, by móc ją aktualizować.

Oba paski - nawigacyjny i narzędziowy - układają się prawidłowo zarówno w orientacji pionowej, jak i poziomej ekranu (w nawigacyjnym nic nie robiłem, w toolbarze upewniłem się tylko co do warunków na lyout, na początku “zrobiła się” sztywna odległość “od góry”, która bruździła).

Teraz omówimy logikę, która zamyka się w kontolerze ViewController:

import UIKit
import MediaPlayer

class ViewController: UIViewController, MPMediaPickerControllerDelegate {

    @IBOutlet weak var toolbar: UIToolbar!
    @IBOutlet weak var song: UILabel!
   
    @IBOutlet var playButton: UIBarButtonItem!
    var pauseButton: UIBarButtonItem!
   
    var player: MPMusicPlayerController!
    var collection: MPMediaItemCollection!    
   

           override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
       
        self.pauseButton = UIBarButtonItem(barButtonSystemItem: .Pause, target: self,
        action: #selector(ViewController.playPausePressed(_:)))
        self.pauseButton.style = .Plain
       
        self.player = MPMusicPlayerController.systemMusicPlayer()
        self.player.repeatMode = .All
       
        let notificationCenter = NSNotificationCenter.defaultCenter()
        notificationCenter.addObserver(self, selector: #selector(ViewController.nowPlayingItemChanged(_:)),
        name: MPMusicPlayerControllerNowPlayingItemDidChangeNotification, object: self.player)
        notificationCenter.addObserver(self, selector: #selector(ViewController.playbackStateChanged(_:)),
        name: MPMusicPlayerControllerPlaybackStateDidChangeNotification, object: self.player)
        self.player.beginGeneratingPlaybackNotifications()
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
       
        NSNotificationCenter.defaultCenter().removeObserver(self, name: MPMusicPlayerControllerNowPlayingItemDidChangeNotification, object: self.player)
        NSNotificationCenter.defaultCenter().removeObserver(self, name: MPMusicPlayerControllerPlaybackStateDidChangeNotification, object: self.player)
    }
   
    @IBAction func search(sender: AnyObject) {
       
        let picker = MPMediaPickerController(mediaTypes: MPMediaType.Music)
        picker.delegate = self
        picker.allowsPickingMultipleItems = true
        picker.prompt = NSLocalizedString("Select items to play", comment: "Select items to play")
        self.presentViewController(picker, animated: true, completion: nil)
    }
   
    func mediaPickerDidCancel(mediaPicker: MPMediaPickerController) {
        self.dismissViewControllerAnimated(true, completion: nil)
    }
   
    func mediaPicker(mediaPicker: MPMediaPickerController, didPickMediaItems mediaItemCollection: MPMediaItemCollection) {
        self.dismissViewControllerAnimated(true, completion: nil)
       
        self.collection = mediaItemCollection
        self.player.setQueueWithItemCollection(self.collection)       
       
        var playbackState = self.player.playbackState as MPMusicPlaybackState
        if playbackState == .Playing {
            self.player.pause()
        }
       
        let item = self.collection.items[0] as MPMediaItem
        self.player.nowPlayingItem = item
       
        playbackState = self.player.playbackState as MPMusicPlaybackState
        self.player.play()
    }
   
    @IBAction func playPausePressed(sender: AnyObject) {
        let playbackState = self.player.playbackState as MPMusicPlaybackState
        if playbackState == .Stopped || playbackState == .Paused {
            self.player.play()
           
        } else if playbackState == .Playing {
            self.player.pause()
        }
    }
   
    func nowPlayingItemChanged(notification: NSNotification) {
        if let currentItem = self.player.nowPlayingItem as MPMediaItem? {
            self.song.text = currentItem.valueForProperty(MPMediaItemPropertyTitle) as? String
        } else {           
            self.song.text = nil
        }
    }
   
    func playbackStateChanged(notification: NSNotification) {
        let playbackState = self.player.playbackState as MPMusicPlaybackState
       
        self.toolbar.hidden = playbackState != .Playing && playbackState != .Paused
       
        var items = self.toolbar.items!
        if playbackState == .Stopped || playbackState == .Paused {
            items[0] = self.playButton
        } else if playbackState == .Playing {
            items[0] = self.pauseButton
        }
        self.toolbar.setItems(items, animated: false)
    }
}

Z tematem audio związane są różne możliwości, na razie zrobiliśmy pierwsze podejście z odtwarzaniem w oparciu o MediaPlayer Framework bez ukierunkowania na analizę dźwięku, raczej nie jest to ostatnie słowo.

Wyszukiwanie i pobieranie plików z Music Library na telefonie możemy zrealizować albo przy pomocy gotowego kontrolera MPMediaPickerController (zwróćmy uwagę na metodę search, a także mediaPicker - sukces i  mediaPickerDidCancel - anulowanie) albo poprzez napisanie zapytań i własne UI. Dzisiaj zastosowaliśmy pierwsze podejscie, ale następnym razem pomyślimy nad drugą opcją.

Odtwarzanie realizujemy przy pomocy MPMusicPlayerController.systemMusicPlayer (iPodMusicPlayer jest deprecated), który działa również w tle.  Nie trzeba nigdzie już samemu definiować pozwoleń z tym związanych. Za pomocą odpowiednich notyfikacji nasłuchujemy zmian stanu odtwarzacza, jak również aktualnie wybranego w nim utworu. Wybrane piosenki są odtwarzane w pętli nieskończoną ilość razy (repeatMode w odtwarzaczu). Edytor Xcode usłużnie podpowiada, co zrobiliśmy nie tak lub co jest deprecated. Dawniej np. referencje do callback-ów przekazywało się w postaci stringa jak w Objective-C, teraz należy posługiwać się konstrukcją #selector.

Brak komentarzy: