Label does not update using Swift - ios

I'm trying to improve a GitHub project I forked (https://github.com/giacmarangoni/Swift-Radio-Pro/tree/xcode8).
After some fixes and changes everything seems to work good but suddenly I noticed a really strange behavior.
When I open "NowPlayingViewController" for the first time and station starts to stream, everything is working and AVPlayer delegate updates user interface as expected (songLabel, titleLabel and albumArtwork).
After that, without stopping radio streaming, I tried to go back to "StationsViewController" and immediately to reopen "NowPlayingViewController" using "Now playing" button.
At this point delegation is still active, streaming is going on, but when song changes all variables in this view controller are updated but I can't say the same for the user interface. I tried to debug and I noticed that labels are populated but not updated. UI updates in the main thread and setNeedDisplay didn't help.
NowPlayingViewController
AVPlayer setup:
func setUpPlayer(){
radioPlayer = Player.radio
radioPlayer.rate = 1
NotificationCenter.default.addObserver(
self,
selector: #selector(self.playerItemDidReachEnd),
name: NSNotification.Name.AVPlayerItemDidPlayToEndTime,
object: self.radioPlayer.currentItem
)
}
Here you can find func onMetaData(_ metaData: [AVMetadataItem]?)).
//*****************************************************************
// MARK: - AVPlayerItem Delegate (for metadata)
//*****************************************************************
extension NowPlayingViewController: CustomAVPlayerItemDelegate {
func onMetaData(_ metaData: [AVMetadataItem]?) {
if let metaDatas = metaData{
startNowPlayingAnimation()
let firstMeta: AVMetadataItem = metaDatas.first!
let metaData = firstMeta.value as! String
var stringParts = [String]()
if metaData.range(of: " - ") != nil {
stringParts = metaData.components(separatedBy: " - ")
} else {
stringParts = metaData.components(separatedBy: "-")
}
// Set artist & songvariables
let currentSongName = track.title
track.artist = stringParts[0].decodeAllChars()
track.title = stringParts[0].decodeAllChars()
if stringParts.count > 1 {
track.title = stringParts[1].decodeAllChars()
}
if track.artist == "" && track.title == "" {
track.artist = currentStation.stationDesc
track.title = currentStation.stationName
}
DispatchQueue.main.async {
if currentSongName != self.track.title {
if kDebugLog {
print("METADATA artist: \(self.track.artist) | title: \(self.track.title)")
}
// Update Labels
self.artistLabel.text = self.track.artist
self.songLabel.text = self.track.title
self.updateUserActivityState(self.userActivity!)
// songLabel animation
self.songLabel.animation = "zoomIn"
self.songLabel.duration = 1.5
self.songLabel.damping = 1
self.songLabel.animate()
// Update Stations Screen
self.delegate?.songMetaDataDidUpdate(self.track)
// Query API for album art
self.resetAlbumArtwork()
self.queryAlbumArt()
}
}
}
}
}
This method is observed in "CustomAVPlayerItem" according to timedMetaData key path; It's fired every time AVPlayer metadatas change. This class is a subclass of AVPlayerItem:
import MediaPlayer
import Foundation
protocol CustomAVPlayerItemDelegate {
func onMetaData(_ metaData:[AVMetadataItem]?)
}
//*****************************************************************
// Makes sure that observers are removed before deallocation
//*****************************************************************
class CustomAVPlayerItem: AVPlayerItem {
var delegate : CustomAVPlayerItemDelegate?
init(url URL:URL)
{
if kDebugLog {print("CustomAVPlayerItem.init")}
super.init(asset: AVAsset(url: URL) , automaticallyLoadedAssetKeys:[])
addObserver(self, forKeyPath: "timedMetadata", options: NSKeyValueObservingOptions.new, context: nil)
}
deinit{
if kDebugLog {print("CustomAVPlayerItem.deinit")}
removeObserver(self, forKeyPath: "timedMetadata")
}
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if let avpItem: AVPlayerItem = object as? AVPlayerItem {
if keyPath == "timedMetadata" {
delegate?.onMetaData(avpItem.timedMetadata)
}
}
}
}
The following is my AVPlayer:
import MediaPlayer
//*****************************************************************
// This is a singleton struct using Swift
//*****************************************************************
struct Player {
static var radio = AVPlayer()
}
This is the segue function I use to open to "NowPlayingViewController". StationsViewController
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "NowPlaying" {
self.title = ""
firstTime = false
let nowPlayingVC = segue.destination as! NowPlayingViewController
nowPlayingVC.delegate = self
if let indexPath = (sender as? IndexPath) {
// User clicked on row, load/reset station
if searchController.isActive {
currentStation = searchedStations[indexPath.row]
} else {
currentStation = stations[indexPath.row]
}
nowPlayingVC.currentStation = currentStation
nowPlayingVC.newStation = true
} else {
// User clicked on a now playing button
if let currentTrack = currentTrack {
// Return to NowPlaying controller without reloading station
nowPlayingVC.track = currentTrack
nowPlayingVC.currentStation = currentStation
nowPlayingVC.newStation = false
} else {
// Issue with track, reload station
nowPlayingVC.currentStation = currentStation
nowPlayingVC.newStation = true
}
}
}
}

Here's what I think you're not understanding and what's actually going on.
Normally, when you "go back" from a pushed view controller, the pushed view controller is popped and destroyed. Your pushed view controller is a NowPlayingViewController. It should be destroyed when you "go back" from it to the StationsViewController. Thus, when you show the NowPlayingViewController again, you would have to create a new, different NowPlayingViewController.
Okay, so far so good, provided you understand all of that. But in your case there is a further complication: you have a leak! Your old NowPlayingViewController is not being destroyed. Thus, when you "go back" to the StationsViewController and show the NowPlayingViewController for a second time, there are now two NowPlayingViewControllers — the new one that you see, and the old one that is leaking.
Okay, so your logging continues to show the old NowPlayingViewController, which is still observing and updating. But your eyes are seeing the new NowPlayingViewController, which is doing nothing. And that explains the phenomena you have described.
If this is right — and, from what you've said, I'm pretty sure it is — then you need to reorganize your architecture either so that you don't get this leak or so that when you show the NowPlayingViewController the second time you show the same NowPlayingViewController rather than creating a different one. (The first approach would be better.)

Related

How to listen for change in variable inside a function

I have a class called Observers to observe Firebase Storage Upload Tasks, but before observing the progress, it waits for PHPickerviewcontroller to upload the video. I have an instance variable in my class, hasUploaded so that I can know when I can start to change the progress bar, however, with the way it's set up, the block of code in the if statement will never be called. I know there is didSet but that doesn't help me in this case, because I need to listen for change inside the function. How do I do that?
func observeProgress(progressWheel: UIActivityIndicatorView, errorLabel: UILabel, progressView: UIProgressView, progressLabel: UILabel)
{
progressLabel.text = "Downloading from Device...Please Wait (1/2)"
progressWheel.startAnimating()
progressWheel.alpha = 1
inProgress = true
//RIGHT HERE - Wait for hasUploaded to == true
if hasUploaded
{
progressWheel.alpha = 0
self.taskReference!.observe(.progress)
{ (snapshot) in
guard let progress = snapshot.progress?.fractionCompleted else { /**alert**/ return }
progressView.progress = Float(progress)
progressLabel.text = "\(round(100 * Float(progress)))% (2/2)"
if progress == 1
{
progressLabel.text = "Upload Successful!"
progressLabel.textColor = .black
progressView.progress = 0
}
}
}
}
I thought about it again and maybe it is easier for you to use the NotificationCenter.
In the Model or ViewController add
let nc = NotificationCenter.default
and thenadjust the hasUploaded variable to
var hasUploaded = false {
didSet {
let statusChange = ["userInfo": ["hasUploaded": hasUploaded]]
NotificationCenter.default
.post(name:
NSNotification.Name("com.user.hasUploaded"),
object: nil,
userInfo: statusChange)
}
}
In the controller with the function observeProgress, also add
let nc = NotificationCenter.default
Add the following to the viewDidLoad() function
NotificationCenter.default
.addObserver(self, selector:#selector(hasUploadedNotificationReceived(_:)),
name: NSNotification.Name ("com.user.hasUploaded"),
object: nil)
}
Finally create the function hasUploadedNotificationReceived() (which is called above whenever the notification will be received) to add the magic that should happen after the the change. For example:
#objc func hasUploadedNotificationReceived(_ notification: Notification){
let notification = notification.userInfo?["userInfo"] as? [String: Bool] ?? [:]
if (notification["hasUploaded"] as? Bool)! {
observeProgress(...) {
[...]
}
}
}
Please read also the documentation to figure out what options you have and what you can add or modify.
Beside this implementation, I also can imagine that the a Delegate as well as Combine and as #matt mentioned async/await could help to achieve your desired behavior.

How can I create an Instance of NSManagedObject in a NotesApp without a Button - Apple's NoteApp Style?

I started learning programming and I decided to try out my first Note Taking App.
My Goal is to create an App similar to the iPhone's NoteApp. Therefore, I wanted the note's title be set when the User writes in the TextView as the first line. Therefore, I created a NoteViewController, which contains a TextView and a NoteIndexViewController, which is a TableViewController, both embedded in a NavigationController.
I'm also using Core Data to store the data.
The problem is that I don't know how I can commit those changes to the DataBase without using a button. I know how to create an instance of the NSManagedObject - in NoteIndexViewController to create new notes in the TableView using a Button:
#IBAction func addNotePressed(_ sender: UIBarButtonItem) {
let newNoteIndex = NoteIndex(context: self.context)
newNoteIndex.name = "Temporal Name"
notesArray.append(newNoteIndex)
saveNoteIndex()
performSegue(withIdentifier: K.segueToNote, sender: self)
}
But I'm completely lost if I want to commit the changes without a "Save Button" to create the instance and also committing changes. This is the code I got so far. Notice that I did not set any Note() object.
class NoteViewController: UIViewController {
var noteArray = [Note]()
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
var selectedNote: NoteIndex? {
didSet {
loadData()
}
}
var firstLine: String?
#IBOutlet weak var textView: UITextView!
override func viewDidLoad() {
super.viewDidLoad()
loadData()
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(true)
if !textView.text.isEmpty {
if let newLine = textView.text.firstIndex(of: "\n") {
let firstLetter = textView.text.startIndex
let lineBrake = textView.text.index(before: newLine)
let lettersTillPosition = textView.text.distance(from: firstLetter, to: lineBrake)
firstLine = (textView.text as NSString).substring(to: lettersTillPosition)
} else {
if textView.text.count >= 30{
firstLine = (textView.text as NSString).substring(to: 30)
} else {
firstLine = (textView.text as NSString).substring(to: textView.text.count)
}
}
selectedNote!.name = firstLine
saveCurrentNote()
}
}
//MARK: - Data Manipulation Methods
func saveCurrentNote() {
do {
try context.save()
} catch {
print("Error saving cateogry \(error)")
}
}
func loadData(with request: NSFetchRequest<Note> = Note.fetchRequest()) {
// goToIndex is the relationship between the IndexNote entity and Note. And when Back button is pressed the code tend also to break in this part.
request.predicate = NSPredicate(format: "goToIndex.name MATCHES %#", selectedNote!.name!)
do {
noteArray = try context.fetch(request)
} catch {
print("This is a load error: \(error)")
}
}
}
extension NoteViewController: UITextViewDelegate {
func textViewDidChange(_ textView: UITextView) {
saveCurrentNote()
}
}
Here is a possible solution for your question. You can use Notification Center to monitor if the user is interrupted and if so you can do a quick save.
Place these in the scene delegate
func sceneWillResignActive(_ scene: UIScene) {
let notificationName = NSNotification.Name(ReuseIdentifier.pause)
NotificationCenter.default.post(name: notificationName , object: nil)
}
func sceneDidDisconnect(_ scene: UIScene) {
let notificationName = NSNotification.Name(ReuseIdentifier.quit)
NotificationCenter.default.post(name: notificationName, object: nil)
}
Place something like this where the user data is being saved.
/// Monitors application state for major changes.
/// - Pause Observer: Adds observer that notifies application if application is no longer active (enters foreground).
/// - Quit Observer: Adds observer that notifies application if terminated.
private func checkForPauseOrQuit(){
NotificationCenter.default.addObserver(self,
selector: #selector(autoSave),
name: NSNotification.Name(ReuseIdentifier.pause),
object: nil)
NotificationCenter.default.addObserver(self,
selector: #selector(autoSave),
name: NSNotification.Name(ReuseIdentifier.quit),
object: nil)
}
And then for your selector method you create your NSManagedObject and capture whatever values the user may have started typing.
On startup you do the reverse, and make sure to erase the values. This should function only as a temporary holding container not your main entity. Check out my note application for reference:
https://github.com/victis23/AwesomeNote/tree/WorkingBranch/AwesomeNote

Integrating Facebook and Twitter in Xcode Scenes Swift iOS

I have developed a game in Xcode using sprite kit, and scenes. Now I am trying to integrate the functionality to post high scores to twitter and Facebook. I've looked around, and most people say to use SLComposeServiceViewController which is fine, until I try and present it. Because my app really only uses scenes, they never have the member function "presentViewController(....)". Thus, I am unable to ever present it. Anyone know any way around this?
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch:UITouch = touches.first!
let touchLocation = touch.location(in: self)
let touchedNode = self.atPoint(touchLocation)
if (touchedNode.name == "tryAgain") {
let nextScene = Scene_LiveGame(size: self.scene!.size)
nextScene.scaleMode = self.scaleMode
self.view?.presentScene(nextScene, transition: SKTransition.fade(withDuration: 0.5))
}
else if (touchedNode.name == "share") {
if SLComposeViewController.isAvailable(forServiceType: SLServiceTypeFacebook) {
let fShare = SLComposeViewController(forServiceType: SLServiceTypeFacebook)
self.presentViewController(fShare!, animated: true, completion: nil)
//^This is where my problem is. Xcode is telling me that self has no member function presentViewController which I totally understand, because its a scene and thus doesn't share those functions. But every resource online has shown me this is the only way to do it
}
}
Your are getting this error because you need to present a UIViewController from another UIViewController. So
self.presentViewController(...)
will not work because self (SKScene) is not a UIViewController. To present from a SKScene you would have to say this
view?.window?.rootViewController?.presentViewController(fShare!, animated: true, completion: nil)
I would recommend that you do not use those APIs anymore. Its better to use a UIActivityViewController for your sharing needs. This way you only need one share button in your app and you can share to all sorts of services (email, Twitter, Facebook, iMessage, WhatsApp etc).
Create a new Swift file and add this code.
enum ShareMenu {
static func open(text: String, image: UIImage?, appStoreURL: String?, from viewController: UIViewController?) {
guard let viewController = viewController, let view = viewController.view else { return }
// Activity items
var activityItems = [Any]()
// Text
activityItems.append(text)
// Image
if let image = image {
activityItems.append(image)
}
/// App url
if let appStoreURL = appStoreURL {
let items = ActivityControllerItems(appStoreURL: appStoreURL)
activityItems.append(items)
}
// Activity controller
let activityController = UIActivityViewController(activityItems: activityItems, applicationActivities: nil)
// iPad settings
if UIDevice.current.userInterfaceIdiom == .pad {
activityController.modalPresentationStyle = .popover
activityController.popoverPresentationController?.sourceView = view
activityController.popoverPresentationController?.sourceRect = CGRect(x: view.bounds.midX, y: view.bounds.midY, width: 0, height: 0)
activityController.popoverPresentationController?.permittedArrowDirections = UIPopoverArrowDirection.init(rawValue: 0)
}
// Excluded activity types
activityController.excludedActivityTypes = [
.airDrop,
.print,
.assignToContact,
.addToReadingList,
]
// Present
DispatchQueue.main.async {
viewController.present(activityController, animated: true)
}
// Completion handler
activityController.completionWithItemsHandler = { (activity, success, items, error) in
guard success else {
if let error = error {
print(error.localizedDescription)
}
return
}
// do something if needed
}
}
}
// MARK: - Activity Controller Items
/**
ActivityControllerItems
*/
private final class ActivityControllerItems: NSObject {
// MARK: - Properties
/// App name
fileprivate let appName = Bundle.main.infoDictionary?["CFBundleName"] as? String ?? "-"
/// App store web url
fileprivate let appStoreURL: String
// MARK: - Init
/// Init
fileprivate init(appStoreURL: String) {
self.appStoreURL = appStoreURL
super.init()
}
}
// MARK: - UIActivityItemSource
/// UIActivityItemSource
extension ActivityControllerItems: UIActivityItemSource {
/// Getting data items
/// Placeholder item
func activityViewControllerPlaceholderItem(_ activityViewController: UIActivityViewController) -> Any {
return ""
}
/// Item for actity type
func activityViewController(_ activityViewController: UIActivityViewController, itemForActivityType activityType: UIActivityType) -> Any? {
return URL(string: appStoreURL) ?? appName
}
/// Provide info about data items
/// Subject field for services such as email
func activityViewController(_ activityViewController: UIActivityViewController, subjectForActivityType activityType: UIActivityType?) -> String {
return appName
}
}
Than when the share button is pressed you can call it like so
ShareMenu.open(
text: "Can you beat my score?",
image: UIImage(...), // set to nil if unused
appStoreURL: "your iTunes app store URL", // set to nil if unused
from: view?.window?.rootViewController
)
Bear in mind that the image and appStoreURL will not show up everywhere, it depends on the sharing service.
You can also use your score value from your scene and add it to the text e.g
ShareMenu.open(
text: "Can you beat my score \(self.score)?",
...
)
Hope this helps
I will not go into SLComposeViewController related code. I will just show you two techniques aside from what crashoverride777 proposed. So the first technique would be using notifications, like this:
GameScene:
import SpriteKit
let kNotificationName = "myNotificationName"
class GameScene: SKScene {
private func postNotification(named name:String){
NotificationCenter.default.post(
Notification(name: Notification.Name(rawValue: name),
object: self,
userInfo: ["key":"value"]))
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
self.postNotification(named: kNotificationName)
}
}
Here, you post a notification by tapping the screen. A desired view controller class can listen for this notification, like this:
import UIKit
import SpriteKit
class GameViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(
self,
selector: #selector(self.handle(notification:)),
name: NSNotification.Name(rawValue: kNotificationName),
object: nil)
if let view = self.view as! SKView? {
// Load the SKScene from 'GameScene.sks'
if let scene = GameScene(fileNamed: "GameScene") {
// Set the scale mode to scale to fit the window
scene.scaleMode = .aspectFill
// Present the scene
view.presentScene(scene)
}
}
}
func handle(notification:Notification){
print("Notification : \(notification)")
}
}
Here, we add self as an observer for this notification - means that when notification happens, an appropriate handling method will be called (and that is our custom handle(notification:) method. In that method, you should call your code:
if SLComposeViewController.isAvailable(forServiceType: SLServiceTypeFacebook) {
let fShare = SLComposeViewController(forServiceType: SLServiceTypeFacebook)
self.presentViewController(fShare!, animated: true, completion: nil)
}
Actually, I will write another example for delegation, to keep things clean :)
As I said, this can be done using notifications, like in this answer, or you can go with delegation:
First you should declare the MyDelegate protocol which defines one method called myMethod().
protocol MyDelegate:class {
func myMethod()
}
That method is a requirement that every class must implement if it conforms to this protocl.
In our example, you can look at the scene as a worker, and a view controller as a boss. When the scene finishes its task, it notifies its boss (delegates responsibilities to him) about job completion, so that boss can decide what is next. I mean, I could say : "The scene is a boss, and it delegates responsibilities to his employee, the view controller..." But it doesn't really matter who you consider as a boss... The delegation pattern matters.
So, the view controller, should conform to this protocol, and it will implement the myMethod() (that will be called by the scene later):
class GameViewController: UIViewController, MyDelegate {
override func viewDidLoad() {
super.viewDidLoad()
//MARK: Conforming to MyDelegate protocol
if let view = self.view as! SKView? {
// Load the SKScene from 'GameScene.sks'
if let scene = GameScene(fileNamed: "GameScene") {
// Set the scale mode to scale to fit the window
scene.scaleMode = .aspectFill
scene.myDelegate = self
// Present the scene
view.presentScene(scene)
}
}
}
func myMethod(){
print("Do your stuff here")
}
}
And here is a code from the GameScene where you define the myDelegate property that we use to communicate with our view controller:
import SpriteKit
class GameScene: SKScene {
weak var myDelegate:MyDelegate?
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
self.myDelegate?.myMethod()
}
}
To find out when to choose delegation over notifications and vice-versa take a look at this article (or just search SO, there are some nice posts about that).

Stream 2 videos simultaneously Swift

I'm trying to stream two videos at the same time with swift on iphone. I already know that the AV Player can only stream one video at a time, but googling it I saw that it's still possible to stream different tracks at the same time. I also saw the picture in picture implementation. The real problem is that is all in objective-c and the code is quite old. I tried to understand it running the code as it is, but there are errors and some of the functions are deprecated.
Does someone know how to do that in swift? Also, I'm streaming video from the internet so merging them before playing is not an option.
Thank you!
Swift Version
The article you referenced is an interesting method of handling multiple video playback in iOS. The article appears to be related to Apple's Stitched Stream Player Sample Project. As an exercise in Swift 2.2, I've rewritten the code on the iOS Guy article.
You can find both the view and the view controller in Swift on my gist. I'm copying below as it is best practices to not use link only answers on SO.
Custom View Player
This custom view player allows one to swap out multiple AVPlayerItem(s).
import UIKit
import AVFoundation
class MNGVideoPlayerView: UIView {
/*
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
override func drawRect(rect: CGRect) {
// Drawing code
}
*/
override class func layerClass () -> AnyClass {
return AVPlayerLayer.self
}
func player () -> AVPlayer {
return (self.layer as! AVPlayerLayer).player!
}
func setPlayer(player:AVPlayer) {
(self.layer as! AVPlayerLayer).player = player
}
func setVideoFillMode(fillMode:String) {
let playerLayer = (self.layer as! AVPlayerLayer)
playerLayer.videoGravity = fillMode
}
}
Multiple Video Player View Controller
This controller manages the distribution and presentation of the different AVPlayerItem(s). Check my gist repos for additional updates. (ported from original objc source #iOS Guy)
import UIKit
import AVFoundation
class MNGVideoPlayerViewController: UIViewController {
let kTracksKey = "tracks";
let kStatusKey = "status";
let kRateKey = "rate";
let kPlayableKey = "playable";
let kCurrentItemKey = "currentItem";
let kTimedMetadataKey = "currentItem.timedMetadata";
var _URL:NSURL? = nil
var player:AVPlayer? = nil
var playerItem:AVPlayerItem? = nil
var playerView:MNGVideoPlayerView? = nil
var AVPlayerDemoPlaybackViewControllerStatusObservationContext = UnsafeMutablePointer<Void>()
var AVPlayerDemoPlaybackViewControllerCurrentItemObservationContext = UnsafeMutablePointer<Void>()
var AVPlayerDemoPlaybackViewControllerStatusObservation = UnsafeMutablePointer<Void>()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: - Public methods
func setURL(url:NSURL) {
self._URL = url
let asset = AVURLAsset(URL: self._URL!)
let requestedKeys = [kTracksKey,kPlayableKey]
asset.loadValuesAsynchronouslyForKeys(requestedKeys) { () -> Void in
self.prepareToPlayAsset(asset, withKeys: requestedKeys)
}
}
func prepareToPlayAsset(asset:AVURLAsset, withKeys requestedKeys:NSArray) {
var error:NSErrorPointer = nil
for thisKey in requestedKeys {
let keyStatus = asset.statusOfValueForKey(thisKey as! String, error: error)
if keyStatus == .Failed {
return
}
}
if !asset.playable {
return
}
if (self.playerItem != nil) {
self.playerItem?.removeObserver(self, forKeyPath: kStatusKey)
NSNotificationCenter.defaultCenter().removeObserver(self, name: AVPlayerItemDidPlayToEndTimeNotification, object: self.playerItem)
}
self.playerItem = AVPlayerItem(asset: asset)
self.playerItem?.addObserver(self, forKeyPath: kStatusKey, options: [.Initial,.New], context: AVPlayerDemoPlaybackViewControllerStatusObservationContext)
if (self.player == nil) {
self.player = AVPlayer(playerItem: self.playerItem!)
self.player?.addObserver(self, forKeyPath: kCurrentItemKey, options: [.Initial,.New], context: AVPlayerDemoPlaybackViewControllerCurrentItemObservationContext)
}
if self.player?.currentItem! != self.playerItem! {
self.player?.replaceCurrentItemWithPlayerItem(self.playerItem!)
}
}
// MARK: - Key Value Observing
override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {
if context == AVPlayerDemoPlaybackViewControllerStatusObservation {
let status:Int = (change![NSKeyValueChangeNewKey]?.integerValue)!
if status == AVPlayerStatus.ReadyToPlay.rawValue {
self.player?.play()
}
} else if context == AVPlayerDemoPlaybackViewControllerCurrentItemObservationContext {
let newPlayerItem = change![NSKeyValueChangeNewKey] as? AVPlayerItem
if newPlayerItem != nil {
self.playerView?.setPlayer(self.player!)
self.playerView?.setVideoFillMode(AVLayerVideoGravityResizeAspect)
}
} else {
super.observeValueForKeyPath(keyPath, ofObject: object, change: change, context: context)
}
}
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
// Get the new view controller using segue.destinationViewController.
// Pass the selected object to the new view controller.
}
*/
}
It looks like http://iosguy.com/2012/01/11/multiple-video-playback-on-ios/ is the best solution at the moment. Nobody tried to convert it in swift yet.
I play multiple videos in my app at once, I just use multiple instances of AVPlayer.

Swift and Socket.io: Event doesn't run anymore when re-open a controller

I'm beginner Swift and Socket.io. The socket.io library for Swift that I'm using is https://github.com/nuclearace/Socket.IO-Client-Swift
For using 1 socket.io connection in a multiple controllers, I use Singleton with below code in Singleton_SocketManager.swift:
import Foundation
class Singleton_SocketManager {
static let sharedInstance = Singleton_SocketManager()
let socket = SocketIOClient(socketURL: "localhost:3000")
var currentController = ""
var loadedHander = [String: Bool]()
var currentRoomId = 0
init() {
loadedHander["ViewController"] = false
loadedHander["NextViewController"] = false
}
}
let sharedSocket = Singleton_SocketManager()
To add event handler in each controller, I call addHandlers() function in viewDidLoad() and the code of addHandlers() like that:
For ViewController:
func addHandlers() {
if (sharedSocket.loadedHander["ViewController"]!) {
return;
} else {
sharedSocket.loadedHander["ViewController"] = true
}
sharedSocket.socket.on("eventInViewController1") {[weak self] data, ack in
println("View Controller: Event 1")
}
}
For NextViewController:
func addHandlers() {
if (sharedSocket.loadedHander["NextViewController"]!) {
return;
} else {
sharedSocket.loadedHander["NextViewController"] = true
}
sharedSocket.socket.on("eventInNextViewController1") {[weak self] data, ack in
println("NextViewController: Event 1")
}
}
They run fine in first time. But if I have to change controller with this code:
let storyBoard = self.storyboard
let nextViewController = storyBoard.instantiateViewControllerWithIdentifier("NextViewController") as! NextViewController
self.presentViewController(nextViewController, animated: false, completion: nil)
and go back to ViewController with the same code, the old event handler will not run. I know I can run it by remove the code of checking and marking "sharedSocket.loadedHander" but if I do like that, all events will run twice, and triple if I go NextViewController and back to ViewController again.
I tried to research a lot about this issue but can't find any solutions for this. If you work with Swift or Objective-C and Socket.io library before and resolved this issue, please help me.
Thank you very much for your reading!
Try to disconnect from the first ViewController before performing the segue to the next ViewController. Then, reconnect in the second ViewController.
In addition, you might want declare the socket constant globally.

Resources