Playlist wont continue in background mode - swift

I am having trouble playing a playlist in background mode while the device is locked. It plays the current audio when locked but I am having trouble with it going to the next audio file in background mode.
I am using a collectionView and a custom cell where I am allowing the user to tap and play the audio in that cell. I made it autoplay, so when the audio is done, it scrolls to the next cell automatically and plays that one and so on. All of that works, its just when the device is locked and in background mode that the playing of the next audio cell stops and just wont continue. I do have 3 second dispatch delays in between the load of the next audio file.
Any help or insight is greatly appreciated.
View Controller
import UIKit
import AVFoundation
class MainVC: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout, CustomCellDelegate {
var streams: [[String:Any]] = []
#IBOutlet weak var cv: UICollectionView!
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if let cell = cv.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as? CollectionViewCell {
cell.delegate = self
cell.nameLbl.text = streams[indexPath.item]["shortname"] as? String
return cell
} else {
return CollectionViewCell()
}
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
if thereIsCellTapped != true {
let item = cv.cellForItem(at: indexPath) as! CollectionViewCell
self.loadingCell()
let when = DispatchTime.now() + 3 // change 2 to desired number of seconds
DispatchQueue.main.asyncAfter(deadline: when) {
DispatchQueue.main.async {
UIView.animate(
withDuration: 0.5,
delay: 0,
usingSpringWithDamping: 1,
initialSpringVelocity: 1,
options: [],
animations: {
item.superview?.bringSubview(toFront: item)
self.view.layoutIfNeeded()
},
}), completion: { (finished: Bool) in
let theStream = self.streams[indexPath.item]["audioUrl"] as? String
item.audioStreamSelected(audioUrl: audioUrl!)
})
)
}
}
}
}
func closeView(_ cell: CollectionViewCell) {
let visibleItems: NSArray = indexPath as NSArray
let currentItem: IndexPath = visibleItems.object(at: 0) as! IndexPath
let nextItem: IndexPath = IndexPath(item: currentItem.item + 1, section: 0)
if nextItem.row == streams.count{
self.cv?.scrollToItem(
at: IndexPath(row: 0, section: 0),
at: .right,
animated: true
)
} else {
self.cv.scrollToItem(at: nextItem, at: .left, animated: true)
let when = DispatchTime.now() + 3
DispatchQueue.main.asyncAfter(deadline: when) {
self.collectionView(self.cv, didSelectItemAt: nextItem as IndexPath)
}
}
}
}
Custom Cell
import UIKit
import AVFoundation
import MediaPlayer
protocol CustomCellDelegate {
func closeView(_ cell:CollectionViewCell)
}
class CollectionViewCell: UICollectionViewCell {
var player = AVPlayer()
var playerItem:AVPlayerItem?
let mpic = MPRemoteCommandCenter.shared()
var delegate: CustomCellDelegate?
var _timeObserver:Any?
#IBOutlet weak var nameLbl: UILabel!
#IBAction func closeBtn(_ sender: Any) {
if let delegate = self.delegate {
delegate.closeView(self)
}
}
override func awakeFromNib() {
super.awakeFromNib()
NotificationCenter.default.addObserver(self,selector: #selector(playerItemDidReachEnd(_:)),name:NSNotification.Name.AVPlayerItemDidPlayToEndTime,
object: player.currentItem)
UIApplication.shared.beginReceivingRemoteControlEvents()
do {
try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback)
print("AVAudioSession Category Playback OK")
do {
try AVAudioSession.sharedInstance().setActive(true)
print("AVAudioSession is Active")
} catch let error as NSError {
print(error.localizedDescription)
}
} catch let error as NSError {
print(error.localizedDescription)
}
}
func setupNowPlayingInfoCenter() {
UIApplication.shared.beginReceivingRemoteControlEvents()
mpic.skipBackwardCommand.isEnabled = false
mpic.seekBackwardCommand.isEnabled = false
mpic.playCommand.addTarget {event in
self.player.play()
self.setupNowPlayingInfoCenter()
return .success
}
mpic.pauseCommand.addTarget {event in
self.player.pause()
return .success
}
mpic.nextTrackCommand.addTarget {event in
return .success
}
mpic.previousTrackCommand.addTarget {event in
return .success
}
}
func playerItemDidReachEnd(_ notification: Notification) {
if let _: AVPlayerItem = notification.object as? AVPlayerItem {
player.replaceCurrentItem(with: nil)
if let delegate = self.delegate {
delegate.closeView(self)
}
player.pause()
player.seek(to: kCMTimeZero)
if (_timeObserver != nil) {
player.removeTimeObserver(_timeObserver!)
_timeObserver = nil
}
}
}
func audioStreamSelected(audioUrl: String){
if let streamer = URL(string: audioUrl){
let playerItem:AVPlayerItem = AVPlayerItem(url: streamer)
self.player = AVPlayer(playerItem: playerItem)
player.replaceCurrentItem(with: playerItem)
player.actionAtItemEnd = .pause
if (_timeObserver == nil) {
_timeObserver = player.addPeriodicTimeObserver(forInterval: interval, queue: DispatchQueue.main) {
[weak self] time in
if self?.player.currentItem?.status == AVPlayerItemStatus.readyToPlay {
}
}
}
}
}
}

Related

Adding image from api to CustomCell programmatically is getting laggy

My tableview gets slow when scrolling, I have a custom cell and a tableview:
I have this controller, where the api call is made and the array of trips is filled, then in cellForRowAt im creating the cell
class HistorialViewController: UIViewController , UITableViewDataSource, UITableViewDelegate {
#IBOutlet weak var historialTableView: UITableView!
var trips = [RootClass]()
override func viewDidLoad() {
super.viewDidLoad()
self.historialTableView.delegate = self
self.historialTableView.dataSource = self
self.historialTableView.register(CustomCellHistorial.self, forCellReuseIdentifier: cellId)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
print("coming back")
self.fetchTrips()
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = self.historialTableView.dequeueReusableCell(withIdentifier: cellId, for: indexPath) as! CustomCellHistorial
let trip = self.trips[indexPath.row]
cell.trip = trip
return cell
}
private func fetchTrips(){
AFWrapper.getTripHistory( success: { (jsonResponse) in
self.trips = []
for item in jsonResponse["trips"].arrayValue {
self.trips.append(RootClass(fromJson:item))
}
self.reloadTrips()
Timer.scheduledTimer(withTimeInterval: 2.0, repeats: false) { (nil) in
self.indicator.stopAnimating()
}
}, failure: { (error) -> Void in
print(error)
})
}
func reloadTrips(){
DispatchQueue.main.async{
self.historialTableView.reloadData()
}
}
This is my CustomCell
class CustomCellHistorial : UITableViewCell{
var trip: RootClass? {
didSet{
dateTimeLabel.text = returnCleanDate(fecha: trip!.createdAt)
distanceAndTimeLabel.text = returnDistanceAndTime(distance: (trip?.trip!.real!.dist!)!, time: (trip?.trip!.real!.time)!)
priceLabel.text = returnCleanPrice(price: (trip?.trip!.real!.price!)!)
ratedLabel.text = "Not Rated"
self.productImage.image = self.returnDriverImage(photoUrl: (self.trip?.driver!.photo!)!)
if (trip!.score) != nil {
let score = trip!.score
if (score?.driver) != nil{
if(trip!.score!.driver!.stars! != 0.0 ){
ratedLabel.isHidden = true
}else{
ratedLabel.isHidden = false
}
}else{
print("yei")
}
}
}
}
private func returnDriverImage(photoUrl: String) -> UIImage{
let url = URL(string: photoUrl)
do {
let data = try Data(contentsOf: url!)
if let roundedimg = UIImage(data: data){
let croppedImageDriver = roundedimg.resize(toTargetSize: self.productImage.frame.size)
return croppedImageDriver
}
} catch let error {
debugPrint("ERRor :: \(error)")
let image = UIImage(named: "perfilIcono")
return image!
}
let image = UIImage(named: "perfilIcono")
return image!
}
Answers that I have found are for older versions of Swift, and the way they make the tableview its in storyboard or they are not handling custom cells.
I think the problem is in the returnDriverImage function.
This line
let data = try Data(contentsOf: url!)
You call from
self.productImage.image = self.returnDriverImage(photoUrl: (self.trip?.driver!.photo!)!)
blocks the main thread and re downloads the same image multiple times when scroll , please consider using SDWebImage

Trying to make a like button but Unexpectedly found nil while implicitly unwrapping an Optional value

I try to make a like button and unlike but I it gives me error when I try to press the button in the simulator .if you know any other code for the like button to be much easier will be helpful ( or some websites , yt vids)
#IBOutlet weak var postTextLabel: UILabel!
#IBOutlet weak var subtitleLabel: UILabel!
#IBOutlet weak var profileImageView: UIImageView!
#IBOutlet weak var usernameLabel: UILabel!
#IBOutlet weak var likeLabel : UILabel!
#IBOutlet weak var likeBtn: UIButton!
#IBOutlet weak var unlikeBtn: UIButton!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
profileImageView.layer.cornerRadius = profileImageView.bounds.height / 2
profileImageView.clipsToBounds = true
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
weak var post:Post?
func set(post:Post) {
self.post = post
var postID : String!
self.profileImageView.image = nil
ImageService.getImage(withURL: post.author.photoURL) { image , url in
guard let _post = self.post else {return}
if _post.author.photoURL.absoluteString == url.absoluteString {
self.profileImageView.image = image
}else {
print("not the right image")
}
}
usernameLabel.text = post.author.username
postTextLabel.text = post.text
subtitleLabel.text = post.createdAt.calenderTimeSinceNow()
}
var postID : String!
#IBAction func likePressed(_ sender: Any) {
self.postID = "post_0"
let ref = Database.database().reference()
let keyToPost = ref.child("posts").childByAutoId().key
ref.child("posts").child(self.postID).observeSingleEvent(of: .value) { (snapshot) in
if let post = snapshot.value as? [String : AnyObject] {
let updateLikes : [ String : Any] = [ "peopleWhoLike/\(keyToPost)" : Auth.auth().currentUser!.uid ]
ref.child("posts").child(self.postID).updateChildValues(updateLikes, withCompletionBlock : {(error ,reff) in
if error == nil {
ref.child("posts").child(self.postID).observeSingleEvent(of : .value, with: { (snap) in
if let properties = snap.value as? [ String : AnyObject] {
if let likes = properties["peopleWhoLike"] as? [String: AnyObject] {
let count = likes.count
self.likeLabel.text = "\(count) Likes"
}
}
})
}
})
}
}
}
#IBAction func unlikedPressed(_ sender:Any) {
let ref = Database.database().reference()
ref.child("posts").child(self.postID).observeSingleEvent(of: .value, with: { (snapshot) in
if let properties = snapshot.value as? [String : AnyObject] {
if let peopleWhoLike = properties["peopleWhoLike"] as? [String: AnyObject] {
for (id,person) in peopleWhoLike {
if person as? String == Auth.auth().currentUser!.uid {
ref.child("posts").child(self.postID).child("peopleWhoLike").child(id).removeValue(completionBlock: {(error , reff)in
if error == nil {
ref.child("posts").child(self.postID).observeSingleEvent(of: .value, with: {(snap) in
if let prop = snap.value as? [String : AnyObject] {
if let likes = prop["peopleWhoLike"] as? [String: AnyObject] {
let count = likes.count
self.likeLabel.text = "\(count) Likes"
ref.child("posts").child(self.postID).updateChildValues(["likes" : count])
} else {
self.likeLabel.text = " 0 Likes"
ref.child("posts").child(self.postID).updateChildValues(["likes" : 0])
}
}
})
}
})
self.likeBtn.isHidden = false
self.unlikeBtn.isHidden = true
self.unlikeBtn.isEnabled = true
break
}
}
}
}
})
ref.removeAllObservers()
}
}
class HomeController: UIViewController, UITableViewDelegate, UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
return 2
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
switch section {
case 0:
return posts.count
case 1:
return fetchingMore ? 1 : 0
default:
return 0
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.section == 0 {
let cell = tableView.dequeueReusableCell(withIdentifier: "postCell", for: indexPath) as! PostTableViewCell
cell.set(post: posts[indexPath.row])
return cell
} else {
let cell = tableView.dequeueReusableCell(withIdentifier: "loadingCell", for: indexPath) as! LoadingCell
cell.spinner.startAnimating()
return cell
}
}
var tableView:UITableView!
var cellHeights: [IndexPath : CGFloat] = [:]
var posts = [Post]()
var fetchingMore = false
var endReached = false
let leadingScreensForBatching:CGFloat = 3.0
var refreshControl:UIRefreshControl!
var seeNewPostsButton:SeeNewPostsButton!
var seeNewPostsButtonTopAnchor:NSLayoutConstraint!
var lastUploadedPostID:String?
var postsRef:DatabaseReference {
return Database.database().reference().child("posts")
}
var oldPostsQuery:DatabaseQuery {
var queryRef:DatabaseQuery
let lastPost = posts.last
if lastPost != nil {
let lastTimestamp = lastPost!.createdAt.timeIntervalSince1970 * 1000
queryRef = postsRef.queryOrdered(byChild: "timestamp").queryEnding(atValue: lastTimestamp)
} else {
queryRef = postsRef.queryOrdered(byChild: "timestamp")
}
return queryRef
}
var newPostsQuery:DatabaseQuery {
var queryRef:DatabaseQuery
let firstPost = posts.first
if firstPost != nil {
let firstTimestamp = firstPost!.createdAt.timeIntervalSince1970 * 1000
queryRef = postsRef.queryOrdered(byChild: "timestamp").queryStarting(atValue: firstTimestamp)
} else {
queryRef = postsRef.queryOrdered(byChild: "timestamp")
}
return queryRef
}
#IBAction func handleLogoutButton(_ sender: Any) {
try! Auth.auth().signOut()
}
override func viewDidLoad() {
super.viewDidLoad()
tableView = UITableView(frame: view.bounds, style: .plain)
let cellNib = UINib(nibName: "PostTableViewCell", bundle: nil)
tableView.register(cellNib, forCellReuseIdentifier: "postCell")
tableView.register(LoadingCell.self, forCellReuseIdentifier: "loadingCell")
tableView.backgroundColor = UIColor(white: 0.90,alpha:1.0)
view.addSubview(tableView)
var layoutGuide:UILayoutGuide!
if #available(iOS 11.0, *) {
layoutGuide = view.safeAreaLayoutGuide
} else {
// Fallback on earlier versions
layoutGuide = view.layoutMarginsGuide
}
tableView.translatesAutoresizingMaskIntoConstraints = false
tableView.leadingAnchor.constraint(equalTo: layoutGuide.leadingAnchor).isActive = true
tableView.topAnchor.constraint(equalTo: layoutGuide.topAnchor).isActive = true
tableView.trailingAnchor.constraint(equalTo: layoutGuide.trailingAnchor).isActive = true
tableView.bottomAnchor.constraint(equalTo: layoutGuide.bottomAnchor).isActive = true
tableView.delegate = self
tableView.dataSource = self
tableView.reloadData()
refreshControl = UIRefreshControl()
if #available(iOS 10.0, *) {
tableView.refreshControl = refreshControl
} else {
// Fallback on earlier versions
tableView.addSubview(refreshControl)
}
refreshControl.addTarget(self, action: #selector(handleRefresh), for: .valueChanged)
seeNewPostsButton = SeeNewPostsButton()
view.addSubview(seeNewPostsButton)
seeNewPostsButton.translatesAutoresizingMaskIntoConstraints = false
seeNewPostsButton.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
seeNewPostsButtonTopAnchor = seeNewPostsButton.topAnchor.constraint(equalTo: layoutGuide.topAnchor, constant: -44)
seeNewPostsButtonTopAnchor.isActive = true
seeNewPostsButton.heightAnchor.constraint(equalToConstant: 32.0).isActive = true
seeNewPostsButton.widthAnchor.constraint(equalToConstant: seeNewPostsButton.button.bounds.width).isActive = true
seeNewPostsButton.button.addTarget(self, action: #selector(handleRefresh), for: .touchUpInside)
//observePosts()
beginBatchFetch()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
listenForNewPosts()
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
stopListeningForNewPosts()
}
func toggleSeeNewPostsButton(hidden:Bool) {
if hidden {
// hide it
UIView.animate(withDuration: 0.5, delay: 0.0, usingSpringWithDamping: 0.5, initialSpringVelocity: 0.5, options: .curveEaseOut, animations: {
self.seeNewPostsButtonTopAnchor.constant = -44.0
self.view.layoutIfNeeded()
}, completion: nil)
} else {
// show it
UIView.animate(withDuration: 0.5, delay: 0.0, usingSpringWithDamping: 0.5, initialSpringVelocity: 0.5, options: .curveEaseOut, animations: {
self.seeNewPostsButtonTopAnchor.constant = 12
self.view.layoutIfNeeded()
}, completion: nil)
}
}
#objc func handleRefresh() {
print("Refresh!")
toggleSeeNewPostsButton(hidden: true)
newPostsQuery.queryLimited(toFirst: 20).observeSingleEvent(of: .value, with: { snapshot in
var tempPosts = [Post]()
let firstPost = self.posts.first
for child in snapshot.children {
if let childSnapshot = child as? DataSnapshot,
let data = childSnapshot.value as? [String:Any],
let post = Post.parse(childSnapshot.key, data),
childSnapshot.key != firstPost?.id {
tempPosts.insert(post, at: 0)
}
}
self.posts.insert(contentsOf: tempPosts, at: 0)
let newIndexPaths = (0..<tempPosts.count).map { i in
return IndexPath(row: i, section: 0)
}
self.refreshControl.endRefreshing()
self.tableView.insertRows(at: newIndexPaths, with: .top)
self.tableView.scrollToRow(at: IndexPath(row: 0, section: 0), at: .top, animated: true)
self.listenForNewPosts()
})
}
func fetchPosts(completion:#escaping (_ posts:[Post])->()) {
oldPostsQuery.queryLimited(toLast: 20).observeSingleEvent(of: .value, with: { snapshot in
var tempPosts = [Post]()
let lastPost = self.posts.last
for child in snapshot.children {
if let childSnapshot = child as? DataSnapshot,
let data = childSnapshot.value as? [String:Any],
let post = Post.parse(childSnapshot.key, data),
childSnapshot.key != lastPost?.id {
tempPosts.insert(post, at: 0)
}
}
return completion(tempPosts)
})
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let offsetY = scrollView.contentOffset.y
let contentHeight = scrollView.contentSize.height
if offsetY > contentHeight - scrollView.frame.size.height * leadingScreensForBatching {
if !fetchingMore && !endReached {
beginBatchFetch()
}
}
}
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
cellHeights[indexPath] = cell.frame.size.height
}
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
return cellHeights[indexPath] ?? 72.0
}
func beginBatchFetch() {
fetchingMore = true
self.tableView.reloadSections(IndexSet(integer: 1), with: .fade)
fetchPosts { newPosts in
self.posts.append(contentsOf: newPosts)
self.fetchingMore = false
self.endReached = newPosts.count == 0
UIView.performWithoutAnimation {
self.tableView.reloadData()
self.listenForNewPosts()
}
}
}
var postListenerHandle:UInt?
func listenForNewPosts() {
guard !fetchingMore else { return }
// Avoiding duplicate listeners
stopListeningForNewPosts()
postListenerHandle = newPostsQuery.observe(.childAdded, with: { snapshot in
if snapshot.key != self.posts.first?.id,
let data = snapshot.value as? [String:Any],
let post = Post.parse(snapshot.key, data) {
self.stopListeningForNewPosts()
if snapshot.key == self.lastUploadedPostID {
self.handleRefresh()
self.lastUploadedPostID = nil
} else {
self.toggleSeeNewPostsButton(hidden: false)
}
}
})
}
func stopListeningForNewPosts() {
if let handle = postListenerHandle {
newPostsQuery.removeObserver(withHandle: handle)
postListenerHandle = nil
}
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let newPostNavBar = segue.destination as? UINavigationController,
let newPostVC = newPostNavBar.viewControllers[0] as? NewPostViewController {
newPostVC.delegate = self
}
}
}
extension HomeController: NewPostVCDelegate {
func didUploadPost(withID id: String) {
self.lastUploadedPostID = id
}
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// Get the new view controller using segue.destination.
// Pass the selected object to the new view controller.
}
*/
}
I try to make a like button and unlike but I it gives me error when I try to press the button in the simulator .if you know any other code for the like button to be much easier will be helpful ( or some websites , yt vids)
On this line
ref.child("posts").child(self.postID)
the postID is undefined which is causing the crash. In the comments you state you assign a value to it, but the code in the question doesn't include how that's being done so there's a high likelyhood that value is not being assigned.
The fix would be to assign a value to that class var before that line, like this
#IBAction func likePressed(_ sender: Any) {
self.postID = "post_0" //or however you determine which post it is
// e.g. self.postID = getCurrentPostId()
let ref = Database.database().reference()
let keyToPost = ref.child("posts").childByAutoId().key
ref.child("posts").child(self.postID)...
You may also want to implement some basic error checking as well to ensure the postID is not nil before trying to call the Firebase function.
if let postID = getCurrentPostID() {
//perform the firebase function using postID
} else {
//display an error 'not post selected'
}

Collection View cell label overrides label of other cells

I have a UICollectionView where the label (recordingTitle) for each cell is created using the input from a UIAlert. However, at the moment any new input from the alert overrides the labels of all previously created cells in the UICollectionView. How can I have each cell have an individual label?
import UIKit
import AVFoundation
class ViewController: UIViewController, AVAudioRecorderDelegate, UICollectionViewDelegate, UICollectionViewDataSource {
var audioRecorder: AVAudioRecorder?=nil
var audioPlayer: AVAudioPlayer!
var numberOfRecordings = 0
var recordingTitle: String = ""
var fileURL: URL!
#IBOutlet weak var recordButton: UIButton!
#IBOutlet weak var editButton: UIButton!
#IBOutlet weak var myCollectionView: UICollectionView!
override func viewDidLoad() {
super.viewDidLoad()
recordButton.layer.cornerRadius = 10
editButton.layer.cornerRadius = 10
// Set the numberOfRecordings to be exactly the number of files stored in the File Manager so that they're in sync
let fileManager = FileManager.default
let documentsURL = fileManager.urls(for: .documentDirectory, in: .userDomainMask)[0]
do {
let fileURLs = try fileManager.contentsOfDirectory(at: documentsURL, includingPropertiesForKeys: nil, options: .skipsHiddenFiles)
numberOfRecordings = fileURLs.count
} catch {
print("Error while enumerating files \(documentsURL.path): \(error.localizedDescription)")
}
}
// Get the directory where we're going to store the recordings
func getDirectory() -> URL {
let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
let documentDirectory = paths[0]
return documentDirectory
}
// Create a general alert to display error messages
func displayAlert(title: String, message: String) {
let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "Dismiss", style: .default, handler: nil))
present(alert, animated: true, completion: nil)
}
// Create an alert to ask for recording title input
func askForRecordingTitle() {
let alertController = UIAlertController(title: "Recording Title", message: nil, preferredStyle: .alert)
alertController.addTextField(configurationHandler: nil)
let submitAction = UIAlertAction(title: "Save", style: .default) { [weak self] (alertAction) in
guard let `self` = self else { return }
if let userResponse = alertController.textFields?[0].text {
self.recordingTitle = userResponse
let fileURL = self.getDirectory().appendingPathComponent("\(self.recordingTitle).m4a")
self.fileURL = fileURL
let settings = [
AVFormatIDKey: Int(kAudioFormatMPEG4AAC),
AVSampleRateKey: 12000,
AVNumberOfChannelsKey: 1,
AVEncoderAudioQualityKey: AVAudioQuality.high.rawValue
]
do {
let audioRecorder = try AVAudioRecorder(url: fileURL, settings: settings)
audioRecorder.delegate = self
audioRecorder.record()
self.recordButton.setTitle("Stop Recording", for: .normal)
self.audioRecorder = audioRecorder
} catch {
self.displayAlert(title: "Oops!", message: "Recording Failed")
}
} else {
print("Not nil or a value")
}
}
alertController.addAction(submitAction)
present(alertController, animated: true, completion: nil)
}
//MARK: - Record Button Methods
#IBAction func recordAction(_ sender: Any) {
if audioRecorder == nil {
askForRecordingTitle()
numberOfRecordings += 1
} else {
audioRecorder?.stop()
audioRecorder = nil
UserDefaults.standard.set(numberOfRecordings, forKey: "numberOfRecordings")
recordButton.setTitle("Start Recording", for: .normal)
myCollectionView.reloadData()
}
}
//MARK: - Collection View Setup
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return numberOfRecordings
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath) as! RecordingCollectionViewCell
if editButton.titleLabel?.text == "Stop Editing" {
cell.isEditing = true
} else {
cell.isEditing = false
}
cell.recordingLabel.text = recordingTitle
cell.layer.cornerRadius = 10
cell.delegate = self as RecordingCellDelegate
return cell
}
//MARK: - Audio Player
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let path = getDirectory().appendingPathComponent("\(recordingTitle).m4a")
do {
audioPlayer = try AVAudioPlayer(contentsOf: path)
audioPlayer.volume = 1.0
audioPlayer.prepareToPlay()
audioPlayer.play()
} catch let error {
print("Error: \(error.localizedDescription)")
}
}
//MARK: - Edit Button Methods
#IBAction func editButtonTapped(_ sender: Any) {
if editButton.titleLabel?.text == "Edit" {
recordButton.isEnabled = false
editButton.setTitle("Stop Editing", for: .normal)
if let indexPaths = myCollectionView?.indexPathsForVisibleItems {
for indexPath in indexPaths {
if let cell = myCollectionView?.cellForItem(at: indexPath) as? RecordingCollectionViewCell {
cell.isEditing = true
}
}
}
} else {
editButton.setTitle("Edit", for: .normal)
recordButton.isEnabled = true
if let indexPaths = myCollectionView?.indexPathsForVisibleItems {
for indexPath in indexPaths {
if let cell = myCollectionView?.cellForItem(at: indexPath) as? RecordingCollectionViewCell {
cell.isEditing = false
}
}
}
}
}
}
extension ViewController: RecordingCellDelegate {
func delete(cell: RecordingCollectionViewCell) {
if let indexPath = myCollectionView?.indexPath(for: cell) {
// 1. Delete the recording from the File Manager
let fileManager = FileManager.default
fileURL = getDirectory().appendingPathComponent("\(recordingTitle).m4a")
do {
try fileManager.removeItem(at: fileURL)
} catch let error {
print("File not found: \(error.localizedDescription)")
displayAlert(title: "Oops!", message: "File not found: \(error.localizedDescription)")
}
// 2. Update the data model
let documentsURL = fileManager.urls(for: .documentDirectory, in: .userDomainMask)[0]
do {
let fileURLs = try fileManager.contentsOfDirectory(at: documentsURL, includingPropertiesForKeys: nil, options: .skipsHiddenFiles)
numberOfRecordings = fileURLs.count
} catch {
print("Error while enumerating files \(documentsURL.path): \(error.localizedDescription)")
}
// 3. Delete cell in the collectionView
myCollectionView.deleteItems(at: [indexPath])
}
}
}
You are saving your recordingTitle in a single variable, so every time you write to it you override it, you need to store them as an array.
You initialize your variable with:
var recordingTitles: [String] = []
You add your new title with:
recordingTitles.append(userResponse)
and you read it with:
let recordingTitle = recordingTitles[indexPath.item]

Swift Toolbar button triggers a exec_bad_instruction only on one scene

I'm changing my app to start to use toolbar buttons for the main pages instead of a menu. All of the menu links work and the toolbar buttons work on every page except one. When I click on the button the app freezes, and Xcode brings me to this line:
'weak var itemDetailPage = segue.destination as? ItemDetailViewController' with this error:
Thread 1: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)
All the variables are nil.
Any help would be monumental!
Here is the code for the view controller it is specifically happening on:
import UIKit
import AVFoundation
import Alamofire
#objc protocol FavRefreshLikeCountsDelegate: class {
func updateLikeCounts(_ likeCount: Int)
}
#objc protocol FavRefreshReviewCountsDelegate: class {
func updateReviewCounts(_ reviewCount: Int)
}
class FavouriteItemsViewController: UICollectionViewController {
var populationItems = false
var selectedSubCategoryId:Int = 0
var selectedCityId:Int = 1
var selectedCityLat: String!
var selectedCityLng: String!
var items = [ItemModel]()
var currentPage = 0
var loginUserId:Int = 0
weak var selectedCell : AnnotatedPhotoCell!
#IBOutlet weak var menuButton: UIBarButtonItem!
var defaultValue: CGPoint!
override func viewDidLoad() {
if self.revealViewController() != nil {
menuButton.target = self.revealViewController()
menuButton.action = #selector(SWRevealViewController.revealToggle(_:))
self.view.addGestureRecognizer(self.revealViewController().panGestureRecognizer())
}
collectionView!.register(AnnotatedPhotoCell.classForCoder(), forSupplementaryViewOfKind: UICollectionElementKindSectionFooter, withReuseIdentifier: "AnnotatedPhotoCell")
if let layout = collectionView?.collectionViewLayout as? PinterestLayout {
layout.delegate = self
}
loadLoginUserId()
loadFavouriteItems()
defaultValue = collectionView?.frame.origin
animateCollectionView()
}
override func viewDidAppear(_ animated: Bool) {
updateNavigationStuff()
}
override var preferredStatusBarStyle : UIStatusBarStyle {
return UIStatusBarStyle.lightContent
}
override func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
loadFavouriteItems()
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
weak var itemCell = sender as? AnnotatedPhotoCell
weak var itemDetailPage = segue.destination as? ItemDetailViewController
itemDetailPage!.selectedItemId = Int((itemCell!.item?.itemId)!)!
itemDetailPage!.selectedShopId = Int((itemCell!.item?.itemShopId)!)!
itemDetailPage!.favRefreshLikeCountsDelegate = self
itemDetailPage!.favRefreshReviewCountsDelegate = self
selectedCell = itemCell
updateBackButton()
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return items.count
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
var cell : AnnotatedPhotoCell
cell = collectionView.dequeueReusableCell(withReuseIdentifier: "AnnotatedPhotoCell", for: indexPath) as! AnnotatedPhotoCell
cell.item = items[(indexPath as NSIndexPath).item]
let imageURL = configs.imageUrl + items[(indexPath as NSIndexPath).item].itemImage
cell.imageView?.loadImage(urlString: imageURL) { (status, url, image, msg) in
if(status == STATUS.success) {
print(url + " is loaded successfully.")
self.items[indexPath.item].itemImageBlob = image
}else {
print("Error in loading image" + msg)
}
}
cell.imageView.alpha = 0
cell.captionLabel.alpha = 0
UIView.animate(withDuration: 1, delay: 0, options: UIViewAnimationOptions.curveEaseIn, animations: {
cell.imageView.alpha = 1.0
cell.captionLabel.alpha = 1.0
}, completion: nil)
return cell
}
func updateBackButton() {
let backItem = UIBarButtonItem()
backItem.title = ""
navigationItem.backBarButtonItem = backItem
}
func updateNavigationStuff() {
self.navigationController?.navigationBar.topItem?.title = language.favouritePageTitle
self.navigationController?.navigationBar.titleTextAttributes = [ NSAttributedStringKey.font: UIFont(name: customFont.boldFontName, size: CGFloat(customFont.boldFontSize))!, NSAttributedStringKey.foregroundColor:UIColor.white]
self.navigationController!.navigationBar.barTintColor = Common.instance.colorWithHexString(configs.barColorCode)
}
func loadLoginUserId() {
let plistPath = Common.instance.getLoginUserInfoPlist()
let myDict = NSDictionary(contentsOfFile: plistPath)
if let dict = myDict {
loginUserId = Int(dict.object(forKey: "_login_user_id") as! String)!
print("Login User Id : " + String(loginUserId))
} else {
print("WARNING: Couldn't create dictionary from LoginUserInfo.plist! Default values will be used!")
}
}
func loadFavouriteItems() {
if self.currentPage == 0 {
_ = EZLoadingActivity.show("Loading...", disableUI: true)
}
Alamofire.request(APIRouters.GetFavouriteItems( loginUserId, configs.pageSize, self.currentPage)).responseCollection {
(response: DataResponse<[Item]>) in
if self.currentPage == 0 {
_ = EZLoadingActivity.hide()
}
if response.result.isSuccess {
if let items: [Item] = response.result.value {
if(items.count > 0) {
for item in items {
let oneItem = ItemModel(item: item)
self.items.append(oneItem)
self.currentPage+=1
}
}
}
self.collectionView!.reloadData()
} else {
print(response)
}
}
}
func animateCollectionView() {
moveOffScreen()
UIView.animate(withDuration: 1, delay: 0,
usingSpringWithDamping: 0.9,
initialSpringVelocity: 0.9, options: UIViewAnimationOptions.curveEaseOut, animations: {
self.collectionView?.frame.origin = self.defaultValue
}, completion: nil)
}
fileprivate func moveOffScreen() {
collectionView?.frame.origin = CGPoint(x: (collectionView?.frame.origin.x)!,
y: (collectionView?.frame.origin.y)! + UIScreen.main.bounds.size.height)
}
}
extension FavouriteItemsViewController : PinterestLayoutDelegate {
func collectionView(_ collectionView:UICollectionView, heightForPhotoAtIndexPath indexPath:IndexPath , withWidth width:CGFloat) -> CGFloat {
let item = items[(indexPath as NSIndexPath).item]
let boundingRect = CGRect(x: 0, y: 0, width: width, height: CGFloat(MAXFLOAT))
let size = CGSize(width: item.itemImageWidth, height: item.itemImageHeight)
var rect : CGRect
if item.itemImageBlob != nil {
rect = AVMakeRect(aspectRatio: item.itemImageBlob!.size, insideRect: boundingRect)
}else{
rect = AVMakeRect(aspectRatio: size, insideRect: boundingRect)
}
return rect.size.height
}
func collectionView(_ collectionView: UICollectionView, heightForAnnotationAtIndexPath indexPath: IndexPath, withWidth width: CGFloat) -> CGFloat {
let annotationPadding = CGFloat(4)
let annotationHeaderHeight = CGFloat(17)
let height = annotationPadding + annotationHeaderHeight + annotationPadding + 30
return height
}
}
extension FavouriteItemsViewController : FavRefreshLikeCountsDelegate, FavRefreshReviewCountsDelegate {
func updateLikeCounts(_ likeCount: Int) {
if selectedCell != nil {
selectedCell.lblLikeCount.text = "\(likeCount)"
}
}
func updateReviewCounts(_ reviewCount: Int) {
if selectedCell != nil {
selectedCell.lblReviewCount.text = "\(reviewCount)"
}
}
}
You should try be checking your ItemDetailViewController.
for example:
if segue.identifier == "ItemDetailVC" {
weak var itemCell = sender as? AnnotatedPhotoCell
weak var itemDetailPage = segue.destination as? ItemDetailViewController
...
}

Parse: loadObjects/refreshing the tableview after a pull to refresh

Swift
I'm trying to make it so when you pull to refresh on the tableview it updates the tableview with the data that is stored on Parse.com
I've researched this and it seems I need to use loadObjects()
When I place this in it says: "Use of unresolved identifier 'loadObjects'
Not sure if this is what I need or not..
Here is my ViewController.swift
import UIKit
import Parse
import Bolts
import ParseUI
class EventsViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
var timer: NSTimer!
var isAnimating = false
var currentColorIndex = 0
var currentLabelIndex = 0
var customView: UIView!
var labelsArray: Array<UILabel> = []
var refreshControl: UIRefreshControl!
var testArray = [String]()
var subArray = [String]()
#IBOutlet weak var tableview: UITableView!
#IBOutlet weak var webView: UIWebView!
override func viewDidLoad() {
super.viewDidLoad()
tableview.delegate = self
tableview.dataSource = self
refreshControl = UIRefreshControl()
tableview.addSubview(refreshControl)
loadCustomRefreshContents()
//refreshControl colors
refreshControl.backgroundColor = UIColor.clearColor() //color of background
refreshControl.tintColor = UIColor.clearColor() //color of indicator
let query = PFQuery(className: "events")
let runkey = query.orderByDescending("eventTitle")
runkey.findObjectsInBackgroundWithBlock {
(objects: [PFObject]?, error:NSError?) -> Void in
if error == nil {
if let objects = objects as [PFObject]! {
for object in objects {
let load = object.objectForKey("eventTitle") as! String
self.testArray.append(load)
let subload = object.objectForKey("date") as! String
self.subArray.append(subload)
print(self.testArray)
}
}
} else {
print("error:\(error!) \(error!.userInfo)")
}
}
sleep(3)
do_table_refresh()
loadViewIfNeeded()
loadObjects()
/*let url = NSURL(string: "https://m.facebook.com/CathedralCityMunicipal")
let task = NSURLSession.sharedSession().dataTaskWithURL(url!) {
(data, response, error) in
if error == nil {
let urlContent = NSString(data: data!, encoding: NSUTF8StringEncoding)
print(urlContent)
dispatch_async(dispatch_get_main_queue()) {
self.webView.loadHTMLString(urlContent! as String, baseURL: nil)
}
}
}
task.resume()*/
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func do_table_refresh() {
dispatch_async(dispatch_get_main_queue()) {
self.tableview.reloadData()
return
}
}
func tableView(tableView:UITableView!, numberOfRowsInSection section:Int) -> Int {
return testArray.count
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("eventcell", forIndexPath: indexPath) as! EventTableViewCell
cell.title.text = self.testArray[indexPath.row]
cell.subTitle.text = self.subArray[indexPath.row]
return cell
}
//refreshes tableview; starts refresh
func loadCustomRefreshContents() {
let refreshContents = NSBundle.mainBundle().loadNibNamed("RefreshControl", owner: self, options: nil)
customView = refreshContents[0] as! UIView
customView.frame = refreshControl.bounds
for var i=0; i<customView.subviews.count; ++i {
labelsArray.append(customView.viewWithTag(i + 1) as! UILabel)
}
refreshControl.addSubview(customView)
do_table_refresh()
}
//stops refresh
func scrollViewDidEndDecelerating(scrollView: UIScrollView) {
do_table_refresh()
if refreshControl.refreshing {
if !isAnimating {
doSomething()
animateRefreshStep1()
self.do_table_refresh()
}
}
}
//cycles through colors
func getNextColor() -> UIColor {
var colorsArray: Array<UIColor> = [UIColor.magentaColor(), UIColor.brownColor(), UIColor.yellowColor(), UIColor.redColor(), UIColor.greenColor(), UIColor.blueColor(), UIColor.orangeColor()]
if currentColorIndex == colorsArray.count {
currentColorIndex = 0
}
let returnColor = colorsArray[currentColorIndex]
++currentColorIndex
return returnColor
}
func doSomething() {
timer = NSTimer.scheduledTimerWithTimeInterval(4.0, target: self, selector: "endOfWork", userInfo: nil, repeats: true)
self.do_table_refresh()
}
func endOfWork() {
refreshControl.endRefreshing()
self.do_table_refresh()
timer.invalidate()
timer = nil
}
//first part of animation
func animateRefreshStep1() {
isAnimating = true
UIView.animateWithDuration(0.1, delay: 0.0, options: UIViewAnimationOptions.CurveLinear, animations: { () -> Void in
self.labelsArray[self.currentLabelIndex].transform = CGAffineTransformMakeRotation(CGFloat(M_PI_4))
self.labelsArray[self.currentLabelIndex].textColor = self.getNextColor()
}, completion: { (finished) -> Void in
UIView.animateWithDuration(0.05, delay: 0.0, options: UIViewAnimationOptions.CurveLinear, animations: { () -> Void in
self.labelsArray[self.currentLabelIndex].transform = CGAffineTransformIdentity
self.labelsArray[self.currentLabelIndex].textColor = UIColor.blackColor()
}, completion: { (finished) -> Void in
++self.currentLabelIndex
if self.currentLabelIndex < self.labelsArray.count {
self.animateRefreshStep1()
}
else {
self.animateRefreshStep2()
}
})
})
}
//second part of animation
func animateRefreshStep2() {
UIView.animateWithDuration(0.35, delay: 0.0, options: UIViewAnimationOptions.CurveLinear, animations: { () -> Void in
self.labelsArray[0].transform = CGAffineTransformMakeScale(1.5, 1.5)
self.labelsArray[1].transform = CGAffineTransformMakeScale(1.5, 1.5)
self.labelsArray[2].transform = CGAffineTransformMakeScale(1.5, 1.5)
self.labelsArray[3].transform = CGAffineTransformMakeScale(1.5, 1.5)
self.labelsArray[4].transform = CGAffineTransformMakeScale(1.5, 1.5)
self.labelsArray[5].transform = CGAffineTransformMakeScale(1.5, 1.5)
self.labelsArray[6].transform = CGAffineTransformMakeScale(1.5, 1.5)
self.labelsArray[7].transform = CGAffineTransformMakeScale(1.5, 1.5)
self.labelsArray[8].transform = CGAffineTransformMakeScale(1.5, 1.5)
self.labelsArray[9].transform = CGAffineTransformMakeScale(1.5, 1.5)
}, completion: { (finished) -> Void in
UIView.animateWithDuration(0.25, delay: 0.0, options: UIViewAnimationOptions.CurveLinear, animations: { () -> Void in
self.labelsArray[0].transform = CGAffineTransformIdentity
self.labelsArray[1].transform = CGAffineTransformIdentity
self.labelsArray[2].transform = CGAffineTransformIdentity
self.labelsArray[3].transform = CGAffineTransformIdentity
self.labelsArray[4].transform = CGAffineTransformIdentity
self.labelsArray[5].transform = CGAffineTransformIdentity
self.labelsArray[6].transform = CGAffineTransformIdentity
self.labelsArray[7].transform = CGAffineTransformIdentity
self.labelsArray[8].transform = CGAffineTransformIdentity
self.labelsArray[9].transform = CGAffineTransformIdentity
}, completion: { (finished) -> Void in
if self.refreshControl.refreshing {
self.currentLabelIndex = 0
self.animateRefreshStep1()
}
else {
self.isAnimating = false
self.currentLabelIndex = 0
for var i=0; i<self.labelsArray.count; ++i {
self.labelsArray[i].textColor = UIColor.blackColor()
self.labelsArray[i].transform = CGAffineTransformIdentity
}
}
})
})
}
/*
// 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.
}
*/
}
I have tried letting the do_table_refresh() function run after the refresh is over, but the tableview does not update. I have also tried using self.tableview.reloadData() but also does not work.
I'm not sure what I am doing wrong, any help is greatly appreciated!
Feel free to ask me for any additional information!
Thank you for your time!
So, if you are trying to get the Parse data everytime you to pull-to-refresh you should query for them always.
Keep your query into a single function and call a tableView.reloadData() so it will reload the tableView every time you query your events:
func loadEvents () -> Void {
self.testArray = []
self.subArray = []
let query = PFQuery(className: "events")
let runkey = query.orderByDescending("eventTitle")
runkey.findObjectsInBackgroundWithBlock {
(objects: [PFObject]?, error:NSError?) -> Void in
if error == nil {
if let objects = objects as [PFObject]! {
for object in objects {
let load = object.objectForKey("eventTitle") as! String
self.testArray.append(load)
let subload = object.objectForKey("date") as! String
self.subArray.append(subload)
//reload TableView
self.tableView.reloadData()
print(self.testArray)
}
}
} else {
print("error:\(error!) \(error!.userInfo)")
}
}
refreshControl.endRefreshing()
}
And then add a Target for your UIRefreshControl inside your ViewDidLoad()
refreshControl.addTarget(self, action: Selector("loadEvents"), forControlEvents: UIControlEvents.ValueChanged)
self.tableView.addSubview(refreshControl)
Also, you must declare your UIRefreshControl as follows:
var refreshControl = UIRefreshControl()