Im trying to transfer an image between two view controllers. It is a profile image of the current logged in User, if there is one. The image is successful in downloading from Firebase. It downloads on a launch screen along with the username. If there is no user logged in, view controller changes to a login screen, if there is a logged in user it transfers to a home screen.
This is the launch screen. Both the username label and the image view are clear so user cannot see.
import UIKit
import Firebase
class LaunchScreen: UIViewController {
#IBOutlet weak var usernameLabel: UILabel!
#IBOutlet weak var userImage: UIImageView!
var ref : DatabaseReference!
var storage: StorageReference!
override func viewDidLoad() {
super.viewDidLoad()
ref = Database.database().reference()
storage = Storage.storage().reference()
checkUser()
// Do any additional setup after loading the view.
}
func checkUser() {
Auth.auth().addStateDidChangeListener { (auth, user) in
if user != nil {
let uid = Auth.auth().currentUser?.uid
self.ref.database.reference().child("users/profile").child(uid!).observe(.value, with: { (snapshot: DataSnapshot) in
if let dictionary = snapshot.value as? [String: AnyObject] {
let currentUser = dictionary["username"] as! String
let url = dictionary["photoURL"] as! String
let storageRef = self.storage.storage.reference(forURL: url)
storageRef.getData(maxSize: 1 * 1024 * 1024, completion: { (data, error) in
if let error = error {
print(error)
} else {
self.userImage.image = UIImage(data: data!)
self.reloadInputViews()
}
})
guard let image = self.userImage.image else {return}
self.usernameLabel.text = currentUser
let imageJPG = image.jpegData(compressionQuality: 1)
print(imageJPG?.count)
let defaults = UserDefaults.standard
defaults.set(self.usernameLabel.text, forKey: "user")
defaults.synchronize()
}
})
_ = Timer.scheduledTimer(withTimeInterval: 3.0, repeats: false, block: { (timer) in
let vc = self.storyboard?.instantiateViewController(withIdentifier: "homeVC")
self.present(vc!, animated: true, completion: nil)
})
print("loggedIn")
} else {
_ = Timer.scheduledTimer(withTimeInterval: 3.0, repeats: false, block: { (timer) in
let loginvc = self.storyboard?.instantiateViewController(withIdentifier: "loginVC")
self.present( loginvc!,animated: true, completion: nil)
})
print("loggedOut")
}
}
}
}
This is my home page. where there is a side menu. the side menu pops open from the left hand side of the screen and on it are four collection view cells. the top one is the profile cell which will display the username and the user profile image. that image is where I want the downloaded Firebase userImage in launchScreenVC to be transferred too.
class home VC: UIViewController {
#IBOutlet weak var profileMenuButton: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
collectionView.delegate = self
collectionView.dataSource = self
collectionView.reloadData()
}
let blackView = UIView()
let collectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
let cv = UICollectionView(frame: .zero , collectionViewLayout: layout )
cv.backgroundColor = UIColor.init(displayP3Red: 50/255, green: 51/255, blue: 51/255, alpha: 1)
return cv
}()
#IBAction func profileMenu() {
let width = view.frame.width - 60
collectionView.frame = CGRect(x: 0, y: 0, width: width, height: view.frame.height)
blackView.backgroundColor = UIColor(white: 0, alpha: 0.5)
blackView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(handleDismiss)))
blackView.frame = view.frame
blackView.alpha = 0
view.addSubview(blackView)
view.addSubview(collectionView)
UIView.animate(withDuration: 1.5, delay: 0, options: .curveEaseOut, animations: {
self.blackView.alpha = 1
self.collectionView.frame = CGRect(x: 0 , y: 0, width: width, height: self.view.frame.height)
}, completion: nil)
handleSearchDismiss(searchView)
}
#objc func handleDismiss() {
UIView.animate(withDuration: 0.5){
if let window = UIApplication.shared.keyWindow {
self.collectionView.frame = CGRect(x: 0 - (window.frame.width), y: 0, width: window.frame.width, height: self.collectionView.frame.height)
}
self.blackView.alpha = 0
}
}
}
extension homeVC: UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout, {
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if indexPath.item == 0 {
let cell1 = collectionView.dequeueReusableCell(withReuseIdentifier: "ProfileCellID", for: indexPath) as! profileCell
let defaults = UserDefaults.standard
cell1.usernameLabel.text = (defaults.object(forKey: "user") as! String)
cell1.usernameLabel.textColor = UIColor.lightGray
cell1.usernameLabel.font = UIFont.boldSystemFont(ofSize: 25)
// print(imageData)
// cell1.profileImage.image = (defaults.object(forKey: "userImage") as! Data)
return cell1
} else if indexPath.item == 1{
let cell2 = collectionView.dequeueReusableCell(withReuseIdentifier: "Settings", for: indexPath) as! settingsCell
return cell2
} else if indexPath.item == 2 {
let cell3 = collectionView.dequeueReusableCell(withReuseIdentifier: "Favourites", for: indexPath) as! favouritesCell
return cell3
} else {
let cell4 = collectionView.dequeueReusableCell(withReuseIdentifier: "Logout", for: indexPath) as! logoutCell
return cell4
}
}
}
this is the profile cell class
class profileCell: UICollectionViewCell {
override init(frame: CGRect) {
super.init(frame: frame)
setUpViews()
}
let profileImage: UIImageView = {
let imageView = UIImageView()
return imageView
}()
let usernameLabel: UILabel = {
let label = UILabel()
return label
}()
func setUpViews() {
addSubview(profileImage)
addSubview(usernameLabel)
profileImage.frame = CGRect(x: (center.x)-60, y: 30, width: 120, height: 120)
profileImage.layer.cornerRadius = (profileImage.frame.width/2)
usernameLabel.frame = CGRect(x: (center.x)-60, y: 125, width: 120, height: 120)
usernameLabel.textAlignment = NSTextAlignment.center
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Any Help or tips would be greatly appreciated! Thanks Guys!
Related
This is audio player, It worked fine before I started to try stream audios from firebase. Now data is downloaded from the firebase database, the image, title and subtitle are being displayed correctly, but when user taps on the cell the audio doesn't play and it throws a fatal error, Thread 1: Fatal error: Index out of range
This is a table view controller which lists the audio tracks from firebase database.
import UIKit
import AVKit
import AVFoundation
import FirebaseFirestore
import Combine
import SDWebImage
class ListOfAudioLessonsTableViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet var table: UITableView!
let placeHolderImage = UIImage(named: "placeHolderImage")
private var cancellable: AnyCancellable?
private var viewModel = AudiosViewModel()
var paragraphs = [Audio]()
override func viewDidLoad() {
super.viewDidLoad()
self.viewModel.fetchData()
self.title = "Audio Lessons"
table.delegate = self
table.dataSource = self
cancellable = viewModel.$audios.sink { _ in
DispatchQueue.main.async{
self.table.reloadData()
}
} // Do any additional setup after loading the view.
}
// Table
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
print("audios count = ", viewModel.audios.count)
return viewModel.audios.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
let audio = viewModel.audios[indexPath.row]
tableView.tableFooterView = UIView()
cell.textLabel?.text = audio.albumName
cell.detailTextLabel?.text = audio.name
cell.accessoryType = .disclosureIndicator
let imageURL = audio.audioImageName
cell.imageView?.sd_imageIndicator = SDWebImageActivityIndicator.gray
cell.imageView?.sd_setImage(with: URL(string: imageURL),
placeholderImage: placeHolderImage,
options: SDWebImageOptions.highPriority,
context: nil,
progress: nil,
completed: { downloadedImage, downloadException, cacheType, downloadURL in
if let downloadException = downloadException {
print("error downloading the image: \(downloadException.localizedDescription)")
} else {
print("successfuly downloaded the image: \(String(describing: downloadURL?.absoluteString))")
}
})
cell.textLabel?.numberOfLines = 0
cell.detailTextLabel?.numberOfLines = 0
let backgroundView = UIView()
backgroundView.backgroundColor = UIColor(named: "AudioLessonsCellHighlighted")
cell.selectedBackgroundView = backgroundView
cell.textLabel?.font = UIFont(name: "Helvetica-Bold", size: 14)
cell.detailTextLabel?.font = UIFont(name: "Helvetica", size: 12)
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 100
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
// present the player
let position = indexPath.row
//lessons
guard let vc = storyboard?.instantiateViewController(identifier: "AudioPlayer") as? AudioPlayerViewController else {
return
}
vc.paragraphs = paragraphs
vc.position = position
present(vc, animated: true)
}
func tableView(_ tableView: UITableView, didHighlightRowAt indexPath: IndexPath) {
if let cell = tableView.cellForRow(at: indexPath) {
cell.contentView.backgroundColor = UIColor(named: "AudioLessonsHighlighted")
cell.textLabel?.highlightedTextColor = UIColor(named: "textHighlighted")
cell.detailTextLabel?.highlightedTextColor = UIColor(named: "textHighlighted")
}
}
func tableView(_ tableView: UITableView, didUnhighlightRowAt indexPath: IndexPath) {
if let cell = tableView.cellForRow(at: indexPath) {
cell.contentView.backgroundColor = nil
}
}
}
import UIKit
import AVFoundation
import MediaPlayer
import AVKit
class AudioPlayerViewController: UIViewController {
public var position: Int = 0
public var paragraphs: [Audio] = []
#IBOutlet var holder: UIView!
var player: AVPlayer?
var playerItem: AVPlayerItem?
var isSeekInProgress = false
var chaseTime = CMTime.zero
fileprivate let seekDuration: Float64 = 15
var playerCurrentItemStatus: AVPlayerItem.Status = .unknown
// User Interface elements
private let albumImageView: UIImageView = {
let imageView = UIImageView()
imageView.contentMode = .scaleAspectFill
return imageView
}()
private let paragraphNumberLabel: UILabel = {
let label = UILabel()
label.textAlignment = .left
label.font = .systemFont(ofSize: 16, weight: .light)
label.numberOfLines = 0 // allow line wrap
label.textColor = UIColor(named: "PlayerColors")
return label
}()
private let albumNameLabel: UILabel = {
let label = UILabel()
label.textAlignment = .left
label.font = .systemFont(ofSize: 18, weight: .bold)
label.numberOfLines = 0 // allow line wrap
label.textColor = UIColor(named: "PlayerColors")
return label
}()
private let songNameLabel: UILabel = {
let label = UILabel()
label.textAlignment = .left
label.font = .systemFont(ofSize: 16, weight: .ultraLight)
label.numberOfLines = 0 // allow line wrap
label.textColor = UIColor(named: "PlayerColors")
return label
}()
private let elapsedTimeLabel: UILabel = {
let label = UILabel()
label.textAlignment = .left
label.font = .systemFont(ofSize: 12, weight: .light)
label.textColor = UIColor(named: "PlayerColors")
label.text = "00:00"
label.numberOfLines = 0
return label
}()
private let remainingTimeLabel: UILabel = {
let label = UILabel()
label.textAlignment = .left
label.font = .systemFont(ofSize: 12, weight: .light)
label.textColor = UIColor(named: "PlayerColors")
label.text = "00:00"
label.numberOfLines = 0
return label
}()
private let playbackSlider: UISlider = {
let v = UISlider()
v.addTarget(AudioPlayerViewController.self, action: #selector(progressScrubbed(_:)), for: .valueChanged)
v.minimumTrackTintColor = UIColor.lightGray
v.maximumTrackTintColor = UIColor.darkGray
v.thumbTintColor = UIColor(named: "PlayerColors")
v.minimumValue = 0
v.isContinuous = true
return v
}()
let playPauseButton = UIButton()
override func viewDidLoad() {
super.viewDidLoad()
let panGesture = UIPanGestureRecognizer(target: self, action: #selector(panGesture(gesture:)))
self.playbackSlider.addGestureRecognizer(panGesture)
let audioSession = AVAudioSession.sharedInstance()
do {
try audioSession.setCategory(AVAudioSession.Category.playback)
}
catch{
print(error)
}
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
if holder.subviews.count == 0 {
configure()
}
}
func configure() {
// set up player
let song = paragraphs[position]
let url = URL(string: song.trackURL)
let playerItem: AVPlayerItem = AVPlayerItem(url: url!)
do {
try AVAudioSession.sharedInstance().setMode(.default)
try AVAudioSession.sharedInstance().setActive(true, options: .notifyOthersOnDeactivation)
guard url != nil else {
print("urls string is nil")
return
}
player = AVPlayer(playerItem: playerItem)
let duration : CMTime = playerItem.asset.duration
let seconds : Float64 = CMTimeGetSeconds(duration)
remainingTimeLabel.text = self.stringFromTimeInterval(interval: seconds)
let currentDuration : CMTime = playerItem.currentTime()
let currentSeconds : Float64 = CMTimeGetSeconds(currentDuration)
elapsedTimeLabel.text = self.stringFromTimeInterval(interval: currentSeconds)
playbackSlider.maximumValue = Float(seconds)
player!.addPeriodicTimeObserver(forInterval: CMTimeMakeWithSeconds(1, preferredTimescale: 1), queue: DispatchQueue.main) { (CMTime) -> Void in
if self.player!.currentItem?.status == .readyToPlay {
let time : Float64 = CMTimeGetSeconds(self.player!.currentTime());
self.playbackSlider.value = Float(time)
self.elapsedTimeLabel.text = self.stringFromTimeInterval(interval: time)
}
let playbackLikelyToKeepUp = self.player?.currentItem?.isPlaybackLikelyToKeepUp
if playbackLikelyToKeepUp == false{
print("IsBuffering")
self.playPauseButton.isHidden = true
} else {
// stop the activity indicator
print("Buffering completed")
self.playPauseButton.isHidden = false
}
}
playbackSlider.addTarget(self, action: #selector(AudioPlayerViewController.progressScrubbed(_:)), for: .valueChanged)
self.view.addSubview(playbackSlider)
//subroutine used to keep track of current location of time in audio file
guard let player = player else {
print("player is nil")
return
}
player.play()
}
catch {
print("error accured")
}
// set up user interface elements
//album cover
albumImageView.frame = CGRect(x: 20,
y: 20,
width: holder.frame.size.width - 40,
height: holder.frame.size.width - 40)
albumImageView.image = UIImage(named: song.audioImageName)
holder.addSubview(albumImageView)
//Labels Song name, album, artist
albumNameLabel.frame = CGRect(x: 20,
y: holder.frame.size.height - 300,
width: holder.frame.size.width - 40,
height: 20)
paragraphNumberLabel.frame = CGRect(x: 20,
y: holder.frame.size.height - 280,
width: holder.frame.size.width-40,
height: 20)
songNameLabel.frame = CGRect(x: 20,
y: holder.frame.size.height - 260,
width: holder.frame.size.width-40,
height: 20)
playbackSlider.frame = CGRect(x: 20,
y: holder.frame.size.height - 235,
width: holder.frame.size.width-40,
height: 40)
elapsedTimeLabel.frame = CGRect(x: 25,
y: holder.frame.size.height - 200,
width: holder.frame.size.width-40,
height: 15)
remainingTimeLabel.frame = CGRect(x: holder.frame.size.width-60,
y: holder.frame.size.height - 200,
width: holder.frame.size.width-20,
height: 15)
songNameLabel.text = song.name
albumNameLabel.text = song.albumName
paragraphNumberLabel.text = song.paragraphNumber
holder.addSubview(songNameLabel)
holder.addSubview(albumNameLabel)
holder.addSubview(paragraphNumberLabel)
holder.addSubview(elapsedTimeLabel)
holder.addSubview(remainingTimeLabel)
//Player controls
let nextButton = UIButton()
let backButton = UIButton()
let seekForwardButton = UIButton()
let seekBackwardButton = UIButton()
//frames of buttons
playPauseButton.frame = CGRect(x: (holder.frame.size.width - 40) / 2.0,
y: holder.frame.size.height - 172.5,
width: 40,
height: 40)
nextButton.frame = CGRect(x: holder.frame.size.width - 70,
y: holder.frame.size.height - 162.5,
width: 30,
height: 20)
backButton.frame = CGRect(x: 70 - 30,
y: holder.frame.size.height - 162.5,
width: 30,
height: 20)
seekForwardButton.frame = CGRect(x: holder.frame.size.width - 140,
y: holder.frame.size.height - 167.5,
width: 30,
height: 30)
seekBackwardButton.frame = CGRect(x: 110,
y: holder.frame.size.height - 167.5,
width: 30,
height: 30)
let volumeView = MPVolumeView(frame: CGRect(x: 20,
y: holder.frame.size.height - 80,
width: holder.frame.size.width-40,
height: 30))
holder.addSubview(volumeView)
//actions of buttons
playPauseButton.addTarget(self, action: #selector(didTapPlayPauseButton), for: .touchUpInside)
backButton.addTarget(self, action: #selector(didTapBackButton), for: .touchUpInside)
nextButton.addTarget(self, action: #selector(didTapNextButton), for: .touchUpInside)
seekForwardButton.addTarget(self, action: #selector(seekForwardButtonTapped), for: .touchUpInside)
seekBackwardButton.addTarget(self, action: #selector(seekBackwardButtonTapped), for: .touchUpInside)
//styling of buttons
playPauseButton.setBackgroundImage(UIImage(systemName: "pause.fill"), for: .normal)
nextButton.setBackgroundImage(UIImage(systemName: "forward.fill"), for: .normal)
backButton.setBackgroundImage(UIImage(systemName: "backward.fill"), for: .normal)
seekForwardButton.setBackgroundImage(UIImage(systemName: "goforward.15"), for: .normal)
seekBackwardButton.setBackgroundImage(UIImage(systemName: "gobackward.15"), for: .normal)
playPauseButton.tintColor = UIColor(named: "PlayerColors")
nextButton.tintColor = UIColor(named: "PlayerColors")
backButton.tintColor = UIColor(named: "PlayerColors")
seekForwardButton.tintColor = UIColor(named: "PlayerColors")
seekBackwardButton.tintColor = UIColor(named: "PlayerColors")
holder.addSubview(playPauseButton)
holder.addSubview(nextButton)
holder.addSubview(backButton)
holder.addSubview(seekForwardButton)
holder.addSubview(seekBackwardButton)
}
#objc func panGesture(gesture: UIPanGestureRecognizer) {
let currentPoint = gesture.location(in: playbackSlider)
let percentage = currentPoint.x/playbackSlider.bounds.size.width;
let delta = Float(percentage) * (playbackSlider.maximumValue - playbackSlider.minimumValue)
let value = playbackSlider.minimumValue + delta
playbackSlider.setValue(value, animated: true)
}
#objc func progressScrubbed(_ playbackSlider: UISlider!) {
let seconds : Int64 = Int64(playbackSlider.value)
let targetTime:CMTime = CMTimeMake(value: seconds, timescale: 1)
player!.seek(to: targetTime)
if player!.rate == 0
{
player?.play()
}
}
func setupNowPlaying() {
// Define Now Playing Info
var nowPlayingInfo = [String : Any]()
nowPlayingInfo[MPMediaItemPropertyTitle] = "Unstoppable"
if let image = UIImage(named: "artist") {
nowPlayingInfo[MPMediaItemPropertyArtwork] = MPMediaItemArtwork(boundsSize: image.size) { size in
return image
}
}
nowPlayingInfo[MPNowPlayingInfoPropertyElapsedPlaybackTime] = player?.currentTime
nowPlayingInfo[MPMediaItemPropertyPlaybackDuration] = playerItem?.duration
nowPlayingInfo[MPNowPlayingInfoPropertyPlaybackRate] = player?.rate
// Set the metadata
MPNowPlayingInfoCenter.default().nowPlayingInfo = nowPlayingInfo
}
func updateNowPlaying(isPause: Bool) {
// Define Now Playing Info
var nowPlayingInfo = MPNowPlayingInfoCenter.default().nowPlayingInfo!
nowPlayingInfo[MPNowPlayingInfoPropertyElapsedPlaybackTime] = player?.currentTime
nowPlayingInfo[MPNowPlayingInfoPropertyPlaybackRate] = isPause ? 0 : 1
// Set the metadata
MPNowPlayingInfoCenter.default().nowPlayingInfo = nowPlayingInfo
}
func setupNotifications() {
let notificationCenter = NotificationCenter.default
notificationCenter.addObserver(self,
selector: #selector(handleInterruption),
name: AVAudioSession.interruptionNotification,
object: nil)
notificationCenter.addObserver(self,
selector: #selector(handleRouteChange),
name: AVAudioSession.routeChangeNotification,
object: nil)
}
#objc func handleRouteChange(notification: Notification) {
guard let userInfo = notification.userInfo,
let reasonValue = userInfo[AVAudioSessionRouteChangeReasonKey] as? UInt,
let reason = AVAudioSession.RouteChangeReason(rawValue:reasonValue) else {
return
}
switch reason {
case .newDeviceAvailable:
let session = AVAudioSession.sharedInstance()
for output in session.currentRoute.outputs where output.portType == AVAudioSession.Port.headphones {
print("headphones connected")
DispatchQueue.main.sync {
player?.play()
}
break
}
case .oldDeviceUnavailable:
if let previousRoute =
userInfo[AVAudioSessionRouteChangePreviousRouteKey] as? AVAudioSessionRouteDescription {
for output in previousRoute.outputs where output.portType == AVAudioSession.Port.headphones {
print("headphones disconnected")
DispatchQueue.main.sync {
player?.pause()
}
break
}
}
default: ()
}
}
#objc func handleInterruption(notification: Notification) {
guard let userInfo = notification.userInfo,
let typeValue = userInfo[AVAudioSessionInterruptionTypeKey] as? UInt,
let type = AVAudioSession.InterruptionType(rawValue: typeValue) else {
return
}
if type == .began {
print("Interruption began")
// Interruption began, take appropriate actions
}
else if type == .ended {
if let optionsValue = userInfo[AVAudioSessionInterruptionOptionKey] as? UInt {
let options = AVAudioSession.InterruptionOptions(rawValue: optionsValue)
if options.contains(.shouldResume) {
// Interruption Ended - playback should resume
print("Interruption Ended - playback should resume")
player?.play()
} else {
// Interruption Ended - playback should NOT resume
print("Interruption Ended - playback should NOT resume")
}
}
}
}
#objc func didTapPlayPauseButton() {
if player?.timeControlStatus == .playing {
//pause
player?.pause()
//show play button
playPauseButton.setBackgroundImage(UIImage(systemName: "play.fill"), for: .normal)
//shrink image
UIView.animate(withDuration: 0.2, animations: {
self.albumImageView.frame = CGRect(x: 50,
y: 50,
width: self.holder.frame.size.width - 100,
height: self.holder.frame.size.width - 100)
})
}
else {
//play
player?.play()
//show pause button
playPauseButton.setBackgroundImage(UIImage(systemName: "pause.fill"), for: .normal)
//increase image size
UIView.animate(withDuration: 0.4, animations: {
self.albumImageView.frame = CGRect(x: 20,
y: 20,
width: self.holder.frame.size.width - 40,
height: self.holder.frame.size.width - 40)
})
}
}
private func setupView() {
setupConstraints()
}
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
player?.play()
UIApplication.shared.isIdleTimerDisabled = true
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
player?.pause()
UIApplication.shared.isIdleTimerDisabled = false
}
}
AudioViewModel
import Foundation
import FirebaseFirestore
class AudiosViewModel: ObservableObject {
#Published var audios = [Audio]()
private var db = Firestore.firestore()
func fetchData() {
db.collection("audios").addSnapshotListener { [self] (querySnapshot, error) in
guard let documents = querySnapshot?.documents else {
print("No Documents")
return
}
self.audios = documents.map { (queryDocumentSnapshot) -> Audio in
let data = queryDocumentSnapshot.data()
let name = data["name"] as? String ?? ""
let albumName = data["albumName"] as? String ?? ""
let audioImageName = data["audioImageName"] as? String ?? ""
let paragraphNumber = data["paragraphNumber"] as? String ?? ""
let trackURL = data["trackURL"] as? String ?? ""
print(data)
return Audio(name: name, albumName: albumName, paragraphNumber: paragraphNumber, audioImageName: audioImageName, trackURL: trackURL)
}
}
}
}
Audio Structure
import Foundation
struct Audio {
let name: String
let albumName: String
let paragraphNumber: String
let audioImageName: String
let trackURL: String
}
Assign/append value to your paragraphs and also check your position if there will be no values/wrong position it'll throw index out of range
In Objective c i wrote download/upload with progressview in uitableviewcell (multiple upload/download) by AFNETWORKING. and work find it's can be update progressview/file/cell.
and Now im noob first time change to SWIFT programming and use urlsession.
code
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, URLSessionDelegate, URLSessionDataDelegate, URLSessionTaskDelegate {
//var dataArr:Dictionary<String,String> = [:]
var dataArr : NSMutableArray = NSMutableArray.init()
var myTableview:UITableView = UITableView.init()
let color = UIColor(red: 69/255, green: 57/255, blue: 169/255, alpha: 1.0)
let cellID: String = "customCell"
var progressBar : UIProgressView = UIProgressView.init()
let progressView : UIView = UIView.init(frame: CGRect(x: 100, y: 10, width: 100, height: 20))
func urlSession(_ session: URLSession, task: URLSessionTask, didSendBodyData bytesSent: Int64, totalBytesSent: Int64, totalBytesExpectedToSend: Int64)
{
let uploadProgress:Float = Float(totalBytesSent) / Float(totalBytesExpectedToSend)
print(uploadProgress)
DispatchQueue.main.async {
self.progressBar.progress = uploadProgress
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return dataArr.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: cellID, for: indexPath)
let textName: UILabel = UILabel.init(frame: CGRect(x: 0, y: 0, width: 100, height: 50))
textName.textColor=UIColor.black
textName.backgroundColor=UIColor.green
textName.text=dataArr[indexPath.row] as? String
print("\(dataArr[indexPath.row])");
textName.font=UIFont.systemFont(ofSize: 14)
cell.addSubview(textName)
progressView.backgroundColor = UIColor.red
progressView.tag=indexPath.row
let customKeys=["type","Facebook","Google","Twitter"];
let customsValues=["uploadFile","Mark","Lary","Goo"];
let customDatas=Dictionary(uniqueKeysWithValues: zip(customKeys,customsValues))
progressBar = UIProgressView.init(frame: CGRect(x: 0, y: 5, width: 100, height: 20))
progressBar.tag=indexPath.row
progressView.addSubview(progressBar)
cell.addSubview(progressView)
uploadImage(data_dict: customDatas, uploadImg: dataArr[indexPath.row] as! String)
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
let hCell:CGFloat = 50.0
return hCell
}
// override func viewWillAppear(_ animated: Bool) {
// super.viewWillAppear(animated)
// setNeedsStatusBarAppearanceUpdate()
// }
override var preferredStatusBarStyle: UIStatusBarStyle {
// Change font of status bar is white.
.lightContent
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
dataArr=["1.jpg","2.jpg","3.jpg"]
//print(dataArr)
let myScreen = UIScreen.main.bounds
let statusHieght = UIApplication.shared.statusBarFrame.height
if #available(iOS 13.0, *) {
let app = UIApplication.shared
let statusBarHeight: CGFloat = app.statusBarFrame.size.height
let statusbarView = UIView()
statusbarView.backgroundColor = color
statusbarView.tintColor = .white
view.addSubview(statusbarView)
statusbarView.translatesAutoresizingMaskIntoConstraints = false
statusbarView.heightAnchor
.constraint(equalToConstant: statusBarHeight).isActive = true
statusbarView.widthAnchor
.constraint(equalTo: view.widthAnchor, multiplier: 1.0).isActive = true
statusbarView.topAnchor
.constraint(equalTo: view.topAnchor).isActive = true
statusbarView.centerXAnchor
.constraint(equalTo: view.centerXAnchor).isActive = true
} else {
let statusBar = UIApplication.shared.value(forKeyPath: "statusBarWindow.statusBar") as? UIView
statusBar?.backgroundColor = color
}
UINavigationBar.appearance().barTintColor = color
UINavigationBar.appearance().tintColor = .white
UINavigationBar.appearance().titleTextAttributes = [NSAttributedString.Key.foregroundColor: UIColor.white]
UINavigationBar.appearance().isTranslucent = false
let navBar = UINavigationBar(frame: CGRect(x: 0, y: statusHieght, width: myScreen.size.width, height: 44))
//navBar.isTranslucent=true
//navBar.backgroundColor = .red
let navItem = UINavigationItem(title: "SomeTitle")
let doneItem = UIBarButtonItem(barButtonSystemItem: UIBarButtonItem.SystemItem.done, target:nil , action:#selector(ClickDone))
navItem.rightBarButtonItem = doneItem
navBar.setItems([navItem], animated: false)
self.view.addSubview(navBar)
let AllTopDistance=statusHieght+navBar.frame.size.height
let myView:UIView = UIView.init(frame: CGRect(x: 0, y: AllTopDistance, width: myScreen.size.width, height: myScreen.size.height-AllTopDistance))
myView.backgroundColor = .lightGray
myTableview=UITableView.init(frame: CGRect(x: 0, y: 0, width: myScreen.size.width, height: myScreen.size.height-AllTopDistance))
myTableview.register(UITableViewCell.self, forCellReuseIdentifier: cellID)
print("\(statusHieght) \(myScreen.size.width) \(AllTopDistance)")
myTableview.delegate=self
myTableview.dataSource=self
myTableview.backgroundColor=UIColor.red
myView.addSubview(myTableview)
self.view.addSubview(myView)
}
#objc func ClickDone(){
print("Done")
}
func calculateTopDistance() -> CGFloat{
/// Create view for misure
let misureView : UIView = UIView()
misureView.backgroundColor = .clear
view.addSubview(misureView)
/// Add needed constraint
misureView.translatesAutoresizingMaskIntoConstraints = false
misureView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
misureView.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
misureView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
if let nav = navigationController {
misureView.topAnchor.constraint(equalTo: nav.navigationBar.bottomAnchor).isActive = true
}else{
misureView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
}
/// Force layout
view.layoutIfNeeded()
/// Calculate distance
let distance = view.frame.size.height - misureView.frame.size.height
/// Remove from superview
misureView.removeFromSuperview()
return distance
}
#objc func uploadImage(data_dict : Dictionary<String,String>, uploadImg : String)
{
print("click \(data_dict)")
let image = UIImage(named: uploadImg)
// generate boundary string using a unique per-app string
let boundary = UUID().uuidString
let config = URLSessionConfiguration.default
//let session = URLSession(configuration: config)
let session = URLSession(configuration: config, delegate: self, delegateQueue: OperationQueue.main)
// Set the URLRequest to POST and to the specified URL
var urlRequest = URLRequest(url: URL(string: "http://x.x.x.x/xxx/Labs.php")!)
urlRequest.httpMethod = "POST"
// Set Content-Type Header to multipart/form-data, this is equivalent to submitting form data with file upload in a web browser
// And the boundary is also set here
urlRequest.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")
var data = Data()
for (key, value) in data_dict {
print(key, value)
let fieldName = key
let fieldValue = value
data.append("\r\n--\(boundary)\r\n".data(using: .utf8)!)
data.append("Content-Disposition: form-data; name=\"\(fieldName)\"\r\n\r\n".data(using: .utf8)!)
data.append("\(fieldValue)".data(using: .utf8)!)
}
// Add the image data to the raw http request data
data.append("\r\n--\(boundary)\r\n".data(using: .utf8)!)
data.append("Content-Disposition: form-data; name=\"fileToUpload\"; filename=\"\(uploadImg)\"\r\n".data(using: .utf8)!)
data.append("Content-Type: image/png\r\n\r\n".data(using: .utf8)!)
data.append((image?.jpegData(compressionQuality: 1.0))!)
// End the raw http request data, note that there is 2 extra dash ("-") at the end, this is to indicate the end of the data
// According to the HTTP 1.1 specification https://tools.ietf.org/html/rfc7230
data.append("\r\n--\(boundary)--\r\n".data(using: .utf8)!)
// Send a POST request to the URL, with the data we created earlier
session.uploadTask(with: urlRequest, from: data, completionHandler: { responseData, response, error in
if(error != nil){
print("\(error!.localizedDescription)")
}
guard let responseData = responseData else {
print("no response data")
return
}
if let responseString = String(data: responseData, encoding: .utf8) {
print("Response data : \(responseString)")
}
}).resume()
}
}
?? How define URLSESSION update progressview in uitableview by cell/file
Thank you.
I'm curious how you did it in Objective-C with AFNetworking. At least conceptional, there shouldn't be a big difference to implementation in Swift with URLSession.
Imho your main problem about updating your progress is, that you share the progressView variable with a single UIView instance for all of your cells.
you do not initialize a new progressView for each cell, but share one view for all cells
because of 1, cell.addSubview(progressView) doesn't only add your progressView to that cell, it removes your progressView from the other cells as well, because a view can only have one parent view.
your progressView will have multiple UIProgressBars as subview. One for each time tableView(_:cellForRowAt indexPath:) is called
with self.progressBar.progress = uploadProgress you will always update the progressBar which was last initialized, because you don't have a reference to the other ones.
To get this to work in a clean way, I'd recommend you do some research to MVVM architecture.
create a UITableViewCell subclass for your cell
create a ViewModel class for that cell
store a viewModel instance for each of your cells in your viewController (or better in a separate TableViewDataSource object)
implement URLSessionDelegate protocol in your ViewModel and set the appropriate viewModel instance as delegate for your uploadTasks
For a quick and dirty fix:
Remove these lines:
var progressBar : UIProgressView = UIProgressView.init()
let progressView : UIView = UIView.init(frame: CGRect(x: 100, y: 10, width: 100, height: 20))
Add variable:
var uploadTasks: [URLSessionDataTask: IndexPath] = [:]
Add a helper function to calculate viewTag:
func viewTag(for indexPath: IndexPath) -> Int {
return indexPath.row + 1000
}
Change your tableView(_:cellForRowAt indexPath:) to:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: cellID, for: indexPath)
let textName: UILabel = UILabel.init(frame: CGRect(x: 0, y: 0, width: 100, height: 50))
textName.textColor=UIColor.black
textName.backgroundColor=UIColor.green
textName.text=dataArr[indexPath.row] as? String
print("\(dataArr[indexPath.row])");
textName.font=UIFont.systemFont(ofSize: 14)
cell.addSubview(textName)
let progressView = UIView(frame: CGRect(x: 100, y: 10, width: 100, height: 20))
progressView.backgroundColor = UIColor.red
let customKeys=["type","Facebook","Google","Twitter"];
let customsValues=["uploadFile","Mark","Lary","Goo"];
let customDatas=Dictionary(uniqueKeysWithValues: zip(customKeys,customsValues))
let progressBar = UIProgressView.init(frame: CGRect(x: 0, y: 5, width: 100, height: 20))
progressBar.tag = viewTag(for: indexPath)
progressView.addSubview(progressBar)
cell.addSubview(progressView)
uploadImage(data_dict: customDatas, indexPath: indexPath)
return cell
}
Change your uploadImage method to:
#objc func uploadImage(data_dict : Dictionary<String,String>, indexPath : IndexPath) {
print("click \(data_dict)")
let uploadImg = dataArr[indexPath.row] as! String
let image = UIImage(named: uploadImg)
...
let task = session.uploadTask(with: urlRequest, from: data, completionHandler: { responseData, response, error in
...
})
uploadTasks[task] = indexPath
task.resume()
}
Change your urlSession delegate method to:
func urlSession(_ session: URLSession, task: URLSessionTask, didSendBodyData bytesSent: Int64, totalBytesSent: Int64, totalBytesExpectedToSend: Int64) {
let uploadProgress:Float = Float(totalBytesSent) / Float(totalBytesExpectedToSend)
print(uploadProgress)
guard let indexPath = uploadTasks[task] else { return }
let viewTag = viewTag(for: indexPath)
guard let progressBar = self.view.viewWithTag(viewTag) as? UIProgressView else { return }
DispatchQueue.main.async {
progressBar.progress = uploadProgress
}
}
HI, so this is my vc for a view where I save info about a plane (the type and rego). It saves the plane and deletes and you can close the app and come back and it saves it there in the core data model but I have a weird problem. After you make a plane and it shows in the table view and add a few more planes (table view cell which has info) then the labels which display the info start to overlap with what should be deleted information. The pictures bellow show what I mean. Any help would be greatly appreciated.
var context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
var typeField: UITextField?
var regoField: UITextField?
#IBAction func addPlaneButton(_ sender: Any) {
let alertController = UIAlertController(title: "New Plane", message: "Please Input The Type And Registration", preferredStyle: .alert)
alertController.addTextField(configurationHandler: typeField)
alertController.addTextField(configurationHandler: regoField)
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
let saveAction = UIAlertAction(title: "Save", style: .default, handler: self.savePlane)
alertController.addAction(cancelAction)
alertController.addAction(saveAction)
self.present(alertController, animated: true)
print("Add Plane Pressed")
}
func typeField(textField: UITextField!) {
typeField = textField
typeField?.placeholder = "Aircraft Type"
}
func regoField(textField: UITextField!) {
regoField = textField
regoField?.placeholder = "Aircraft Registration"
}
#IBOutlet weak var tableView: UITableView!
var timer = Timer()
let utcItem = UIBarButtonItem()
let utcLbl = UILabel()
override func viewDidLoad() {
super.viewDidLoad()
//Table View
tableView.delegate = self
tableView.dataSource = self
self.tableView.rowHeight = 88
setupView()
}
//////Functions////
func setupView() {
//UTC Time Formatter
let dateFormatter = DateFormatter()
dateFormatter.timeZone = TimeZone(abbreviation: "UTC")
dateFormatter.dateFormat = "HH:mm"
_ = Timer.scheduledTimer(timeInterval: 0.05, target: self, selector: #selector(updateTime), userInfo: nil, repeats: true)
//UTC Time
utcLbl.frame = CGRect(x: 0, y: 0, width: 100, height: 20)
utcLbl.text = "\(dateFormatter.string(from: Date())) UTC"
utcItem.customView = utcLbl
utcLbl.backgroundColor = UIColor.init(fromHexCode: "4FB7F1")
utcLbl.layer.cornerRadius = 10
utcLbl.textAlignment = .center
utcLbl.layer.masksToBounds = true // Or utcLbl.clipsToBounds = true
self.navigationItem.setLeftBarButtonItems([utcItem], animated: true)
// Large Title
self.title = "Planes"
self.navigationController?.navigationBar.largeTitleTextAttributes = [NSAttributedString.Key.font: UIFont(name: "Avenir-Black", size: 35)!]
self.navigationController?.navigationBar.prefersLargeTitles = true
let customBlue = UIColor(red:0.08, green:0.38, blue:0.75, alpha:1.0)
navigationController?.navigationBar.barTintColor = customBlue
}
//Constant UTC Time Lbl
#objc func updateTime() {
let formatter = DateFormatter()
formatter.timeZone = TimeZone(abbreviation: "UTC")
formatter.dateFormat = "HH:mm"
utcLbl.text = formatter.string(from: Date()) + " UTC"
}
//Save the Plane Info
func savePlane(alert: UIAlertAction) {
if typeField?.text != "" || regoField?.text != "" {
let newLog = NSEntityDescription.insertNewObject(forEntityName: "Planes", into: context)
newLog.setValue(self.typeField?.text, forKey: "type")
newLog.setValue(self.regoField?.text, forKey: "rego")
do{
try context.save()
}
catch {
print(error)
}
//Making the table update itself when user logs the plane
self.fetchData()
self.tableView.reloadData()
}
print("Plane Saved")
}
func fetchData() {
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
do{
planeArray = try context.fetch(Planes.fetchRequest())
}
catch{
print(error)
}
}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
if editingStyle == .delete {
let save = planeArray[indexPath.row]
context.delete(save)
(UIApplication.shared.delegate as! AppDelegate).saveContext()
do {
planeArray = try context.fetch(Planes.fetchRequest())
}
catch {
print(error)
}
tableView.reloadData()
}
}
//Table View Functions
public func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return (planeArray.count)
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
//Plane Pic
let planeView = UIImageView()
planeView.frame = CGRect(x: 0, y: 0, width: 69, height: 67)
//planeView.center = CGPoint(x: cell.center.x - 150, y: cell.center.y)
planeView.center = CGPoint(x: cell.center.x - 145, y: cell.center.y)
let planeImage: UIImage = UIImage(named: "plane")!
planeView.image = planeImage
cell.addSubview(planeView)
//Type Label
let type = UILabel()
type.frame = CGRect(x: 0, y: 0, width: 45, height: 21)
type.center = CGPoint(x: cell.center.x - 80, y: cell.center.y - 22.5)
type.text = "Type:"
type.font = UIFont(name: "Montserrat-Medium", size: 17)
cell.addSubview(type)
//Type Answer
let typeAnswer = UILabel()
typeAnswer.frame = CGRect(x: 0, y: 0, width: 220, height: 21)
typeAnswer.center = CGPoint(x: cell.center.x + 62.5, y: cell.center.y - 22.5)
typeAnswer.text = ""
typeAnswer.font = UIFont(name: "Montserrat-Light", size: 17)
typeAnswer.textAlignment = .right
cell.addSubview(typeAnswer)
//Rego Label
let rego = UILabel()
rego.frame = CGRect(x: 0, y: 0, width: 110, height: 21)
rego.center = CGPoint(x: cell.center.x - 47.5, y: cell.center.y + 18.5)
rego.text = "Registration:"
rego.font = UIFont(name: "Montserrat-Medium", size: 17)
cell.addSubview(rego)
//rego answer
let regoAnswer = UILabel()
regoAnswer.frame = CGRect(x: 0, y: 0, width: 160, height: 21)
regoAnswer.center = CGPoint(x: cell.center.x + 92.5, y: cell.center.y + 18.5)
regoAnswer.text = ""
regoAnswer.font = UIFont(name: "Montserrat-Light", size: 17)
regoAnswer.textAlignment = .right
cell.addSubview(regoAnswer)
let save = planeArray[indexPath.row]
typeAnswer.text = save.type
regoAnswer.text = save.rego
return cell
}
override func viewWillAppear(_ animated: Bool) {
//Making the table update itself when user logs the plane
fetchData()
tableView.reloadData()
}
}
UITableView reuses the cells. In your case, when the cells are created the first time, you add a UILabel to it. The next time this cell is loaded, UITableView reuses the existing cell and cellForRowAt adds another UILabel to this cell. The proper implementation is to create a custom UITableViewCell and reset the value of all the attributes in cellForRowAt method.
You can try something like below (please note that this is just a rough implementation and it is assumed that you know the basics of ios programming. If that is not the case, I'd recommend researching it a bit):
Add a custom tableview cell
class CustomTableViewCell: UITableViewCell {
#IBOutlet weak var imgViewPlane: UIImageView!
#IBOutlet weak var lblType: UILabel!
#IBOutlet weak var lblTypeAnswer: UILabel!
#IBOutlet weak var lblRego: UILabel!
#IBOutlet weak var lblRegoAnswer: UILabel!
}
Create a dynamic prototype cell in storyboard tableview and link the IBOutlets. Then in your cellForRowAt, do something like this
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! CustomTableViewCell
cell.imgViewPlane.image = planeImage
cell.lblType.text = "Type:"
cell.lblTypeAnswer.text = planeArray[indexPath.row].type
cell.lblRego.text = "Registration:"
cell.lblRegoAnswer.text = planeArray[indexPath.row].rego
return cell
}
You’re adding new subviews to your cell every time that cell is dequeued.
I would recommend you to create these views, labels, etc. as lazy variables inside UITableViewCell subclass and add them as subviews inside awakeFromNib()
class YourCell: UITableViewCell {
lazy var label: UILabel = {
var label = UILabel(...)
...
return label
}()
...
override func awakeFromNib() {
addSubview(label)
...
}
}
.... or use IBOutlet if you’re using storyboard
Then in cellForRowAt just change properties of you downcasted cell subclass’s views
let cell = ... as! YourCell
cell.label.text = ""
cell.anotherLabel.isHidden = true
...
I'm having an issue, I can append all the posts into my collection view but the username and profileImage change to whoever the currently logged in user is. Can someone help me figure out how to set it to the user who posted it?
import UIKit
import Firebase
class HomeController: UICollectionViewController, UICollectionViewDelegateFlowLayout {
var cellId = "cellId"
var filteredPosts = [Post]()
var posts = [Post]()
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(handleUpdateFeed), name: PostController.updateFeedNotificationName, object: nil)
collectionView?.backgroundColor = UIColor(red: 240/255, green: 240/255, blue: 240/255, alpha: 1)
collectionView?.register(HomePostCell.self, forCellWithReuseIdentifier: cellId)
let refreshControl = UIRefreshControl()
refreshControl.addTarget(self, action: #selector(handleRefresh), for: .valueChanged)
collectionView?.refreshControl = refreshControl
setupNavigationItems()
fetchPosts()
}
#objc func handleUpdateFeed() {
handleRefresh()
}
fileprivate func fetchPosts() {
guard let uid = FIRAuth.auth()?.currentUser?.uid else { return }
FIRDatabase.fetchUserWithUID(uid: uid) { (user) in
self.fetchPostsWithUser(user: user)
}
}
fileprivate func fetchPostsWithUser(user: User) {
let ref = FIRDatabase.database().reference().child("posts")
ref.observe(.childAdded, with: { (snapshot) in
guard let dictionaries = snapshot.value as? [String: Any]
else { return }
dictionaries.forEach({ (key, value) in
guard let userDictionary = value as? [String: Any] else { return }
var post = Post(user: user, dictionary: userDictionary)
self.posts.append(post)
})
self.posts.sort(by: { (post1, post2) -> Bool in
return post1.creationDate.compare(post2.creationDate) == .orderedDescending
})
self.collectionView?.reloadData()
}) { (err) in
print("Failed to fetch posts.",err)
}
}
#objc func handleRefresh() {
self.posts.removeAll()
self.fetchPosts()
DispatchQueue.global(qos: .background).async {
self.collectionView?.reloadData()
self.collectionView?.refreshControl?.endRefreshing()
}
}
func setupNavigationItems() {
navigationItem.title = "Events"
navigationItem.leftBarButtonItem = UIBarButtonItem(image: #imageLiteral(resourceName: "download").withRenderingMode(.alwaysOriginal), style: .plain, target: self, action: #selector(handleFilter))
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return posts.count
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: view.frame.width, height: 200)
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as! HomePostCell
cell.post = posts[indexPath.item]
return cell
}
}
import UIKit
class HomePostCell: UICollectionViewCell {
var post: Post? {
didSet{
usernameLabel.text = post?.user.username
guard let profileImageUrl = post?.user.profileImageUrl else { return }
userProfileImageView.loadImage(urlString: profileImageUrl)
setupCaptionText()
setupDateLabel()
}
}
let userProfileImageView: CustomImageView = {
let iv = CustomImageView()
iv.clipsToBounds = true
iv.contentMode = .scaleAspectFill
return iv
}()
let usernameLabel: UILabel = {
let label = UILabel()
label.font = UIFont.boldSystemFont(ofSize: 14)
return label
}()
let captionLabel: UILabel = {
let label = UILabel()
label.numberOfLines = 0
label.backgroundColor = .white
return label
}()
let dateLabel: UILabel = {
let label = UILabel()
label.font = UIFont.boldSystemFont(ofSize: 14)
return label
}()
let optionsButton: UIButton = {
let button = UIButton(type: .system)
button.setTitle("•••", for: .normal)
button.setTitleColor(UIColor.black, for: .normal)
return button
}()
fileprivate func setupCaptionText() {
guard let post = self.post else { return }
let attributedText = NSMutableAttributedString(string: " \(post.caption)", attributes: [NSAttributedStringKey.font: UIFont.systemFont(ofSize: 14)])
captionLabel.attributedText = attributedText
}
fileprivate func setupDateLabel() {
guard let post = self.post else { return }
let timeAgoDisplay = post.creationDate.timeAgoDisplay()
let attributedText = NSMutableAttributedString(string: timeAgoDisplay, attributes: [NSAttributedStringKey.font: UIFont.systemFont(ofSize: 14), NSAttributedStringKey.foregroundColor: UIColor.gray])
dateLabel.attributedText = attributedText
}
override init(frame: CGRect) {
super.init(frame: frame)
addSubview(userProfileImageView)
userProfileImageView.anchor(top: topAnchor, left: leftAnchor, bottom: nil, right: nil, paddingTop: 8, paddingLeft: 8, paddingBottom: 5, paddingRight: 0, width: 40, height: 40)
userProfileImageView.layer.cornerRadius = 40 / 2
backgroundColor = .white
addSubview(usernameLabel)
usernameLabel.anchor(top: topAnchor, left: userProfileImageView.rightAnchor, bottom: nil, right: nil, paddingTop: 12, paddingLeft: 8, paddingBottom: 0, paddingRight: 0, width: 0, height: 0)
addSubview(captionLabel)
captionLabel.anchor(top: userProfileImageView.bottomAnchor, left: leftAnchor, bottom: nil, right: rightAnchor, paddingTop: 10, paddingLeft: 8, paddingBottom: 0, paddingRight: 0, width: 0, height: 0)
addSubview(optionsButton)
optionsButton.anchor(top: topAnchor, left: nil, bottom: nil, right: rightAnchor, paddingTop: 5, paddingLeft: 0, paddingBottom: 0, paddingRight: 10, width: 0, height: 0)
addSubview(dateLabel)
dateLabel.anchor(top: nil, left: leftAnchor, bottom: bottomAnchor, right: nil, paddingTop: 0, paddingLeft: 8, paddingBottom: 5, paddingRight: 0, width: 0, height: 0)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
import Foundation
struct User {
var uid: String
let username: String
let profileImageUrl: String
init(uid: String, dictionary: [String: Any]) {
self.uid = uid
self.username = dictionary["usernames"] as? String ?? ""
self.profileImageUrl = dictionary["profileImageUrl"] as? String ?? ""
}
}
import Foundation
struct Post {
var id: String?
let user: User
let caption: String
let creationDate: Date
init(user: User, dictionary: [String: Any]) {
self.user = user
self.caption = dictionary["caption"] as? String ?? ""
let secondsFrom1970 = dictionary["creationDate"] as? Double ?? 0
self.creationDate = Date(timeIntervalSince1970: secondsFrom1970)
}
}
This code here:
fileprivate func fetchPosts() {
guard let uid = FIRAuth.auth()?.currentUser?.uid else { return }
FIRDatabase.fetchUserWithUID(uid: uid) { (user) in
self.fetchPostsWithUser(user: user)
}
}
You're basically making a query based on the currentUser?.uid and not on the user that created the post. You're passing that to the fileprivate func fetchPostsWithUser(user: User) hence it retrieves the same logged in user.
It actually should pass the user.uid stored for each post on the database (unless you want to pull out the posts for the currentUser and display those.
Each post at the moment of posting needs to store the user uid and store that on the database. Then make query to get those posts based on the uid of the user. And cache your users in an array just like you did with your posts.
UPDATED:
class Post {
var id : String?
var title : String?
var content : String?
var userUid : String?
static func transformDataToPost (dictionary: [String : Any], key: String) -> Post {
let post = Post()
post.id = key // this is the snapshot.key that you get
post.userUid = dictionary["userUid"] as? String
post.title = dictionary["title"] as? String
post.content = dictionary["content"] as? String
return
}
Now, when observing the post to get data back from Firebase I'm using it a little more structured, in another class, ie, I have separate PostApi class where I deal with all API requests, not in the UIViewController itself, but the idea is the same.
var REF_POSTS = Database.database().reference().child("posts")
func observePost(withId id: String, completion: #escaping (Post) -> Void) {
REF_POSTS.child(id).observeSingleEvent(of: .value, with: { snapshot in
if let dictionary = snapshot.value as? [String : Any] {
let post = Post.transformDataToPost(dictionary: dictionary, key: snapshot.key)
completion(post)
}
})
}
For sending the data to FirebaseDatabase I do it the following way:
static func sendPostDataToDatabase ( title: String?,
content: String?,
onSuccess: #escaping () -> Void,
onError: #escaping (_ errorMessage: String?) -> Void) {
let newPostId = Api.Post.REF_POSTS.childByAutoId().key // THIS creates the new ID of a post, it's just a dynamic way to get a unique post ID , you can also use NSUUID().uuidString which will give you a unique string
// the definition of `uuidString` is: A string containing a formatted UUID for example E621E1F8-C36C-495A-93FC-0C247A3E6E5F.
let newPostReference = Api.Post.REF_POSTS.child(newPostId)
// MARK: - TIMESTAMP FEATURE
let timestamp = Int(Date().timeIntervalSince1970)
var newPostDict = [ "userUid" : Api.Users.CURRENT_USER!.uid,
"title" : title as Any,
"content" : content as Any,
"timestamp" : timestamp
] as [String : Any]
newPostReference.setValue(newPostDict) { (error, databaseRef) in
if error != nil {
onError(error!.localizedDescription)
return
}
else {
onSuccess(newShipId, photosArray[0])
}
}
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
...
}