I'm working on a project where the user can upload images from their library into a gallery. It's working fine, but I want the images to stay even after I refresh the app. I've been told that NSUserData will actually work better than CoreData, but I'm having a LOT of trouble implementing it into my code. I've never worked with UserData, so I am completely lost.
Any help would be appreciated!
View Controller:
// ViewController.swift
import UIKit
import PhotosUI
import Photos
import CoreData
import Foundation
// user images below
var imageArray = [UIImage]()
var imageIDs = [""]
var countImage = 0
class ViewController: UIViewController, PHPickerViewControllerDelegate {
let userDefaults = UserDefaults.standard
#IBOutlet var collectionView: UICollectionView!
// private let myFileManager = LocalFileManager.instance
// private let folderName = "closet_folder"
override func viewDidLoad() {
super.viewDidLoad()
// set up collection in closet
navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(addPhotos))
collectionView.register(ClosetCollectionViewCell.nib(), forCellWithReuseIdentifier: "ClosetCollectionViewCell")
collectionView.delegate = self
collectionView.dataSource = self
let layout = UICollectionViewFlowLayout()
layout.itemSize = CGSize(width: 115, height: 115)
collectionView.collectionViewLayout = layout
layout.minimumInteritemSpacing = 6
layout.minimumLineSpacing = 7
}
// access photo library
#objc private func addPhotos() {
var config = PHPickerConfiguration()
config.selectionLimit = 10
config.filter = .images
let vc = PHPickerViewController(configuration: config)
vc.delegate = self
present(vc, animated: true)
}
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
picker.dismiss(animated: true, completion: nil)
let group = DispatchGroup()
results.forEach { result in
group.enter()
result.itemProvider.loadObject(ofClass: UIImage.self) { reading, error in
defer {
group.leave()
}
guard let image = reading as? UIImage, error == nil else {
return
}
print(image)
imageArray.append(image)
// imageIDs.append("\(image)")
countImage += 1
print(countImage)
imageIDs.append(String(countImage))
// imageIDs.append("\(image)")
LocalFileManager.instance.saveImage(image: image, imageName: String(countImage), folderName: "closet_folder")
}
}
group.notify(queue: .main) {
self.collectionView.reloadData()
}
}
}
extension ViewController: UICollectionViewDelegate {
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
collectionView.deselectItem(at: indexPath, animated: true)
print("you tapped me!")
// when cell is tapped...
}
}
extension ViewController: UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
// how many cells are shown? based on number of items the user uploaded
return imageArray.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
// return cell for given item
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "ClosetCollectionViewCell", for: indexPath) as! ClosetCollectionViewCell
// show every cell in imageArray
cell.imageView.image = LocalFileManager.instance.getImage(imageName: imageIDs[indexPath.row + 1], folderName: "closet_folder")
//imageArray[indexPath.row]
return cell
}
}
extension ViewController: UICollectionViewDelegateFlowLayout {
// margin of padding between cells
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: 115, height: 115)
}
}
Here's the code with the saveImage and getImage functions:
It generates URLs for every image and the folder it's stored in.
//
// LocalFileManager.swift
import Foundation
import SwiftUI
class LocalFileManager {
static let instance = LocalFileManager()
private init() {}
func saveImage(image: UIImage, imageName: String, folderName: String) {
// create folder
createFolderIfNeeded(folderName: folderName)
// get path for image
guard
let data = image.pngData(),
let url = getURLforImage(imageName: imageName, folderName: folderName)
else { return }
// save image to path
do {
try data.write(to: url)
} catch let error {
print("image name: \(image) error saving image: \(error)")
}
}
func getImage(imageName: String, folderName: String) -> UIImage? {
guard let url = getURLforImage(imageName: imageName, folderName: folderName),
FileManager.default.fileExists(atPath: url.path)
else {
return nil
}
return UIImage(contentsOfFile: url.path)
}
// referenced functions below
// create folder to store images
private func createFolderIfNeeded(folderName: String) {
guard let url = getURLforFolder(folderName: folderName) else {
return
}
if !FileManager.default.fileExists(atPath: url.path) {
do {
try FileManager.default.createDirectory(at: url, withIntermediateDirectories: true)
}
catch let error {
print("folder name: \(folderName) error creating folder! \(error)")
}
}
}
// get URL for that folder
private func getURLforFolder(folderName: String) -> URL? {
guard let url = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first
else {
return nil
}
return url.appendingPathExtension(folderName)
}
// generate URL for images in folder
private func getURLforImage(imageName: String, folderName: String) -> URL? {
guard let folderURL = getURLforFolder(folderName: folderName) else {
return nil
}
return folderURL.appendingPathExtension(imageName)
}
}
Related
I have a list of machine name and type in tableView and when user click it, it push into detailVC.
At first the the user don't have an image, than user choose an image from gallery and return it as a PHAssets.
Than I convert the PHAssets into data and show it in collectionView, so I create an array of data and display it in collectionView.
Than I want to update MachineItem object to my data from PHAssets, since in MachineItem object have an array of Data.
So when user return from machine list in tableView, the object already update. But when I tap the list from tableView. The photo I save in an object is not showing, how can display it in my collectionView.
This is my Model
struct MachineItem: Codable {
var id = UUID().uuidString
var name: String
var type: String
var qrNumber = Int.random(in: 1..<10)
var maintenanceDate: String?
var images: [Data]?
}
This is my MachineStore class that perform all add, update, and remove object from MachineItem
class MachineStore {
var items: [MachineItem] = []
#discardableResult func add(_ machine: MachineItem, at index: Int) -> MachineItem {
let newMachine = MachineItem(id: machine.id, name: machine.name, type: machine.type, qrNumber: machine.qrNumber, maintenanceDate: machine.maintenanceDate, images: machine.images)
items.insert(newMachine, at: index)
return newMachine
}
func update(_ machine: MachineItem) {
if let index = items.firstIndex(where: { $0.id == machine.id }) {
items[index].name = machine.name
items[index].type = machine.type
items[index].qrNumber = machine.qrNumber
items[index].maintenanceDate = machine.maintenanceDate
items[index].images = machine.images
}
}
#discardableResult func remove(at index: Int) -> MachineItem {
return items.remove(at: index)
}
}
This is my MachineDataVC that have a list of MachineItem Object in TableView
class MachineDataVC: UIViewController {
var tableView = UITableView()
var store = MachineStore()
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let item = store.items[indexPath.row]
let detailVC = MachineDetailVC()
detailVC.item = item
detailVC.store = store
navigationController?.pushViewController(detailVC, animated: true)
tableView.deselectRow(at: indexPath, animated: true)
}
}
And this is the detailVC where I can't display the images data when back and fort from MachineDataVC to detailVC
class MachineDetailVC: UIViewController {
var item: MachineItem!
var store: MachineStore!
var images: [Data] = []
var photoCollectionView = UICollectionView(frame: .zero, collectionViewLayout: UICollectionViewFlowLayout())
override func viewDidLoad() {
super.viewDidLoad()
// CollectionView setup
photoCollectionView.delegate = self
photoCollectionView.dataSource = self
photoCollectionView.register(MachineDetailCell.self, forCellWithReuseIdentifier: MachineDetailCell.cellID)
photoCollectionView.backgroundColor = .systemBackground
}
// This where PHAssets being retrieve
#objc func pickPhotos() {
let imagePickerVC = ImagePickerController()
imagePickerVC.settings.selection.max = 10
imagePickerVC.settings.theme.selectionStyle = .numbered
imagePickerVC.settings.fetch.assets.supportedMediaTypes = [.image]
imagePickerVC.settings.selection.unselectOnReachingMax = true
self.presentImagePicker(imagePickerVC) { (assets) in
} deselect: { (_) in
} cancel: { (_) in
} finish: { (assets) in
self.images = self.getImage(from: assets)
self.photoCollectionView.reloadData()
}
}
private func getImage(from assets: [PHAsset]) -> [Data] {
let images = assets.map { fetchImage(from: $0) }
print("Pick image:", images)
return images
}
private func fetchImage(from asset: PHAsset) -> Data {
let manager = PHImageManager.default()
let options = PHImageRequestOptions()
options.isSynchronous = true
var thumbnail = Data()
manager.requestImage(for: asset, targetSize: .init(width: 100, height: 100), contentMode: .aspectFill, options: options) { (result, info) in
if let selectedImage = result?.data {
thumbnail = selectedImage
}
}
return thumbnail
}
// This is how I setup collectionView
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return images.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: MachineDetailCell.cellID, for: indexPath) as! MachineDetailCell
let image = images[indexPath.row]
cell.set(imageData: image)
return cell
}
// This is how I save the object from UIBarButtonItem
#objc func saveItem() {
guard let name = machineNameTF.text else { return }
guard let type = machineTypeTF.text else { return }
guard let date = machineMaintenanceDateTF.text else { return }
let machineItem = MachineItem(id: item.id, name: name, type: type, qrNumber: item.qrNumber, maintenanceDate: date, images: images)
store.update(machineItem)
}
}
What I've been trying so far is like this
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
if !images.isEmpty {
return store.items.count
} else {
return images.count
}
}
I'm very glad if anyone can help me :)
I finally found my answer from my question, what I need to do is in my viewDidLoad I need to add in my MachineDetailVC
self.images = self.item.images ?? []
since when I save the data back into items in machineStore, I need to give the data into images array in MachineDetailVC
How do you properly send data to a custom data NSObject class for a collection view? My variables are always returning as nil.
I have a splash screen in it's own view Controller. When all the data I want loaded has finished loading from firebase, I go to the main screen of the app via performSegue(withIdentifier:), here's the code for the code in question from the SplashScreenViewController:
func getDatabaseReference(){
let d = DispatchGroup()
d.enter()
let encodedURL = (postIDRefDic["post1"]! + "/postURL")
ref.child(encodedURL).observe(.value, with: { (snapshot) in
let newUrl = snapshot.value as! String
DemoSource.shared.url = newUrl
d.leave()
})
d.notify(queue: .main){
self.performSegue(withIdentifier: "showHome", sender: nil)
}
}
in the above code you can see that I'm seguing to my next view controller, HomeViewController, in the HomeViewController class I have a collection view which is helped by a custom class (the NSObject class) called DemoSource (also show above) that I'm using to assign the new data I just got in a variable within that class. This DemoSource class is a custom data class of type NSObject:
import UIKit
import Firebase
struct DataObj {
var image: UIImage?
var play_Url: URL?
var title = ""
var content = ""
}
class DemoSource: NSObject {
static let shared = DemoSource()
var demoData = [DataObj]()
var url = ""
override init() {
demoData += [
DataObj(image: #imageLiteral(resourceName: "ss-1") , play_Url: URL(string: url), title: "title ", content: "Description")
]
}
}
I use this class with my HomeViewController with the collection view:
import UIKit
import AVKit
import Firebase
import MMPlayerView
class HomeViewController: UIViewController {
var offsetObservation: NSKeyValueObservation?
lazy var mmPlayerLayer: MMPlayerLayer = {
let l = MMPlayerLayer()
l.cacheType = .memory(count: 5)
l.coverFitType = .fitToPlayerView
l.videoGravity = AVLayerVideoGravity.resizeAspect
l.replace(cover: CoverA.instantiateFromNib())
l.repeatWhenEnd = true
return l
}()
#IBOutlet weak var playerCollect: UICollectionView!
override func viewDidLoad() {
super.viewDidLoad()
// remove previous download fails file
MMPlayerDownloader.cleanTmpFile()
self.navigationController?.mmPlayerTransition.push.pass(setting: { (_) in
})
offsetObservation = playerCollect.observe(\.contentOffset, options: [.new]) { [weak self] (_, value) in
guard let self = self, self.presentedViewController == nil else {return}
NSObject.cancelPreviousPerformRequests(withTarget: self)
self.perform(#selector(self.startLoading), with: nil, afterDelay: 0.2)
}
playerCollect.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: 200, right:0)
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { [weak self] in
self?.updateByContentOffset()
self?.startLoading()
}
mmPlayerLayer.getStatusBlock { [weak self] (status) in
switch status {
case .failed(let err):
let alert = UIAlertController(title: "err", message: err.description, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
self?.present(alert, animated: true, completion: nil)
case .ready:
print("Ready to Play")
case .playing:
print("Playing")
case .pause:
print("Pause")
case .end:
print("End")
default: break
}
}
mmPlayerLayer.getOrientationChange { (status) in
print("Player OrientationChange \(status)")
}
}
deinit {
offsetObservation?.invalidate()
offsetObservation = nil
print("ViewController deinit")
}
#IBAction func profileButtonTap(_ sender: Any) {
let uid = (Auth.auth().currentUser?.uid)!
let Splash = SpalshScreenViewController()
Splash.GetProfilePicture(uid: uid)
Splash.GetUsername(uid: uid)
Splash.GetName(uid: uid)
Splash.GetClipsNumber(uid: uid)
Splash.GetFollowersNumber(uid: uid)
Splash.GetFollowingsNumber(uid: uid)
performSegue(withIdentifier: "showProfile", sender: nil)
}
}
extension HomeViewController: MMPlayerFromProtocol {
func backReplaceSuperView(original: UIView?) -> UIView? {
guard let path = self.findCurrentPath(),
let cell = self.findCurrentCell(path: path) as? PlayerCell else {
return original
}
return cell.imgView
}
// add layer to temp view and pass to another controller
var passPlayer: MMPlayerLayer {
return self.mmPlayerLayer
}
func transitionWillStart() {
}
// show cell.image
func transitionCompleted() {
self.updateByContentOffset()
self.startLoading()
}
}
extension HomeViewController: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let m = min(UIScreen.main.bounds.size.width, UIScreen.main.bounds.size.height)
return CGSize(width: m, height: m*0.75)
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
DispatchQueue.main.async { [unowned self] in
if self.presentedViewController != nil || self.mmPlayerLayer.isShrink == true {
//self.playerCollect.scrollToItem(at: indexPath, at: .centeredVertically, animated: true)
//self.updateDetail(at: indexPath)
} else {
self.presentDetail(at: indexPath)
}
}
}
fileprivate func updateByContentOffset() {
if mmPlayerLayer.isShrink {
return
}
if let path = findCurrentPath(),
self.presentedViewController == nil {
self.updateCell(at: path)
//Demo SubTitle
if path.row == 0, self.mmPlayerLayer.subtitleSetting.subtitleType == nil {
}
}
}
}
fileprivate func presentDetail(at indexPath: IndexPath) {
self.updateCell(at: indexPath)
mmPlayerLayer.resume()
if let vc = UIStoryboard.init(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "DetailViewController") as? DetailViewController {
vc.data = DemoSource.shared.demoData[indexPath.row]
self.present(vc, animated: true, completion: nil)
}
}
fileprivate func updateCell(at indexPath: IndexPath) {
if let cell = playerCollect.cellForItem(at: indexPath) as? PlayerCell, let playURL = cell.data?.play_Url {
// this thumb use when transition start and your video dosent start
mmPlayerLayer.thumbImageView.image = cell.imgView.image
// set video where to play
mmPlayerLayer.playView = cell.imgView
mmPlayerLayer.set(url: playURL)
}
}
#objc fileprivate func startLoading() {
self.updateByContentOffset()
if self.presentedViewController != nil {
return
}
// start loading video
mmPlayerLayer.resume()
}
private func findCurrentPath() -> IndexPath? {
let p = CGPoint(x: playerCollect.contentOffset.x + playerCollect.frame.width/2, y: playerCollect.frame.height/2)
return playerCollect.indexPathForItem(at: p)
}
private func findCurrentCell(path: IndexPath) -> UICollectionViewCell? {
return playerCollect?.cellForItem(at: path)
}
}
extension HomeViewController: UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return DemoSource.shared.demoData.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "PlayerCell", for: indexPath) as? PlayerCell {
cell.data = DemoSource.shared.demoData[indexPath.row]
return cell
}
return UICollectionViewCell()
}
}
the first time I instantiate the Demosource class is:
extension HomeViewController: UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return DemoSource.shared.demoData.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "PlayerCell", for: indexPath) as? PlayerCell {
cell.data = DemoSource.shared.demoData[indexPath.row]
return cell
}
return UICollectionViewCell()
}
}
When I run my app it crashes as apparently, the url in the data class is nil, even though I set it with the url in my splashscreenviewcontroller? This DemoSource class get's instantiated before the variable is even populated it seems, when I did some debugging with breakpoints.
So my question is, after all this explaining... why isn't the url variable in the DemoSource class still nil and why is the class getting executed before when the view that uses this class is only called AFTER the data has been fetched from Firebase?
You have implemented DemoSource as a singleton, which means that it gets instantiated the first time that you reference DemoSource.shared. This is in getDatabaseReference. When it is instantiated url has its initial value (""), so that is what gets added to the the demoData array.
You don't need an initialiser.
You do need a function to add data to the demoData array.
class DemoSource: NSObject {
static let shared = DemoSource()
var demoData = [DataObj]()
add(urlString: String) {
demoData.append(DataObj(image: #imageLiteral(resourceName: "ss-1") , play_Url: URL(string: urlString), title: "title ", content: "Description"))
}
}
Then, in your getDatabaseReference -
func getDatabaseReference(){
let d = DispatchGroup()
d.enter()
let encodedURL = (postIDRefDic["post1"]! + "/postURL")
ref.child(encodedURL).observe(.value, with: { (snapshot) in
if let newUrl = snapshot.value as? String {
DemoSource.shared.add(urlString: newUrl)
}
d.leave()
})
d.notify(queue: .main){
self.performSegue(withIdentifier: "showHome", sender: nil)
}
}
I'm trying to create a function that parse my JSON according to the ID at the end of the URL. For example: (https://alodjinha.herokuapp.com/produto?categoriaId=1). In this case, "categoriaId=1" will return me a "Games" category as a JSON filled with Games. It should changes depending on each category the user clicks on my UICollectionView categories. So, if the user clicks in "Movies" on my UICollectionView, I gotta change the url to id 2 (for example) https://alodjinha.herokuapp.com/produto?categoriaId=2 then I'll get the JSON filled with Movies and so on. However, It's not working what I'm doing wrong?
That's how I'm trying to get the category ID:
func getCategoriaPorID(IdCategoria:Int, completion:#escaping ([CategoriaIDItems])->Void) {
let url = URL(string: "https://alodjinha.herokuapp.com/produto?categoriaId=\(IdCategoria)")
let session = URLSession.shared
let request = URLRequest(url: url!)
let dataTask = session.dataTask(with: request) { (data, response, error) in
guard let unwrappedData = data else { print("Error data"); return}
do {
let jsonTop10 = try JSONDecoder().decode(CategoriaIDItemsData.self, from: unwrappedData)
completion(jsonTop10.data)
}catch{
print("Could no get API data")
}
}
dataTask.resume()
}
Models:
import Foundation
//Categorias
struct Contents : Decodable {
let data : [Content]
}
struct Content : Decodable {
let id : Int
let descricao : String
let urlImagem : String
}
//Banner
struct BannerData : Decodable {
let data : [Banner]
}
struct Banner : Decodable {
let id : Int
let urlImagem : String
let linkUrl : String
}
//Top10
struct Top10Data:Decodable {
let data: [Top10]
}
struct Top10:Decodable {
let id : Int
let nome : String
let urlImagem : String
let descricao : String
let precoDe : Int
}
struct CategoriaIDItemsData:Decodable {
let data : [CategoriaIDItems]
}
struct CategoriaIDItems:Decodable {
let id : Int
let nome : String
let urlImagem : String
let descricao : String
let precoDe : Int
}
Well, after that I proceeded to the main file (ViewController) where contains all my tables, such as, UITableView and also UICollectionview (Where all the categories are located).
import UIKit
class ViewController: UIViewController, UICollectionViewDataSource, UITableViewDataSource, UITableViewDelegate, UICollectionViewDelegate {
#IBOutlet weak var tableViewTopSell: UITableView!
#IBOutlet var collectionView: UICollectionView!
#IBOutlet weak var collectionViewBanner: UICollectionView!
var dataSource: [Content] = [Content]()
var dataBanner: [Banner] = [Banner]()
var dataTopSold: [Top10] = [Top10]()
var dataCategoriaID: [CategoriaIDItems] = [CategoriaIDItems]()
override func viewDidLoad() {
super.viewDidLoad()
//Delegate TableView
self.tableViewTopSell.delegate = self
//SetupNavBarCustom
self.navigationController?.navigationBar.CustomNavigationBar()
let logo = UIImage(named: "tag.png")
let imageView = UIImageView(image:logo)
self.navigationItem.titleView = imageView
//CallAPIData
getTopSold { (data) in
DispatchQueue.main.async {
self.dataTopSold = data
self.tableViewTopSell.reloadData()
}
}
getBanner { (data) in
DispatchQueue.main.async {
self.dataBanner = data
self.collectionViewBanner.reloadData()
}
}
getAudiobooksAPI { (data) in
DispatchQueue.main.async {
self.dataSource = data
self.collectionView.reloadData()
}
}
}
//CollectionView
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
if (collectionView == self.collectionView) {
return self.dataSource.count
}else{
return self.dataBanner.count
}}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if (collectionView == self.collectionView) {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "collectionViewCell", for: indexPath) as! CollectionViewCell
let content = self.dataSource[indexPath.item]
cell.bookLabel.text = content.descricao
cell.bookImage.setImage(url: content.urlImagem, placeholder: "")
return cell
}else if (collectionView == self.collectionViewBanner) {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "collectionViewCellBanner", for: indexPath) as! CollectionViewCell
let content = self.dataBanner[indexPath.item]
cell.bannerImage.setImage(url: content.urlImagem, placeholder: "")
return cell
}
return UICollectionViewCell()
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
var indexPathId: Int
if (collectionView == self.collectionView) {
let content = self.dataSource[indexPath.item]
indexPathId = content.id
}else if (collectionView == self.collectionViewBanner) {
let content = self.dataBanner[indexPath.item]
indexPathId = content.id
}
getCategoriaPorID(IdCategoria: indexPathId) { (data) in
self.dataCategoriaID = data
self.performSegue(withIdentifier: "segueCategorias", sender:self.dataCategoriaID)
print(self.dataCategoriaID)
}
}
//TableView
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.dataTopSold.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "topSoldCell", for: indexPath) as! TableViewCell
let content = self.dataTopSold[indexPath.row]
cell.labelNomeTopSell.text = content.nome
cell.imageViewTopSell.setImage(url: content.urlImagem, placeholder: "")
cell.labelPrecoDe.text = "R$ \(content.precoDe)"
//Colocar strike em cima do Preco Antigo
let oldPrice = "R$ \(content.precoDe)"
let promotionString = oldPrice + ""
let attributedStr = NSMutableAttributedString(string: promotionString)
let crossAttr = [NSAttributedStringKey.strikethroughStyle: NSUnderlineStyle.styleSingle.rawValue]
attributedStr.addAttributes(crossAttr, range: NSMakeRange(0, oldPrice.count))
cell.labelPrecoDe.attributedText = attributedStr
//
cell.labelPrecoPor.text = "R$ 119.99"
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
performSegue(withIdentifier: "segueId", sender:self.dataTopSold[indexPath.row])
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "segueId" {
let des = segue.destination as? TelaDetalheProdutos
//.item possui uma propriedade instanciada na TelaDetalheProdutos
des?.item = (sender as? Top10)
//Segue para CollectionView Categorias
} else if segue.identifier == "segueCategorias" {
let desc = segue.destination as? TelaCategorias
desc?.item = (sender as? CategoriaIDItems)
}
}
}
//Cast UIImage Extension
extension UIImageView{
func setImage(url : String, placeholder: String, callback : (() -> Void)? = nil){
self.image = UIImage(named: "no-photo")
URLSession.shared.dataTask(with: NSURL(string: url)! as URL, completionHandler: { (data, response, error) -> Void in
guard error == nil else{
return
}
DispatchQueue.main.async(execute: { () -> Void in
let image = UIImage(data: data!)
self.image = image
if let callback = callback{
callback()
}
})
}).resume()
}
}
Screen that will receive the data:
import UIKit
class TelaCategorias: UIViewController, UITableViewDataSource, UITableViewDelegate {
//Class Instanciated
var item:CategoriaIDItems?
var nome = String()
override func viewDidLoad() {
super.viewDidLoad()
????
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return ???
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "tableIDCategorias", for: indexPath) as! TelaCategoriasCell
????
return cell
}
}
App picture:
Main Screen
QUESTION SOLVED SUCCESSFULLY.
FIXED
I downloaded a demo chat application that runs perfectly but when I implement it into my own app it crashes and the code is exactly the same. The app gives you the ability to create a chat room and my app works up to this point but when you click on the chat room name that now appears on the table I get the following error:
Assertion failure in -[Irish_League_Grounds.ChatViewController viewWillAppear:], /Users/ryanball/Desktop/Irish League
Grounds/Pods/JSQMessagesViewController/JSQMessagesViewController/Controllers/JSQMessagesViewController.m:277
2017-05-17 17:32:55.815 Irish League Grounds[20456:681491]
Terminating app due to uncaught exception
'NSInternalInconsistencyException', reason: 'Invalid parameter not
satisfying: self.senderDisplayName != nil'
Here is my code for the two view controllers I'm segueing between:
import UIKit
import Firebase
enum Section: Int {
case createNewChannelSection = 0
case currentChannelsSection
}
class ChannelListViewController: UITableViewController {
// MARK: Properties
var senderDisplayName: String?
var newChannelTextField: UITextField?
private var channelRefHandle: FIRDatabaseHandle?
private var channels: [Channel] = []
private lazy var channelRef: FIRDatabaseReference = FIRDatabase.database().reference().child("channels")
// MARK: View Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
title = "RW RIC"
observeChannels()
}
deinit {
if let refHandle = channelRefHandle {
channelRef.removeObserver(withHandle: refHandle)
}
}
// MARK :Actions
#IBAction func createChannel(_ sender: AnyObject) {
if let name = newChannelTextField?.text {
let newChannelRef = channelRef.childByAutoId()
let channelItem = [
"name": name
]
newChannelRef.setValue(channelItem)
}
}
// MARK: Firebase related methods
private func observeChannels() {
// We can use the observe method to listen for new
// channels being written to the Firebase DB
channelRefHandle = channelRef.observe(.childAdded, with: { (snapshot) -> Void in
let channelData = snapshot.value as! Dictionary<String, AnyObject>
let id = snapshot.key
if let name = channelData["name"] as! String!, name.characters.count > 0 {
self.channels.append(Channel(id: id, name: name))
self.tableView.reloadData()
} else {
print("Error! Could not decode channel data")
}
})
}
// MARK: Navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
super.prepare(for: segue, sender: sender)
if let channel = sender as? Channel {
let chatVc = segue.destination as! ChatViewController
chatVc.senderDisplayName = senderDisplayName
chatVc.channel = channel
chatVc.channelRef = channelRef.child(channel.id)
}
}
// MARK: UITableViewDataSource
override func numberOfSections(in tableView: UITableView) -> Int {
return 2
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if let currentSection: Section = Section(rawValue: section) {
switch currentSection {
case .createNewChannelSection:
return 1
case .currentChannelsSection:
return channels.count
}
} else {
return 0
}
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let reuseIdentifier = (indexPath as NSIndexPath).section == Section.createNewChannelSection.rawValue ? "NewChannel" : "ExistingChannel"
let cell = tableView.dequeueReusableCell(withIdentifier: reuseIdentifier, for: indexPath)
if (indexPath as NSIndexPath).section == Section.createNewChannelSection.rawValue {
if let createNewChannelCell = cell as? CreateChannelCell {
newChannelTextField = createNewChannelCell.newChannelNameField
}
} else if (indexPath as NSIndexPath).section == Section.currentChannelsSection.rawValue {
cell.textLabel?.text = channels[(indexPath as NSIndexPath).row].name
}
return cell
}
// MARK: UITableViewDelegate
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if (indexPath as NSIndexPath).section == Section.currentChannelsSection.rawValue {
let channel = channels[(indexPath as NSIndexPath).row]
self.performSegue(withIdentifier: "ShowChannel", sender: channel)
}
}
}
Here is the second view controller:
import UIKit
import Photos
import Firebase
import JSQMessagesViewController
final class ChatViewController: JSQMessagesViewController {
// MARK: Properties
private let imageURLNotSetKey = "NOTSET"
var channelRef: FIRDatabaseReference?
private lazy var messageRef: FIRDatabaseReference = self.channelRef!.child("messages")
fileprivate lazy var storageRef: FIRStorageReference = FIRStorage.storage().reference(forURL: "gs://chatchat-871d0.appspot.com")
private lazy var userIsTypingRef: FIRDatabaseReference = self.channelRef!.child("typingIndicator").child(self.senderId)
private lazy var usersTypingQuery: FIRDatabaseQuery = self.channelRef!.child("typingIndicator").queryOrderedByValue().queryEqual(toValue: true)
private var newMessageRefHandle: FIRDatabaseHandle?
private var updatedMessageRefHandle: FIRDatabaseHandle?
private var messages: [JSQMessage] = []
private var photoMessageMap = [String: JSQPhotoMediaItem]()
private var localTyping = false
var channel: Channel? {
didSet {
title = channel?.name
}
}
var isTyping: Bool {
get {
return localTyping
}
set {
localTyping = newValue
userIsTypingRef.setValue(newValue)
}
}
lazy var outgoingBubbleImageView: JSQMessagesBubbleImage = self.setupOutgoingBubble()
lazy var incomingBubbleImageView: JSQMessagesBubbleImage = self.setupIncomingBubble()
// MARK: View Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
self.senderId = FIRAuth.auth()?.currentUser?.uid
observeMessages()
// No avatars
collectionView!.collectionViewLayout.incomingAvatarViewSize = CGSize.zero
collectionView!.collectionViewLayout.outgoingAvatarViewSize = CGSize.zero
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
observeTyping()
}
deinit {
if let refHandle = newMessageRefHandle {
messageRef.removeObserver(withHandle: refHandle)
}
if let refHandle = updatedMessageRefHandle {
messageRef.removeObserver(withHandle: refHandle)
}
}
// MARK: Collection view data source (and related) methods
override func collectionView(_ collectionView: JSQMessagesCollectionView!, messageDataForItemAt indexPath: IndexPath!) -> JSQMessageData! {
return messages[indexPath.item]
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return messages.count
}
override func collectionView(_ collectionView: JSQMessagesCollectionView!, messageBubbleImageDataForItemAt indexPath: IndexPath!) -> JSQMessageBubbleImageDataSource! {
let message = messages[indexPath.item] // 1
if message.senderId == senderId { // 2
return outgoingBubbleImageView
} else { // 3
return incomingBubbleImageView
}
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = super.collectionView(collectionView, cellForItemAt: indexPath) as! JSQMessagesCollectionViewCell
let message = messages[indexPath.item]
if message.senderId == senderId { // 1
cell.textView?.textColor = UIColor.white // 2
} else {
cell.textView?.textColor = UIColor.black // 3
}
return cell
}
override func collectionView(_ collectionView: JSQMessagesCollectionView!, avatarImageDataForItemAt indexPath: IndexPath!) -> JSQMessageAvatarImageDataSource! {
return nil
}
override func collectionView(_ collectionView: JSQMessagesCollectionView!, layout collectionViewLayout: JSQMessagesCollectionViewFlowLayout!, heightForMessageBubbleTopLabelAt indexPath: IndexPath!) -> CGFloat {
return 15
}
override func collectionView(_ collectionView: JSQMessagesCollectionView?, attributedTextForMessageBubbleTopLabelAt indexPath: IndexPath!) -> NSAttributedString? {
let message = messages[indexPath.item]
switch message.senderId {
case senderId:
return nil
default:
guard let senderDisplayName = message.senderDisplayName else {
assertionFailure()
return nil
}
return NSAttributedString(string: senderDisplayName)
}
}
// MARK: Firebase related methods
private func observeMessages() {
messageRef = channelRef!.child("messages")
let messageQuery = messageRef.queryLimited(toLast:25)
// We can use the observe method to listen for new
// messages being written to the Firebase DB
newMessageRefHandle = messageQuery.observe(.childAdded, with: { (snapshot) -> Void in
let messageData = snapshot.value as! Dictionary<String, String>
if let id = messageData["senderId"] as String!, let name = messageData["senderName"] as String!, let text = messageData["text"] as String!, text.characters.count > 0 {
self.addMessage(withId: id, name: name, text: text)
self.finishReceivingMessage()
} else if let id = messageData["senderId"] as String!, let photoURL = messageData["photoURL"] as String! {
if let mediaItem = JSQPhotoMediaItem(maskAsOutgoing: id == self.senderId) {
self.addPhotoMessage(withId: id, key: snapshot.key, mediaItem: mediaItem)
if photoURL.hasPrefix("gs://") {
self.fetchImageDataAtURL(photoURL, forMediaItem: mediaItem, clearsPhotoMessageMapOnSuccessForKey: nil)
}
}
} else {
print("Error! Could not decode message data")
}
})
// We can also use the observer method to listen for
// changes to existing messages.
// We use this to be notified when a photo has been stored
// to the Firebase Storage, so we can update the message data
updatedMessageRefHandle = messageRef.observe(.childChanged, with: { (snapshot) in
let key = snapshot.key
let messageData = snapshot.value as! Dictionary<String, String>
if let photoURL = messageData["photoURL"] as String! {
// The photo has been updated.
if let mediaItem = self.photoMessageMap[key] {
self.fetchImageDataAtURL(photoURL, forMediaItem: mediaItem, clearsPhotoMessageMapOnSuccessForKey: key)
}
}
})
}
private func fetchImageDataAtURL(_ photoURL: String, forMediaItem mediaItem: JSQPhotoMediaItem, clearsPhotoMessageMapOnSuccessForKey key: String?) {
let storageRef = FIRStorage.storage().reference(forURL: photoURL)
storageRef.data(withMaxSize: INT64_MAX){ (data, error) in
if let error = error {
print("Error downloading image data: \(error)")
return
}
storageRef.metadata(completion: { (metadata, metadataErr) in
if let error = metadataErr {
print("Error downloading metadata: \(error)")
return
}
if (metadata?.contentType == "image/gif") {
mediaItem.image = UIImage.gifWithData(data!)
} else {
mediaItem.image = UIImage.init(data: data!)
}
self.collectionView.reloadData()
guard key != nil else {
return
}
self.photoMessageMap.removeValue(forKey: key!)
})
}
}
private func observeTyping() {
let typingIndicatorRef = channelRef!.child("typingIndicator")
userIsTypingRef = typingIndicatorRef.child(senderId)
userIsTypingRef.onDisconnectRemoveValue()
usersTypingQuery = typingIndicatorRef.queryOrderedByValue().queryEqual(toValue: true)
usersTypingQuery.observe(.value) { (data: FIRDataSnapshot) in
// You're the only typing, don't show the indicator
if data.childrenCount == 1 && self.isTyping {
return
}
// Are there others typing?
self.showTypingIndicator = data.childrenCount > 0
self.scrollToBottom(animated: true)
}
}
override func didPressSend(_ button: UIButton!, withMessageText text: String!, senderId: String!, senderDisplayName: String!, date: Date!) {
// 1
let itemRef = messageRef.childByAutoId()
// 2
let messageItem = [
"senderId": senderId!,
"senderName": senderDisplayName!,
"text": text!,
]
// 3
itemRef.setValue(messageItem)
// 4
JSQSystemSoundPlayer.jsq_playMessageSentSound()
// 5
finishSendingMessage()
isTyping = false
}
func sendPhotoMessage() -> String? {
let itemRef = messageRef.childByAutoId()
let messageItem = [
"photoURL": imageURLNotSetKey,
"senderId": senderId!,
]
itemRef.setValue(messageItem)
JSQSystemSoundPlayer.jsq_playMessageSentSound()
finishSendingMessage()
return itemRef.key
}
func setImageURL(_ url: String, forPhotoMessageWithKey key: String) {
let itemRef = messageRef.child(key)
itemRef.updateChildValues(["photoURL": url])
}
// MARK: UI and User Interaction
private func setupOutgoingBubble() -> JSQMessagesBubbleImage {
let bubbleImageFactory = JSQMessagesBubbleImageFactory()
return bubbleImageFactory!.outgoingMessagesBubbleImage(with: UIColor.jsq_messageBubbleBlue())
}
private func setupIncomingBubble() -> JSQMessagesBubbleImage {
let bubbleImageFactory = JSQMessagesBubbleImageFactory()
return bubbleImageFactory!.incomingMessagesBubbleImage(with: UIColor.jsq_messageBubbleLightGray())
}
override func didPressAccessoryButton(_ sender: UIButton) {
let picker = UIImagePickerController()
picker.delegate = self
if (UIImagePickerController.isSourceTypeAvailable(UIImagePickerControllerSourceType.camera)) {
picker.sourceType = UIImagePickerControllerSourceType.camera
} else {
picker.sourceType = UIImagePickerControllerSourceType.photoLibrary
}
present(picker, animated: true, completion:nil)
}
private func addMessage(withId id: String, name: String, text: String) {
if let message = JSQMessage(senderId: id, displayName: name, text: text) {
messages.append(message)
}
}
private func addPhotoMessage(withId id: String, key: String, mediaItem: JSQPhotoMediaItem) {
if let message = JSQMessage(senderId: id, displayName: "", media: mediaItem) {
messages.append(message)
if (mediaItem.image == nil) {
photoMessageMap[key] = mediaItem
}
collectionView.reloadData()
}
}
// MARK: UITextViewDelegate methods
override func textViewDidChange(_ textView: UITextView) {
super.textViewDidChange(textView)
// If the text is not empty, the user is typing
isTyping = textView.text != ""
}
}
// MARK: Image Picker Delegate
extension ChatViewController: UIImagePickerControllerDelegate, UINavigationControllerDelegate {
func imagePickerController(_ picker: UIImagePickerController,
didFinishPickingMediaWithInfo info: [String : Any]) {
picker.dismiss(animated: true, completion:nil)
// 1
if let photoReferenceUrl = info[UIImagePickerControllerReferenceURL] as? URL {
// Handle picking a Photo from the Photo Library
// 2
let assets = PHAsset.fetchAssets(withALAssetURLs: [photoReferenceUrl], options: nil)
let asset = assets.firstObject
// 3
if let key = sendPhotoMessage() {
// 4
asset?.requestContentEditingInput(with: nil, completionHandler: { (contentEditingInput, info) in
let imageFileURL = contentEditingInput?.fullSizeImageURL
// 5
let path = "\(FIRAuth.auth()?.currentUser?.uid)/\(Int(Date.timeIntervalSinceReferenceDate * 1000))/\(photoReferenceUrl.lastPathComponent)"
// 6
self.storageRef.child(path).putFile(imageFileURL!, metadata: nil) { (metadata, error) in
if let error = error {
print("Error uploading photo: \(error.localizedDescription)")
return
}
// 7
self.setImageURL(self.storageRef.child((metadata?.path)!).description, forPhotoMessageWithKey: key)
}
})
}
} else {
// Handle picking a Photo from the Camera - TODO
}
}
func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
picker.dismiss(animated: true, completion:nil)
}
}
The problem lies with this section of your code:
guard let senderDisplayName = message.senderDisplayName else {
assertionFailure()
return nil
}
assertionFailure() must link to another function that runs assert(false) or some equivalent to that. assert(_) is meant to verify that a particular parameter or comparison returns true, or if it does not, it will crash the app. The app will not crash if it is a production build (like those on the App Store) because asserts are meant for debugging purposes.
Basically, the guard statement is necessary to verify that message.senderDisplayName is unwrappable to some value (not nil). If message.senderDisplayName is nil, then there is no point in running the code below the guard and the contents of the guard should be run instead. assertionFailure() will crash the app during testing and during production it will be ignored. When it is ignored, nil will be returned for the function and it will continue on as nothing happened.
I've this annoying bug in my app that every time I load the UICollectionView controller, it duplicates the cells.
So this is the when the collection view loads for the first time:
I've 2 view controller so this is the second view controller. If I unwind back to the first view controller which only has 1 button to the 2nd view controller and segue again to the second view controller which triggers the reloadData function, this happens:
I'm not really sure why this is happening, I am using Todd Kramer's UICollectionView with cached images: http://www.tekramer.com/downloading-images-asynchronously-in-swift-with-alamofire/ The only difference is that I'm loading the image urls and data from JSON asynchronously instead of statically defining in a model or plist as the tutorial does.
Here are the classes with changes I made in the code:
import UIKit
import SwiftyJSON
private let PhotoCollectionViewCellIdentifier = "cell"
class ViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource {
#IBOutlet weak var collectionView: UICollectionView!
var index: NSIndexPath!
var names = [String]()
var imgsUrl = [String]()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let photo = PhotosDataManager()
let api = Api()
api.loadJsonData(self.names, imgsUrl: self.imgsUrl, batch: "2016", dept: "dafa") { names, imgsUrl in
self.names = names
self.imgsUrl = imgsUrl
photo.allPhotos(self.names, imgUrls: self.imgsUrl)
self.collectionView.reloadData()
}
}
#IBAction func button(sender: UIButton) {
NSNotificationCenter.defaultCenter().postNotificationName("alert1", object: self, userInfo: nil)
}
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return PhotosDataManager.sharedManager.allPhotos(self.names, imgUrls: self.imgsUrl).count
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier(PhotoCollectionViewCellIdentifier, forIndexPath: indexPath) as! CollectionViewCell
dispatch_async(dispatch_get_main_queue(), {
cell.configure(self.glacierScenicAtIndex(indexPath))
})
return cell
}
func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
PhotosDataManager.sharedManager.purgeCache()
self.performSegueWithIdentifier("previousViewController", sender: self)
}
func glacierScenicAtIndex(indexPath: NSIndexPath) -> GlacierScenic {
let photos = PhotosDataManager.sharedManager.allPhotos(self.names, imgUrls: self.imgsUrl)
return photos[indexPath.row]
}
}
class PhotosDataManager {
static let sharedManager = PhotosDataManager()
private var photos = [GlacierScenic]()
let decoder = ImageDecoder()
let photoCache = AutoPurgingImageCache(
memoryCapacity: 100 * 1024 * 1024,
preferredMemoryUsageAfterPurge: 60 * 1024 * 1024
)
func allPhotos(names: [String], imgUrls: [String]) -> [GlacierScenic] {
var glacierScenic: GlacierScenic!
for i in 0 ..< names.count {
glacierScenic = GlacierScenic(name: names[i], photoURLString: imgUrls[i])
photos.append(glacierScenic)
}
return photos
}
func getNetworkImage(urlString: String, completion: (UIImage -> Void)) -> (ImageRequest) {
let queue = decoder.queue.underlyingQueue
let request = Alamofire.request(.GET, urlString)
let imageRequest = ImageRequest(request: request)
imageRequest.request.response(
queue: queue,
responseSerializer: Request.imageResponseSerializer(),
completionHandler: { response in
guard let image = response.result.value else {
return
}
let decodeOperation = self.decodeImage(image) { image in
completion(image)
self.cacheImage(image, urlString: urlString)
}
imageRequest.decodeOperation = decodeOperation
}
)
return imageRequest
}
func decodeImage(image: UIImage, completion: (UIImage -> Void)) -> DecodeOperation {
let decodeOperation = DecodeOperation(image: image, decoder: self.decoder, completion: completion)
self.decoder.queue.addOperation(decodeOperation)
return decodeOperation
}
func cacheImage(image: Image, urlString: String) {
photoCache.addImage(image, withIdentifier: urlString)
}
func cachedImage(urlString: String) -> Image? {
return photoCache.imageWithIdentifier(urlString)
}
func purgeCache() {
// photoCache.removeAllImages()
print("memory used: \(photoCache.memoryUsage)")
}
}
class PhotosDataManager {
static let sharedManager = PhotosDataManager()
private var photos = [GlacierScenic]()
let decoder = ImageDecoder()
let photoCache = AutoPurgingImageCache(
memoryCapacity: 100 * 1024 * 1024,
preferredMemoryUsageAfterPurge: 60 * 1024 * 1024
)
func allPhotos(names: [String], imgUrls: [String]) -> [GlacierScenic] {
var glacierScenic: GlacierScenic!
for i in 0 ..< names.count {
glacierScenic = GlacierScenic(name: names[i], photoURLString: imgUrls[i])
photos.append(glacierScenic)
}
return photos
}
func getNetworkImage(urlString: String, completion: (UIImage -> Void)) -> (ImageRequest) {
let queue = decoder.queue.underlyingQueue
let request = Alamofire.request(.GET, urlString)
let imageRequest = ImageRequest(request: request)
imageRequest.request.response(
queue: queue,
responseSerializer: Request.imageResponseSerializer(),
completionHandler: { response in
guard let image = response.result.value else {
return
}
let decodeOperation = self.decodeImage(image) { image in
completion(image)
self.cacheImage(image, urlString: urlString)
}
imageRequest.decodeOperation = decodeOperation
}
)
return imageRequest
}
func decodeImage(image: UIImage, completion: (UIImage -> Void)) -> DecodeOperation {
let decodeOperation = DecodeOperation(image: image, decoder: self.decoder, completion: completion)
self.decoder.queue.addOperation(decodeOperation)
return decodeOperation
}
func cacheImage(image: Image, urlString: String) {
photoCache.addImage(image, withIdentifier: urlString)
}
func cachedImage(urlString: String) -> Image? {
return photoCache.imageWithIdentifier(urlString)
}
func purgeCache() {
// photoCache.removeAllImages()
print("memory used: \(photoCache.memoryUsage)")
}
}
ISSUE
In PhotosDataManager => You are using sharedManager reference, which saves object reference in static reference and keeps reusing it, in all segues navigation, and thus your allPhotos method, keep adding data in old array.
static let sharedManager = PhotosDataManager()
ANOTHER ISSUE
Don't Call allPhotos method in numberOfItemsInSection, as it gets called multiple number of times, which might append the data multiple number of times
SOLUTION
In your method:
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
//USE Instantiated reference
return photos.getPhotosCount()
}
Instead, inside your class class PhotosDataManager
class PhotosDataManager {
func getPhotosCount(){
return photos.count
}
}