An observer was overreleased or smashed - ios

I got a crash on first time launch the app
KVO_IS_RETAINING_ALL_OBSERVERS_OF_THIS_OBJECT_IF_IT_CRASHES_AN_OBSERVER_WAS_OVERRELEASED_OR_SMASHED
But the second time when I open the app it is not crash
Firebase Crashlytics Log:
Crashed: com.apple.main-thread
0 libobjc.A.dylib 0x1fa2d7dac object_isClass + 16
1 Foundation 0x1fbb7fbd8 KVO_IS_RETAINING_ALL_OBSERVERS_OF_THIS_OBJECT_IF_IT_CRASHES_AN_OBSERVER_WAS_OVERRELEASED_OR_SMASHED + 68
2 Foundation 0x1fbb7d36c NSKeyValueWillChangeWithPerThreadPendingNotifications.llvm.6024598272766318604 + 304
3 AVFoundation 0x201146074 __avplayeritem_fpItemNotificationCallback_block_invoke + 5800
4 libdispatch.dylib 0x1fab436c8 _dispatch_call_block_and_release + 24
5 libdispatch.dylib 0x1fab44484 _dispatch_client_callout + 16
6 libdispatch.dylib 0x1faaf09ec _dispatch_main_queue_callback_4CF$VARIANT$mp + 1068
7 CoreFoundation 0x1fb09a1bc __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__ + 12
8 CoreFoundation 0x1fb095084 __CFRunLoopRun + 1964
9 CoreFoundation 0x1fb0945b8 CFRunLoopRunSpecific + 436
10 GraphicsServices 0x1fd308584 GSEventRunModal + 100
11 UIKitCore 0x227adb558 UIApplicationMain + 212
12 Boxit4me 0x102a59ca0 main (FAQVC.swift:29)
13 libdyld.dylib 0x1fab54b94 start + 4
How could I solve this crash?
Updates:
I think it is related to my SignupViewController:
import UIKit
import AVFoundation
import AVKit
import NVActivityIndicatorView
import Localize_Swift
import IQKeyboardManagerSwift
class BaseSignUpViewController: BaseViewController {
#IBOutlet weak var mainView: UIView!
#IBOutlet weak var alreadySignInLabel: UILabel!
#IBOutlet weak var businessAccountBtn: UIButton!
#IBOutlet weak var personalAccountBtn: UIButton!
#IBOutlet weak var signupAsLabel: UILabel!
#IBOutlet weak var videoView: UIView!
#IBOutlet weak var vwVideoContainer : UIView!
#IBOutlet weak var vwSignUpView : UIView!
#IBOutlet weak var btnSignUp : UIButton!
#IBOutlet weak var lblHowItWorks : UILabel!
#IBOutlet weak var btnFullScreenVideo: UIButton!
var player: AVPlayer?
var activityIndicator : NVActivityIndicatorView!
#IBOutlet var rapidLabel: IGUILabel!
#IBOutlet weak var thumbnailImage: UIImageView!
#IBOutlet weak var playVideoButton: UIButton!
class func initializeViewController() -> UINavigationController {
return (StoryBoard.SignUpFlow.storyboard().instantiateInitialViewController() as? UINavigationController)!
}
class func initializeViewBaseController() -> BaseSignUpViewController {
return StoryBoard.SignUpFlow.storyboard().instantiateViewController(withIdentifier: SegueIdentifiers.SignUpFlow.BaseSignUpViewController) as! BaseSignUpViewController
}
override func viewDidLoad() {
super.viewDidLoad()
IQKeyboardManager.shared.shouldResignOnTouchOutside = true
}
deinit {
NotificationCenter.default.removeObserver(self)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
setUIText()
AppUtility.setUpNavigationBar()
self.navigationController?.setNavigationBarHidden(true, animated: true)
// self.navigationController?.isNavigationBarHidden = true
if (UserDefaults.standard.object(forKey: "LCLCurrentLanguageKey") as? String) == nil {
self.performSegue(withIdentifier: SegueIdentifiers.SignUpFlow.LanguageSelection, sender: nil)
}
self.btnFullScreenVideo.isHidden = true
self.thumbnailImage.isHidden = false
self.playVideoButton.isHidden = false
}
override func viewDidAppear(_ animated: Bool) {
self.showVideoView()
NotificationCenter.default.addObserver(self, selector: #selector(playerItemDidReachEnd), name: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: self.player?.currentItem)
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidAppear(animated)
}
override func setUIText() {
if(Localize.currentLanguage() == "ar"){
UIView.appearance().semanticContentAttribute = .forceRightToLeft
UINavigationBar.appearance().semanticContentAttribute = .forceRightToLeft
self.btnSignUp.semanticContentAttribute = .forceRightToLeft
self.btnSignUp.titleEdgeInsets = UIEdgeInsetsMake(0, -20, 0, 0)
self.thumbnailImage.image = getThumbnailImage(forUrl: URL(string: Constants.websiteBaseURL + "Content/Theme/videos/how_it_works_ar.mp4")!)
self.player = AVPlayer(url: URL(string: Constants.websiteBaseURL + "Content/Theme/videos/how_it_works_ar.mp4")!)
}else{
UIView.appearance().semanticContentAttribute = .forceLeftToRight
UINavigationBar.appearance().semanticContentAttribute = .forceLeftToRight
self.btnSignUp.semanticContentAttribute = .forceLeftToRight
self.btnSignUp.titleEdgeInsets = UIEdgeInsetsMake(0, 20, 0, 0)
self.thumbnailImage.image = getThumbnailImage(forUrl: URL(string: Constants.websiteBaseURL + "Content/Theme/videos/how_it_works.mp4")!)
self.player = AVPlayer(url: URL(string: Constants.websiteBaseURL + "Content/Theme/videos/how_it_works.mp4")!)
}
self.showVideoView()
self.btnSignUp.titleLabel?.font = Font.Regular.fontWithSize(21)
self.btnSignUp.setTitle("Signup As".localized(), for: .normal)
self.businessAccountBtn.setTitle("Premium Account".localized(), for: .normal)
self.personalAccountBtn.setTitle("Personal Account (Free)".localized(), for: .normal)
self.businessAccountBtn.titleLabel?.font = Font.Regular.fontWithSize(13) //UIFont().textFieldText()
self.personalAccountBtn.titleLabel?.font = Font.Regular.fontWithSize(13)
self.lblHowItWorks.text = "How it works".localized()
self.lblHowItWorks.font = Font.Regular.fontWithSize(20)
setupSignInLabel()
setupGestures()
setupMainView()
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let objLanguagePreferenceVC = segue.destination as? LanguagePreferenceVC {
objLanguagePreferenceVC.languageChanged = {
self.setUIText()
}
}
if let objSignUp = segue.destination as? SignUpViewController {
if (sender as! UIButton) == self.personalAccountBtn {
objSignUp.accountType = AccountType.Personal
}
else{
objSignUp.accountType = AccountType.Premium
}
}
}
#IBAction func btnSignUpClicked(_ sender : UIButton){
self.performSegue(withIdentifier: SegueIdentifiers.SignUpFlow.SignUpSegue, sender: sender)
}
#IBAction func videoViewTapped(_ sender : UITapGestureRecognizer){
if self.player?.timeControlStatus == .playing {
self.player?.pause()
self.playVideoButton.isHidden = false
self.btnFullScreenVideo.isHidden = true
}
else{
self.player?.play()
self.btnFullScreenVideo.isHidden = false
self.playVideoButton.isHidden = true
}
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(true)
self.player?.pause()
let playerItem = self.player?.currentItem
do{try self.removeObserver(self, forKeyPath: "actionAtItemEnd")}catch{}
do{try playerItem?.removeObserver(self, forKeyPath: "playbackBufferEmpty")}catch{}
do{try playerItem?.removeObserver(self, forKeyPath: "playbackLikelyToKeepUp")}catch{}
do{try playerItem?.removeObserver(self, forKeyPath: "playbackBufferFull") }catch{}
self.player = nil
}
func setupSignInLabel(){
let italicAttribute = [NSAttributedStringKey.font:Font.Regular.fontWithSize(13), NSAttributedStringKey.foregroundColor: Color.blueColor.value]
let nextDropString = NSAttributedString(string: "Already have an account?".localized(), attributes: italicAttribute)
let colorFontAttributes = [NSAttributedStringKey.font:Font.Regular.fontWithSize(13) , NSAttributedStringKey.foregroundColor: Color.theme.value]
let timerString = NSAttributedString(string: " " + "Login".localized(), attributes: colorFontAttributes)
let labelString = NSMutableAttributedString(attributedString: nextDropString)
labelString.append(timerString)
self.alreadySignInLabel.attributedText = labelString
}
func setupGestures() {
self.alreadySignInLabel.isUserInteractionEnabled = true
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(loadSignIn(_:)))
self.alreadySignInLabel.addGestureRecognizer(tapGesture)
}
func setupMainView(){
self.mainView.layer.masksToBounds = false;
self.mainView.layer.shadowOffset = CGSize.init(width: 0, height: 0)
self.mainView.layer.shadowOpacity = 0.4;
self.mainView.layer.shadowColor = UIColor.lightGray.cgColor
self.vwSignUpView.layer.masksToBounds = false;
self.vwSignUpView.layer.shadowOffset = CGSize.init(width: 0, height: 0)
self.vwSignUpView.layer.shadowOpacity = 0.4;
self.vwSignUpView.layer.shadowColor = UIColor.lightGray.cgColor
self.vwVideoContainer.layer.borderColor = UIColor.gray.withAlphaComponent(0.8).cgColor
self.vwVideoContainer.layer.borderWidth = 0.5
self.vwVideoContainer.layer.masksToBounds = false;
self.vwVideoContainer.layer.shadowOffset = CGSize.init(width: 0, height: 0)
self.vwVideoContainer.layer.shadowOpacity = 0.1;
self.vwVideoContainer.layer.shadowColor = UIColor.lightGray.cgColor
}
func showVideoView(){
addObserver(self, forKeyPath: "actionAtItemEnd", options: [], context: nil)
let playerItem = self.player?.currentItem
playerItem?.addObserver(self, forKeyPath: "playbackBufferEmpty", options: .new, context: nil)
playerItem?.addObserver(self, forKeyPath: "playbackLikelyToKeepUp", options: .new, context: nil)
playerItem?.addObserver(self, forKeyPath: "playbackBufferFull", options: .new, context: nil)
// create a video layer for the player
let layer: AVPlayerLayer = AVPlayerLayer(player: player)
layer.backgroundColor = UIColor.clear.cgColor //Color.custom(hexString: "#F9F9F9", alpha: 1.0).value.cgColor
// make the layer the same size as the container view
layer.frame = videoView.bounds
let xAxis = self.videoView.center.x
let yAxis = self.videoView.center.y
let frame = CGRect(x: (xAxis - 23), y: (yAxis - 23), width: 45, height: 45)
activityIndicator = NVActivityIndicatorView(frame: frame)
activityIndicator.type = .ballPulse // add your type
activityIndicator.color = Color.theme.value// add your color
// make the video fill the layer as much as possible while keeping its aspect size
layer.videoGravity = AVLayerVideoGravity.resizeAspectFill
// add the layer to the container view
videoView.layer.addSublayer(layer)
self.videoView.addSubview(activityIndicator)
}
#objc func loadSignIn(_ sender : Any){
//self.goBack()
let businessSignupVC = SignInViewController(nibName: "SignInViewController", bundle: nil)
self.navigationController?.pushViewController(businessSignupVC, animated: true)
}
#IBAction func businessAccountBtnPressed(_ sender: Any) {
let businessSignupVC = BussninessSignupViewController(nibName: "BussninessSignupViewController", bundle: nil)
self.navigationController?.pushViewController(businessSignupVC, animated: true)
}
#IBAction func personalAccountBtnPressed(_ sender: Any) {
let personalSignupVC = PersonalSignupViewController(nibName: "PersonalSignupViewController", bundle: nil)
self.navigationController?.pushViewController(personalSignupVC, animated: true)
}
func getThumbnailImage(forUrl url: URL) -> UIImage? {
let asset: AVAsset = AVAsset(url: url)
let imageGenerator = AVAssetImageGenerator(asset: asset)
do {
let thumbnailImage = try imageGenerator.copyCGImage(at: CMTimeMake(32, 60) , actualTime: nil)
self.thumbnailImage.image = UIImage(cgImage: thumbnailImage)
return UIImage(cgImage: thumbnailImage)
} catch let error {
print(error)
}
return nil
}
#IBAction func btnFullScreenClicked(_ sender: UIButton) {
playVideo(needFullScreen:true)
}
#IBAction func playAction(_ sender: UIButton) {
sender.isHidden = true
playVideo()
// startLoadingView()
}
func playVideo(needFullScreen:Bool=false){
self.thumbnailImage.isHidden = true
self.playVideoButton.isHidden = true
self.btnFullScreenVideo.isHidden = false
if needFullScreen == false{
player?.play()
}else{
let playerViewController = AVPlayerViewController()
playerViewController.player = player
self.present(playerViewController, animated: true) {
playerViewController.player!.play()
}
}
}
// MARK: - Loop video when ended.
#objc func playerItemDidReachEnd(notification: NSNotification) {
self.player?.seek(to: kCMTimeZero)
self.player?.play()
}
// MARK: - Loop video when ended.
#objc func playerDidStarted(notification: NSNotification) {
// self.player?.seek(to: kCMTimeZero)
// self.player?.play()
}
override public func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if object is AVPlayerItem {
switch keyPath! {
case "playbackBufferEmpty":
startLoadingView()
print("playbackBufferEmpty")
case "playbackLikelyToKeepUp":
stopLoadingiew()
print("playbackLikelyToKeepUp")
case "playbackBufferFull":
stopLoadingiew()
print("playbackBufferFull")
default:
break
}
}
}
func startLoadingView(){
activityIndicator.color = Color.theme.value// add your color
self.videoView.addSubview(activityIndicator)
activityIndicator.startAnimating()
}
func stopLoadingiew(){
activityIndicator.stopAnimating()
activityIndicator.removeFromSuperview()
}
}

You call showVideoView every time setUIText is called, which adds several KVO observations:
addObserver(self, forKeyPath: "actionAtItemEnd", options: [], context: nil)
let playerItem = self.player?.currentItem
playerItem?.addObserver(self, forKeyPath: "playbackBufferEmpty", options: .new, context: nil)
playerItem?.addObserver(self, forKeyPath: "playbackLikelyToKeepUp", options: .new, context: nil)
playerItem?.addObserver(self, forKeyPath: "playbackBufferFull", options: .new, context: nil)
However, you only remove these in viewWillDisappear, which is not balanced. You must ensure that you only observe exactly one time for each property, and remove the observation exactly one time.

I followed Rob Napier answer and I made these 2 functions
var isObserversAdded = false
func addObservers(){
self.isObserversAdded = true
addObserver(self, forKeyPath: "actionAtItemEnd", options: [], context: nil)
let playerItem = self.player?.currentItem
playerItem?.addObserver(self, forKeyPath: "playbackBufferEmpty", options: .new, context: nil)
playerItem?.addObserver(self, forKeyPath: "playbackLikelyToKeepUp", options: .new, context: nil)
playerItem?.addObserver(self, forKeyPath: "playbackBufferFull", options: .new, context: nil)
}
func removeObservers(){
if isObserversAdded {
let playerItem = self.player?.currentItem
self.removeObserver(self, forKeyPath: "actionAtItemEnd")
playerItem?.removeObserver(self, forKeyPath: "playbackBufferEmpty")
playerItem?.removeObserver(self, forKeyPath: "playbackLikelyToKeepUp")
playerItem?.removeObserver(self, forKeyPath: "playbackBufferFull")
self.isObserversAdded = false
}
}
so when I want to remove an observer I will be sure that I added it and I call removeObservers() every time before I call addObservers()

Related

I want to add activity indicator to my avplayer when its buffering

private func startVideo() {
if let url = URL(string: "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/VolkswagenGTIReview.mp4") {
player = AVPlayer(url: url)
let playerViewController = AVPlayerViewController()
playerViewController.player = player
playerViewController.view.frame = avPlayerView.bounds
addChild(playerViewController)
avPlayerView.addSubview(playerViewController.view)
playerViewController.didMove(toParent: self)
player?.play()
}
}
need to add a activity loader whenever the video is buffering
You can get this working using the following code. Observing the KeyPath on the Player can make you achieve this.
var activityIndicator: UIActivityIndicatorView!
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
if player != nil {
player.play()
player.addObserver(self, forKeyPath: "timeControlStatus", options: [.old, .new], context: nil)
}
}
override public func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if keyPath == "timeControlStatus", let change = change, let newValue = change[NSKeyValueChangeKey.newKey] as? Int, let oldValue = change[NSKeyValueChangeKey.oldKey] as? Int {
if #available(iOS 10.0, *) {
let oldStatus = AVPlayer.TimeControlStatus(rawValue: oldValue)
let newStatus = AVPlayer.TimeControlStatus(rawValue: newValue)
if newStatus != oldStatus {
DispatchQueue.main.async {[weak self] in
if newStatus == .playing || newStatus == .paused {
self!.activityIndicator.stopAnimating()
} else {
self!.activityIndicator.startAnimating()
}
}
}
} else {
// Fallback on earlier versions
self.activityIndicator.stopAnimating()
}
}
}

How to open web view on button click in SwiftUI?

Here is my WebView
struct Webview: UIViewControllerRepresentable {
let url: URL
func makeUIViewController(context: Context) -> WebviewController {
let webviewController = WebviewController()
let request = URLRequest(url: self.url, cachePolicy: .returnCacheDataElseLoad)
webviewController.webview.load(request)
return webviewController
}
func updateUIViewController(_ webviewController: WebviewController, context: Context) {
//
}
}
class WebviewController: UIViewController, WKNavigationDelegate {
lazy var webview: WKWebView = WKWebView()
lazy var progressbar: UIProgressView = UIProgressView()
deinit {
self.webview.removeObserver(self, forKeyPath: "estimatedProgress")
self.webview.scrollView.removeObserver(self, forKeyPath: "contentOffset")
}
override func viewDidLoad() {
super.viewDidLoad()
self.webview.navigationDelegate = self
self.view.addSubview(self.webview)
self.webview.frame = self.view.frame
self.webview.translatesAutoresizingMaskIntoConstraints = false
self.view.addConstraints([
self.webview.topAnchor.constraint(equalTo: self.view.topAnchor),
self.webview.bottomAnchor.constraint(equalTo: self.view.bottomAnchor),
self.webview.leadingAnchor.constraint(equalTo: self.view.leadingAnchor),
self.webview.trailingAnchor.constraint(equalTo: self.view.trailingAnchor),
])
self.webview.addSubview(self.progressbar)
self.setProgressBarPosition()
webview.scrollView.addObserver(self, forKeyPath: "contentOffset", options: .new, context: nil)
self.progressbar.progress = 0.1
webview.addObserver(self, forKeyPath: "estimatedProgress", options: .new, context: nil)
}
func setProgressBarPosition() {
self.progressbar.translatesAutoresizingMaskIntoConstraints = false
self.webview.removeConstraints(self.webview.constraints)
self.webview.addConstraints([
self.progressbar.topAnchor.constraint(equalTo: self.webview.topAnchor, constant: self.webview.scrollView.contentOffset.y * -1),
self.progressbar.leadingAnchor.constraint(equalTo: self.webview.leadingAnchor),
self.progressbar.trailingAnchor.constraint(equalTo: self.webview.trailingAnchor),
])
}
// MARK: - Web view progress
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
switch keyPath {
case "estimatedProgress":
if self.webview.estimatedProgress >= 1.0 {
UIView.animate(withDuration: 0.3, animations: { () in
self.progressbar.alpha = 0.0
}, completion: { finished in
self.progressbar.setProgress(0.0, animated: false)
})
} else {
self.progressbar.isHidden = false
self.progressbar.alpha = 1.0
progressbar.setProgress(Float(self.webview.estimatedProgress), animated: true)
}
case "contentOffset":
self.setProgressBarPosition()
default:
super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
}
}
}
And here is my button in the Content view.
Button(action: {
Webview(url: URL(string:"https://www.google.com")!)
})
{
Image("go")
}
}
Actually, when I am clicking it, nothing happens but when I move it outside of the button, it opens automatically.
My question is, how to open WebView on the button click, I am very new to SwiftUI.
Thank you.
You are calling in the wrong way.
Use this
struct ContentView: View {
#State private var isShowingWebView: Bool = false
var body: some View {
Button(action: {
isShowingWebView = true
// Webview(url: URL(string:"https://www.google.com")!) <<-- Remove
})
{
Text("go")
}
.sheet(isPresented: $isShowingWebView) {
Webview(url: URL(string:"https://www.google.com")!)
}
}
}

Issues Uploading Images and posts to Firebase 5 Database

I am currently trying to upload a photo URL and post caption onto my firebase database. The photos are currently being saved on firebase storage which is fine, however I would like for it to also appear on the firebase database.
I repeatedly find myself dealing with this error "Value of type 'StorageMetadata' has no member 'downloadURL'"
I understand that in firebase 5 to get the url from storage you need to call downloadURL on storage reference, not metadata. I have tried multiple ways and examples, but it all leads to errors.screenshot of error
#IBOutlet weak var photo: UIImageView!
#IBOutlet weak var captionTextView: UITextView!
#IBOutlet weak var removeButton: UIBarButtonItem!
#IBOutlet weak var shareButton: UIButton!
var selectedImage: UIImage?
override func viewDidLoad() {
super.viewDidLoad()
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(self.handleSelectPhoto)); photo.addGestureRecognizer(tapGesture)
photo.isUserInteractionEnabled = true
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
handlePost()
}
func handlePost() {
if selectedImage != nil {
self.shareButton.isEnabled = true
self.removeButton.isEnabled = true
self.shareButton.backgroundColor = UIColor(red: 0, green: 0, blue: 0, alpha: 1)
}else{
self.shareButton.isEnabled = false
self.shareButton.backgroundColor = .lightGray
self.removeButton.isEnabled = false
}
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
view.endEditing(true)
}
#objc func handleSelectPhoto() {
let pickerController = UIImagePickerController()
pickerController.delegate = self
present(pickerController, animated: true, completion: nil)
}
#IBAction func shareButton_TouchUpInside(_ sender: Any) {
view.endEditing(true)
let hud = JGProgressHUD(style: .dark)
hud.textLabel.text = "Loading"
hud.show(in: self.view)
hud.dismiss(afterDelay: 3.0)
if let profileImg = self.selectedImage, let imageData = UIImageJPEGRepresentation(profileImg, 0.1) {
let photoIdString = NSUUID().uuidString
let storageRef = Storage.storage().reference(forURL: ".......").child("posts").child(photoIdString)
storageRef.putData(imageData, metadata: nil, completion: { (metadata, error) in
if error != nil {
return
}
//let photoUrl = url?.absoluteString
let photoUrl = metadata?.downloadURL()?.absoluteString
self.sendDataToDatabase(photoUrl: photoUrl!)
}
)}
}
#IBAction func remove_TouchUpInside(_ sender: Any) {
clean()
handlePost()
}
func sendDataToDatabase(photoUrl: String) {
let ref = Database.database().reference()
let postsReference = ref.child("posts")
let newPostId = postsReference.childByAutoId().key
let newPostReference = postsReference.child(newPostId)
newPostReference.setValue(["photoUrl": photoUrl, "caption": captionTextView.text!], withCompletionBlock: {
(error, ref) in
if error != nil {
ProgressHUD.showError(error!.localizedDescription)
return
}
ProgressHUD.showSuccess("Success")
self.clean()
self.tabBarController?.selectedIndex = 0 // switches user back to selected tabbar 0 = first 1 = second etc.
})
}
func clean() {
self.captionTextView.text = ""
self.photo.image = UIImage(named:"placeholder-photo")
self.selectedImage = nil
}
}
extension CameraViewController: UIImagePickerControllerDelegate,
UINavigationControllerDelegate {
func imagePickerController(_ picker: UIImagePickerController,
didFinishPickingMediaWithInfo info: [String : Any]) {
print("did finish picking media")
if let image = info["UIImagePickerControllerOriginalImage"]
as? UIImage {
selectedImage = image
photo.image = image
}
// profileImage.image = infoPhoto
dismiss(animated: true, completion: nil)
}
}
import UIKit
import ProgressHUD
import FirebaseStorage
import FirebaseDatabase
class CameraViewController: UIViewController {
#IBOutlet weak var photo: UIImageView!
#IBOutlet weak var shareButton: UIButton!
#IBOutlet weak var captionTextView: UITextView!
#IBOutlet weak var removeButton: UIBarButtonItem!
var selectedImage: UIImage?
override func viewDidLoad() {
super.viewDidLoad()
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(self.handleSelectPhoto))
photo.addGestureRecognizer(tapGesture)
photo.isUserInteractionEnabled = true
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
handlePost()
}
func handlePost(){
if selectedImage != nil {
self.shareButton.isEnabled = true
self.removeButton.isEnabled = true
self.shareButton.backgroundColor = UIColor(red: 0, green: 0, blue: 0, alpha: 1)
} else {
self.shareButton.isEnabled = false
self.removeButton.isEnabled = false
self.shareButton.backgroundColor = .lightGray
}
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
view.endEditing(true)
}
#objc func handleSelectPhoto(){
let pickerController = UIImagePickerController()
pickerController.delegate = self
present(pickerController, animated: true, completion: nil)
}
// Share photos to the storage database
#IBAction func shareButton_TouchUpInside(_ sender: Any) {
view.endEditing(true)
ProgressHUD.show("Please wait...", interaction: false)
if let profileImg = self.selectedImage, let photoData = profileImg.jpegData(compressionQuality: 0.1) {
let photoIdString = NSUUID().uuidString
print(photoIdString)
let storageRef = Storage.storage().reference(forURL:Config.STORAGE_ROOT_REF).child("Posts").child(photoIdString)
storageRef.putData(photoData, metadata: nil, completion: { (metadata, error) in
if error != nil {
ProgressHUD.showError(error!.localizedDescription)
return
}
let photoUrl = metadata?.downloadURL()?.absoluteString
self.sendDataToDatabase(photoUrl: photoUrl!)
})
} else {
ProgressHUD.showError("Profile image can't be empty")
}
}
#IBAction func remove_touchUpInside(_ sender: Any) {
clean()
handlePost()
}
func sendDataToDatabase(photoUrl:String){
let ref = Database.database().reference()
let postsReference = ref.child("posts")
let newPostId = postsReference.childByAutoId().key
let newPostReference = postsReference.child(newPostId)
newPostReference.setValue(["photoUrl": photoUrl, "caption": captionTextView.text!], withCompletionBlock: {
(error, ref) in
if error != nil{
ProgressHUD.showError(error!.localizedDescription)
return
}
ProgressHUD.showSuccess("Success")
self.clean()
self.tabBarController?.selectedIndex = 0
})
}
func clean(){
self.captionTextView.text = ""
self.photo.image = UIImage(named: "placeholder-img")
self.shareButton = nil
}
}
extension CameraViewController : UIImagePickerControllerDelegate, UINavigationControllerDelegate {
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
print("did Finish picking media")
if let chosenImage = info[.originalImage] as? UIImage {
selectedImage = chosenImage
photo.image = chosenImage
}
dismiss(animated: true, completion: nil)
}
}
Use Above code, it will successfully upload the image and post that to the Firebase Database

AVPlayer video still playing after segue to next view controller

I'm currently looping my mp4 video with no playback controls, kind of like a gif but with sound. But I do not know why when I segue to the next view controller, the video is still playing. Does anybody know the simplest method to resolve this issue?
ViewController
import UIKit
import AVKit
import AVFoundation
fileprivate var playerObserver: Any?
class ScoreController: UIViewController {
#IBOutlet var label: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
let returnValue: Int = UserDefaults.standard.integer(forKey: "userScore")
label.text = "Your Score: \(returnValue)/30"
do {
try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback)
let path = Bundle.main.path(forResource: "Innie-Kiss", ofType:"mp4")
let player = AVPlayer(url: URL(fileURLWithPath: path!))
let resetPlayer = {
player.seek(to: kCMTimeZero)
player.play()
}
playerObserver = NotificationCenter.default.addObserver(forName: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: player.currentItem, queue: nil) { notification in resetPlayer() }
let controller = AVPlayerViewController()
controller.player = player
controller.showsPlaybackControls = false
self.addChildViewController(controller)
let screenSize = UIScreen.main.bounds.size
let videoFrame = CGRect(x: 0, y: 130, width: screenSize.width, height: (screenSize.height - 130) / 2)
controller.view.frame = videoFrame
self.view.addSubview(controller.view)
player.play()
} catch {
}
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
}
fileprivate var player: AVPlayer? {
didSet { player?.play() }
}
deinit {
guard let observer = playerObserver else { return }
NotificationCenter.default.removeObserver(observer)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
#IBAction func doneButton(_ sender: Any) {
self.performSegue(withIdentifier: "done", sender: self)
}
}
In viewWillDisapper() or button action for segue do this :
NotificationCenter.default.removeObserver(self)
Also move this from viewDidLoad() to some function like :
var player: AVPlayer?
func audioPlayer(){
do {
try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback)
let path = Bundle.main.path(forResource: "Innie-Kiss", ofType:"mp4")
player = AVPlayer(url: URL(fileURLWithPath: path!))
let resetPlayer = {
self.player?.seek(to: kCMTimeZero)
self.player?.play()
}
playerObserver = NotificationCenter.default.addObserver(forName: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: player?.currentItem, queue: nil) { notification in resetPlayer() }
let controller = AVPlayerViewController()
controller.player = player
controller.showsPlaybackControls = false
self.addChildViewController(controller)
let screenSize = UIScreen.main.bounds.size
let videoFrame = CGRect(x: 0, y: 130, width: screenSize.width, height: (screenSize.height - 130) / 2)
controller.view.frame = videoFrame
self.view.addSubview(controller.view)
player?.play()
} catch {
}
}
and make player object a global variable. var player = AVPlayer? and in viewWillDisappear make it nil.
So your viewWillDisappear should look like this :
override func viewWillDisappear(_ animated: Bool) {
NotificationCenter.default.removeObserver(self)
if player != nil{
player?.replaceCurrentItem(with: nil)
player = nil
}
}
override func viewWillDisappear(_ animated: Bool) {
}
This may be helpful to you if you are switching windows; as you perform a segue, you will execute the function included in curly brackets automatically. I'm rubbish with AV, but some of the other's code suggested to turn off the function may work here if it isn't working in the IBAction.
I'm not great with Xcode though, so mileage will probably vary.
I had this issue and fixed it by adding this line before setting player to nil:
[self.player pause];
here is my viewWillDisapear:
- (void)viewWillDisappear:(BOOL)animated
{
[[NSNotificationCenter defaultCenter] removeObserver:self
name:AVPlayerItemDidPlayToEndTimeNotification
object:self.player];
[self.player removeObserver:self forKeyPath:#"rate"];
[self.player removeTimeObserver:self.playerObserver];
self.playerObserver = nil;
[self.player pause];
self.player = nil;
[[UIDevice currentDevice] setValue:#(UIInterfaceOrientationPortrait) forKey:#"orientation"];
[UINavigationController attemptRotationToDeviceOrientation];
[StayfilmApp restrictOrientation:YES];
}
class ScoreController: UIViewController {
#IBOutlet var label: UILabel!
var player: AVPlayer?
override func viewDidLoad() {
super.viewDidLoad()
let returnValue: Int = UserDefaults.standard.integer(forKey: "userScore")
label.text = "Your Score: \(returnValue)/30"
do {
try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback)
let path = Bundle.main.path(forResource: "Innie-Kiss", ofType:"mp4")
player = AVPlayer(url: URL(fileURLWithPath: path!))
let resetPlayer = {
player.seek(to: kCMTimeZero)
player.play()
}
playerObserver = NotificationCenter.default.addObserver(forName: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: player.currentItem, queue: nil) { notification in resetPlayer() }
let controller = AVPlayerViewController()
controller.player = player
controller.showsPlaybackControls = false
self.addChildViewController(controller)
let screenSize = UIScreen.main.bounds.size
let videoFrame = CGRect(x: 0, y: 130, width: screenSize.width, height: (screenSize.height - 130) / 2)
controller.view.frame = videoFrame
self.view.addSubview(controller.view)
player.play()
} catch {
}
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
player.pause()
}
fileprivate var player: AVPlayer? {
didSet { player?.play() }
}
deinit {
guard let observer = playerObserver else { return }
NotificationCenter.default.removeObserver(observer)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
#IBAction func doneButton(_ sender: Any) {
self.performSegue(withIdentifier: "done", sender: self)
}
}
notable changes:
var player: AVPlayer?
and also
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
NotificationCenter.default.removeObserver(self)
player?.pause()
}
I have the same problem when I used segue to go to the next ViewController, then I used present code with swift by using this code
#IBAction func next(_ sender: Any) {
let vc = self .storyboard?.instantiateViewController(withIdentifier:"ViewControllerAA") as! ViewControllerAA
self.present(vc,animated: true,completion: nil)
}
but the problem didn't solve , then I used this code to go to the next VC and my problem solved and the background sound of the video stoped, and the code is this below
#IBAction func next(_ sender: Any) {
if let vc = self.storyboard?.instantiateViewController(withIdentifier: "ViewControllerAA") as? ViewControllerAA {
let appDelegate = UIApplication.shared.delegate as! AppDelegate
appDelegate.window?.rootViewController = vc
}
}

AVPlayerViewController not deallocating immediately

I am playing a video in my app in TVos. I am using AVPlayerViewController to play the video. But when i press Menu button on Apple Tv remote i goes back to the view controller from which i came here but video continues to play and after 8 or 10 second it gets deallocated. This is really a bad bug and i am stuck on this for few days. Any help will be highly appreciated.
Here is my code for the view controller.
import Foundation
import UIKit
import AVKit
class ViewController : UIViewController {
var avplayerVC : AVPlayerViewController?
var recentlyWatchedTimer : NSTimer?
var lessonToWatch : Lesson?
override func viewDidLoad() {
super.viewDidLoad()
if let urlVideo = lessonToWatch?.lessonurl {
let activityIndicator = UIActivityIndicatorView(frame: CGRectMake(self.view.frame.size.width / 2.0, self.view.frame.size.height / 2.0, 30.0, 30.0))
let asset : AVURLAsset = AVURLAsset(URL: NSURL.init(string: urlVideo)!, options: nil)
let keys = ["playable"];
avplayerVC = AVPlayerViewController()
weak var weakSelf = self
asset.loadValuesAsynchronouslyForKeys(keys) { () -> Void in
dispatch_async(dispatch_get_main_queue(), { () -> Void in
weakSelf!.avplayerVC?.player = AVPlayer(playerItem: AVPlayerItem(asset: asset))
weakSelf!.avplayerVC?.player?.seekToTime(kCMTimeZero)
print("Status 1: " + "\(self.avplayerVC?.player?.status.rawValue)")
print(self.view?.frame)
// doesn't work
weakSelf!.avplayerVC?.view.frame = self.view.frame
activityIndicator.stopAnimating()
activityIndicator.removeFromSuperview()
weakSelf!.view.addSubview((self.avplayerVC?.view!)!)
weakSelf!.avplayerVC?.player?.play()
weakSelf!.recentlyWatchedTimer = NSTimer.scheduledTimerWithTimeInterval(20.0, target: self, selector: "addToRecentlyWatched" , userInfo: nil, repeats: false)
})
}
print("In LessonPlayViewController View Did Load")
self.view.addSubview(activityIndicator)
activityIndicator.startAnimating()
}
}
func addToRecentlyWatched() {
if let lesson = lessonToWatch {
DataManager.sharedInstance.addRecentlyWatch(lesson)
}
recentlyWatchedTimer?.invalidate()
}
deinit {
print("deinit")
avplayerVC?.view.removeFromSuperview()
avplayerVC?.player = nil
avplayerVC = nil
}
// MARK : AVPlayerViewControllerDelegate
}
You want to use weakSelf in all places inside the async callback block, but you're printing self.view.frame at once place.
You should use a capture list to make sure all references to self are weak within the closure. Then use a guard to check that the references are still valid within the closure.
import Foundation
import UIKit
import AVKit
import AVFoundation
class ViewController : UIViewController {
var avplayerVC : AVPlayerViewController?
var recentlyWatchedTimer : NSTimer?
var lessonToWatch : Lesson?
override func viewDidLoad() {
super.viewDidLoad()
if let urlVideo = lessonToWatch?.lessonurl {
let activityIndicator = UIActivityIndicatorView(frame: CGRectMake(self.view.frame.size.width / 2.0, self.view.frame.size.height / 2.0, 30.0, 30.0))
let asset : AVURLAsset = AVURLAsset(URL: NSURL.init(string: urlVideo)!, options: nil)
let keys = ["playable"];
avplayerVC = AVPlayerViewController()
asset.loadValuesAsynchronouslyForKeys(keys) { [weak self] in
dispatch_async(dispatch_get_main_queue()) { [weak self] in
guard let vc = self, playerVC = vc.avplayerVC else {
return
}
playerVC.player = AVPlayer(playerItem: AVPlayerItem(asset: asset))
playerVC.player?.seekToTime(kCMTimeZero)
print("Status 1: " + "\(playerVC.player?.status.rawValue)")
print(vc.view?.frame)
// doesn't work
playerVC.view.frame = vc.view.frame
activityIndicator.stopAnimating()
activityIndicator.removeFromSuperview()
vc.view.addSubview(playerVC.view)
playerVC.player?.play()
vc.recentlyWatchedTimer = NSTimer.scheduledTimerWithTimeInterval(20.0, target: vc, selector: "addToRecentlyWatched" , userInfo: nil, repeats: false)
}
}
print("In LessonPlayViewController View Did Load")
self.view.addSubview(activityIndicator)
activityIndicator.startAnimating()
}
}
func addToRecentlyWatched() {
if let lesson = lessonToWatch {
DataManager.sharedInstance.addRecentlyWatch(lesson)
}
recentlyWatchedTimer?.invalidate()
}
deinit {
print("deinit")
avplayerVC?.view.removeFromSuperview()
avplayerVC?.player = nil
avplayerVC = nil
}
// MARK : AVPlayerViewControllerDelegate
}
More on capture lists:
https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/AutomaticReferenceCounting.html#//apple_ref/doc/uid/TP40014097-CH20-ID57
Edit
Also note that you should explicitly stop the video playback when leaving the view controller and not rely on the player to stop the playback as part of its deallocation.
I.e. in viewDidDisappear or alike.

Resources