How to make a searching result? Search bar in UICollection View - swift

I parsed a data from iTunes API with model:
struct AlbumData: Codable {
let results: [Result]
}
struct Result: Codable {
let artistName, collectionName: String
let trackCount: Int
let releaseDate: String
let artworkUrl100: String
}
struct AlbumModel {
let albumsResult: [Result]
}
and this is NetworkService code:
import Foundation
protocol NetworkServiceDelegate {
func updateInfo (_ manager: NetworkService, album: AlbumModel)
func errorInfo (error: Error)
}
struct NetworkService {
var delegate: NetworkServiceDelegate?
func fetchAlbums () {
let urlString = "https://itunes.apple.com/search?term=eminem&limit=8&entity=album"
performRequest(with: urlString)
}
func performRequest(with urlString: String) {
if let url = URL(string: urlString) {
let session = URLSession(configuration: .default)
let task = session.dataTask(with: url) { (data, response, error) in
if error != nil {
delegate?.errorInfo(error: error!)
print("Debuggg error \(LocalizedError.self)")
}
if let safeData = data {
if let albums = self.parseJSON(safeData) {
delegate?.updateInfo(self, album: albums)
print("succses - \(albums)")
}
}
}
task.resume()
}
}
func parseJSON(_ data: Data) -> AlbumModel? {
let decoder = JSONDecoder()
do {
let decodedData = try decoder.decode(AlbumData.self, from: data)
let result = decodedData.results
let album = AlbumModel(albumsResult: result)
return album
} catch {
print(error)
return nil
}
}
}
after that I appended this data into array:
var albums: [AlbumModel] = []
now I have array like this:
[Itunes_Albums.AlbumModel(albumsResult: [Itunes_Albums_.Result(artistName: "Eminem", collectionName: "The Eminem Show", trackCount: 20, releaseDate: "2002-01-01T08:00:00Z", artworkUrl100: "https://is5-ssl.mzstatic.com/image/thumb/Music115/v4/61/d5/0a/61d50a3d-4a27-187a-d16f-6b8ce4b62560/source/100x100bb.jpg"), Itunes_Albums_.Result(artistName: "Eminem", collectionName: "Recovery (Deluxe Edition)", trackCount: 19, releaseDate: "2010-06-21T07:00:00Z", artworkUrl100: "https://is5-ssl.mzstatic.com/image/thumb/Music125/v4/f1/40/ce/f140ce18-f176-7cf9-c220-3958d7747ae6/source/100x100bb.jpg")
After that I display this data into collection view
my SearchViewController
after that I want to write some characters to searchBar (name of album) and display that albums, but it doesn't works.
I can show all code:
import UIKit
class SearchViewController: UIViewController {
#IBOutlet weak var albumCollectionView: UICollectionView!
var albums: [AlbumModel] = []
var albumsForSearch: [AlbumModel] = []
var networkService = NetworkService()
override func viewDidLoad() {
super.viewDidLoad()
title = "Search"
registerCells()
networkService.delegate = self
networkService.fetchAlbums()
}
private func registerCells() {
albumCollectionView.register(UINib(nibName: AlbumCollectionViewCell.identifier, bundle: nil), forCellWithReuseIdentifier: AlbumCollectionViewCell.identifier)
}
}
//MARK: - AlbumManagerDelegate
extension SearchViewController: NetworkServiceDelegate {
func updateInfo(_ manager: NetworkService, album: AlbumModel) {
DispatchQueue.main.async {
self.albums.append(album)
self.albumsForSearch = self.albums
print("CHEEEEEK - \(self.albums)")
self.albumCollectionView.reloadData()
}
}
func errorInfo(error: Error) {
print(error)
}
}
//MARK: - UICollectionViewDelegate, UICollectionViewDataSource
extension SearchViewController: UICollectionViewDelegate, UICollectionViewDataSource {
func numberOfSections(in collectionView: UICollectionView) -> Int {
return albums.count
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return albums[section].albumsResult.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: AlbumCollectionViewCell.identifier, for: indexPath)
as! AlbumCollectionViewCell
let jData = albums[indexPath.section].albumsResult[indexPath.item]
cell.albumNameLabel.text = jData.collectionName
cell.artistNameLabel.text = jData.artistName
cell.numberOfTracksLabel.text = "\(jData.trackCount) song(s)"
cell.albumImage.load(urlString: "\(jData.artworkUrl100)")
cell.dateOfRelease.text = jData.releaseDate.substring(toIndex: 10)
return cell
}
}
//MARK: - UISearchBarDelegate
extension SearchViewController: UISearchBarDelegate, UISearchResultsUpdating {
func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
let searchView = collectionView.dequeueReusableSupplementaryView(ofKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "CollectionReusableView", for: indexPath)
return searchView
}
func updateSearchResults(for searchController: UISearchController) {
if let searchText = searchController.searchBar.text?.lowercased(), !searchText.isEmpty {
albumsForSearch = albums.filter { album -> Bool in
return album.albumsResult.contains { item -> Bool in item.collectionName.lowercased().contains(searchText)
}
}
albumCollectionView.reloadData()
} else {
albumsForSearch = albums
}
}
}
When I write some characters into searchBar nothing changes

Not sure what exactly it is you're struggling with so ill just point out a few things and hope this will solve your issue.
First, I don't know what your AlbumModel looks like. Perhaps you can show this code. but it looks like each instance contains more than one albumsResult. Are you expecting exactly one object in albumsResult in this array?
If not, I find this line really strange item.albumsResult[0]. That 0 there is a potential for crashes. So my answer doesn't assume there will always be one (And only one) item in that array.
Second, I also find this strange:
} else {
searching = false
albumsForSearch.removeAll()
albumsForSearch = albums
}
You're resetting your array every time you don't find a result. Not sure I can think of a valid reason why you would ever want to do that.
So perhaps this code is what you need:
if let searchText = let searchText = searchController.searchBar.text?.lowercased(), !searchText.isEmpty {
// Search text means filter results
albumsForSearch = albums.filter { album -> Bool in
return album.albumResult.contains { item -> Bool in
item.collectionName.lowercased().contains(searchText)
}
}
} else {
// No search text means display everything
albumsForSearch = albums
}
Then you just base your tableview sections and rows on albumsForSearch

Related

Filtering Image Data with Search Bar

I am trying to filter the data from API. The is successful loaded into view controller with table view cell . This is a movie applications . I am trying to filter the data based on the user type into the text box . I mentioned in the code filter my the title of the movie but The code is only able to filter the title and overview of the movie but the Image fields remain unfiltered such as image , overview etc. Here is the struct model .
import Foundation
struct Movie: Decodable {
let originalTitle: String
let overview: String
let posterPath: String
enum CodingKeys: String, CodingKey {
case originalTitle = "original_title"
case overview
case posterPath = "poster_path"
}
}
Here is the protocol class code .
import Foundation
class MoviePresenter: MoviePresenterProtocol {
private let view: MovieViewProtocol
private let networkManager: NetworkManager
var movies = [Movie]()
private var cache = [Int: Data]()
var rows: Int {
return movies.count
}
init(view: MovieViewProtocol, networkManager: NetworkManager = NetworkManager()) {
self.view = view
self.networkManager = networkManager
}
func getMovies() {
let url = "https://api.themoviedb.org/3/movie/popular?language=en-US&page=3&api_key=6622998c4ceac172a976a1136b204df4"
networkManager.getMovies(from: url) { [weak self] result in
switch result {
case .success(let response):
self?.movies = response.results
self?.downloadImages()
DispatchQueue.main.async {
self?.view.resfreshTableView()
}
case .failure(let error):
DispatchQueue.main.async {
self?.view.displayError(error.localizedDescription)
}
}
}
}
func getTitle(by row: Int) -> String? {
return movies[row].originalTitle
}
func getOverview(by row: Int) -> String? {
return movies[row].overview
}
func getImageData(by row: Int) -> Data? {
return cache[row]
}
private func downloadImages() {
let baseImageURL = "https://image.tmdb.org/t/p/w500"
let posterArray = movies.map { "\(baseImageURL)\($0.posterPath)" }
let group = DispatchGroup()
group.enter()
for (index, url) in posterArray.enumerated() {
networkManager.getImageData(from: url) { [weak self] data in
if let data = data {
self?.cache[index] = data
}
}
}
group.leave()
group.notify(queue: .main) { [weak self] in
self?.view.resfreshTableView()
}
}
}
Here is the controller code .
import UIKit
class MovieViewController: UIViewController, UISearchBarDelegate {
#IBOutlet weak var userName: UILabel!
#IBOutlet weak var tableView: UITableView!
#IBOutlet weak var searchBar: UISearchBar!
private var presenter: MoviePresenter!
var finalname = ""
override func viewDidLoad() {
super.viewDidLoad()
userName.text = "Hello: " + finalname
setUpUI()
presenter = MoviePresenter(view: self)
searchBarText()
}
private func setUpUI() {
tableView.dataSource = self
tableView.delegate = self
}
private func searchBarText() {
searchBar.delegate = self
}
#IBAction func selectSegment(_ sender: UISegmentedControl) {
if sender.selectedSegmentIndex == 1{
setUpUI()
presenter = MoviePresenter(view: self)
presenter.getMovies()
}
}
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
if searchText == ""{
presenter.getMovies()
}
else {
presenter.movies = presenter.movies.filter({ movies in
return movies.originalTitle.lowercased().contains(searchText.lowercased())
})
}
tableView.reloadData()
}
}
extension MovieViewController: MovieViewProtocol {
func resfreshTableView() {
tableView.reloadData()
}
func displayError(_ message: String) {
let alert = UIAlertController(title: "Error", message: message, preferredStyle: .alert)
let doneButton = UIAlertAction(title: "Done", style: .default, handler: nil)
alert.addAction(doneButton)
present(alert, animated: true, completion: nil)
}
}
extension MovieViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
presenter.rows
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: MovieViewCell.identifier, for: indexPath) as! MovieViewCell
let row = indexPath.row
let title = presenter.getTitle(by: row)
let overview = presenter.getOverview(by: row)
let data = presenter.getImageData(by: row)
cell.configureCell(title: title, overview: overview, data: data)
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let dc = storyboard?.instantiateViewController(withIdentifier: "MovieDeatilsViewController") as! MovieDeatilsViewController
let row = indexPath.row
dc.titlemovie = presenter.getTitle(by: row) ?? ""
dc.overview = presenter.getOverview(by: row) ?? ""
dc.imagemovie = UIImage(data: presenter.getImageData(by: row)!)
self.navigationController?.pushViewController(dc, animated: true)
}
}
extension MovieViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableView.automaticDimension
}
}
Here is the screenshot of the result .
Caching image in tableview is a little bit tricky, and you may get problem when the cell changes or reusing itself,
that's cause you see same image when texts are different.
there are 2 famous package you can use it for you're problem and it's easy to use with a lot of options.
1- Kingfisher
2- SDWebImage

How to Increase count of Page in Url for loading more data and show indicator at bottom?

I Creating a demo of webservices, In this I want to increase page count and load more data from api, and add in table view after activity indicator refreshing. I find many tutorials but Not found useful... They are all Advance and I'm beginner so i didn't get properly. Can Any one please tell how to do this.
Here's My Demo details...
This Is Page Count of URL
"info": {
"count": 826,
"pages": 42,
"next": "https://rickandmortyapi.com/api/character/?page=3",
"prev": "https://rickandmortyapi.com/api/character/?page=1"
},
My json Model
import UIKit
import Foundation
// MARK: - JsonModel
struct JSONModel:Decodable {
let info: Info
let results: [Result]
}
// MARK: - Info
struct Info : Decodable {
let count, pages: Int
let next: String
let prev: NSNull
}
// MARK: - Result
struct Result : Decodable {
let id: Int
let name: String
let status: Status
let species: Species
let type: String
let gender: Gender
let origin, location: Location
let image: String
let episode: [String]
let url: String
let created: String
}
enum Gender {
case female
case male
case unknown
}
// MARK: - Location
struct Location {
let name: String
let url: String
}
enum Species {
case alien
case human
}
enum Status {
case alive
case dead
case unknown
}
This is my View controller Class
import UIKit
import Kingfisher
class ViewController: UIViewController,UISearchBarDelegate{
#IBOutlet weak var searchBar: UISearchBar!
#IBOutlet weak var tableView: UITableView!
var results = [Results]()
var filteredData = [Results]()
var batchSize = 42
var fromIndex = 0
override func viewDidLoad() {
super.viewDidLoad()
searchBar.delegate = self
tableView.delegate = self
tableView.dataSource = self
apiCalling()
filteredData = results
}
override func viewWillAppear(_ animated: Bool) {
filteredData = results
self.tableView.reloadData()
}
func apiCalling(){
guard let url = URL(string: "https://rickandmortyapi.com/api/character/") else { return }
URLSession.shared.dataTask(with: url) {[weak self]data, response, error in
if error != nil{
print("error While Fetching Data")
}
guard let data = data else {
return
}
do {
let resultData = try JSONDecoder().decode(JsonModel.self, from: data)
self?.results = resultData.results!
self?.filteredData = self!.results
DispatchQueue.main.async {
self?.tableView.reloadData()
}
} catch {
print(error.localizedDescription)
}
}.resume()
}
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
let searchText = searchBar.text!
guard !searchText.isEmpty else {
filteredData = results
tableView.reloadData()
return
}
filteredData = results.filter({ $0.name!.lowercased().contains(searchText.lowercased() ) })
tableView.reloadData()
}
func searchBarTextDidBeginEditing(_ searchBar: UISearchBar) {
self.searchBar.showsCancelButton = true
}
func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
searchBar.showsCancelButton = false
searchBar.text = ""
searchBar.resignFirstResponder()
filteredData.removeAll()
self.tableView.reloadData()
}
}
This My Tableview Extension
extension ViewController : UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return filteredData.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell") as! UserTableViewCell
let row = filteredData[indexPath.row]
let imageUrl = URL(string: row.image!)
cell.userImage.kf.setImage(with: imageUrl)
cell.lblGender.text = "Gender:- \(row.gender ?? "no value")"
cell.lblID.text = "ID:- \(row.id ?? 0)"
cell.lblName.text = "Name: \(row.name!)"
cell.lblSpecies.text = "Species:- \(row.species ?? "No Speies")"
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableView.automaticDimension
}
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
return 250
}
}
u need save page info.
self?.info = resultData.info!
call "loadpage" when u loading more data
override func viewDidLoad() {
super.viewDidLoad()
searchBar.delegate = self
tableView.delegate = self
tableView.dataSource = self
filteredData = []
result = []
apiCalling(apiurl:"https://rickandmortyapi.com/api/character/")
}
func apiCalling(apiurl:String){
guard let url = URL(string: apiurl) else { return }
URLSession.shared.dataTask(with: url) {[weak self]data, response, error in
if error != nil{
print("error While Fetching Data")
}
guard let data = data else {
return
}
do {
let resultData = try JSONDecoder().decode(JsonModel.self, from: data)
self?.results.append(resultData.results!)
self?.info = resultData.info!
filterWord()
} catch {
print(error.localizedDescription)
}
}.resume()
}
func filterWord(){
let searchText = searchBar.text!
guard !searchText.isEmpty else {
filteredData = results
tableView.reloadData()
return
}
filteredData = results.filter({ $0.name!.lowercased().contains(searchText.lowercased() ) })
tableView.reloadData()
}
func loadPage(){
guard let page = self?.info.next,!page.isEmpty else{
return
}
apiCalling(apiurl:page)
}
under indicator simple example like this
func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
guard let page = self?.info.next,!page.isEmpty else{
return nil
}
//press to call loadPage
let loading = UIButton.init()
let view = UIView.init()
view.addSubview(loading)
return view
}
I'm Giving My own Questions answer Here...
I Have Create 3 more variables
var curentIndex : Int = 0
// I'm Putting Default Limit Here...
var numberArray = Array(1...42)
var fetchingMore = false
Api Call
func apiCalling(){
guard !fetchingMore else {
print("Didn't call Get Data")
return
}
fetchingMore = true
guard let url = URL( string: "\(baseUrl)?page=\(numberArray[curentIndex])") ?? URL(string: "" ) else {
fetchingMore = false
return
}
curentIndex += 1
URLSession.shared.dataTask(with: url) {[weak self]data, response, error in
if error != nil{
print("error While Fetching Data")
}
guard let data = data else {
return
}
do {
let resultData = try JSONDecoder().decode(JsonModel.self, from: data)
self?.results += resultData.results!
self?.filteredData = self!.results
DispatchQueue.main.async {
self?.tableView.reloadData()
}
} catch {
print(error.localizedDescription)
}
self?.fetchingMore = false
}.resume()
}
**Here's My CellForRowMethod **
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell") as! UserTableViewCell
let row = filteredData[indexPath.row]
if indexPath.row == filteredData.count - 1 && curentIndex <= row.id ?? 0 {
apiCalling()
}
let imageUrl = URL(string: row.image!)
cell.userImage.kf.setImage(with: imageUrl)
cell.lblGender.text = "Gender:- \(row.gender ?? "no value")"
cell.lblID.text = "ID:- \(row.id ?? 0)"
cell.lblName.text = "Name: \(row.name!)"
cell.lblSpecies.text = "Species:- \(row.species ?? "No Speies")"
return cell
}

Implement UISearchBarController within UICollectionViewController (Swift 4)?

I'm trying to implement an UISearchBarController in UICollectionViewController, here are the codes:
class FirstViewController: UICollectionViewController, UICollectionViewDelegateFlowLayout, UISearchResultsUpdating {
var titles: [String] = []
var card: [RecommendArticles]?
var searchArr = [String](){
didSet {
self.collectionView?.reloadData()
}
}
func updateSearchResults(for searchController: UISearchController) {
guard let searchText = searchController.searchBar.text else {
return
}
searchArr = titles.filter { (title) -> Bool in
return title.contains(searchText)
}
}
let searchController = UISearchController(searchResultsController: nil)
override func viewDidLoad() {
super.viewDidLoad()
searchController.searchResultsUpdater = self
searchController.dimsBackgroundDuringPresentation = false
navigationItem.hidesSearchBarWhenScrolling = true
self.navigationItem.searchController = searchController
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
if (searchController.isActive) {
return searchArr.count
} else {
return self.counters
}
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: verticalCellId, for: indexPath) as! VerticalCellId
if (searchController.isActive) {
cell.titleLabel.text = searchArr[indexPath.row]
return cell
} else {
if let link = self.card?[indexPath.item]._imageURL {
let url = URL(string: link)
cell.photoImageView.kf.setImage(with: url)
}
if let title = self.card?[indexPath.item]._title {
cell.titleLabel.text = title
titles.append(title)
print("\(titles)")
}
if let source = self.card?[indexPath.item]._source {
cell.sourceLabel.text = source
}
return cell
}
}
Here are the errors locate:
titles.append(title)
print("\(titles)")
When I search a keyword, the filtered results are incorrect, because the collection view cell will change dynamically (I'm not sure if my expression is accurate).
But, if I set up the variable titles like this, it works perfectly:
var titles = ["Tom","Jack","Lily"]
But I have to retrieve the values of titles from internet. Any suggestions would be appreciated.
Here are the update question for creating an array after self.card:
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(true)
self.view.addSubview(activityView)
activityView.hidesWhenStopped = true
activityView.center = self.view.center
activityView.startAnimating()
let dynamoDbObjectMapper = AWSDynamoDBObjectMapper.default()
dynamoDbObjectMapper.load(TheUserIds2018.self, hashKey: "TheUserIds2018", rangeKey: nil, completionHandler: { (objectModel: AWSDynamoDBObjectModel?, error: Error?) -> Void in
if let error = error {
print("Amazon DynamoDB Read Error: \(error)")
return
}
DispatchQueue.main.async {
if let count = objectModel?.dictionaryValue["_articleCounters"] {
self.counters = count as! Int
self.collectionView?.reloadData()
self.updateItems()
self.activityView.stopAnimating()
}
}
})
}
func updateItems() {
let dynamoDbObjectMapper = AWSDynamoDBObjectMapper.default()
var tasksList = Array<AWSTask<AnyObject>>()
for i in 1...self.counters {
tasksList.append(dynamoDbObjectMapper.load(RecommendArticles.self, hashKey: "userId" + String(self.counters + 1 - i), rangeKey: nil))
}
AWSTask<AnyObject>.init(forCompletionOfAllTasksWithResults: tasksList).continueWith { (task) -> Any? in
if let cards = task.result as? [RecommendArticles] {
self.card = cards
DispatchQueue.main.async {
if let totalCounts = self.card?.count {
for item in 0...totalCounts - 1 {
if let title = self.card?[item]._title {
self.newTitle = title
}
self.titles.append(self.newTitle)
print("titles: \(self.titles)")
}
}
}
} else if let error = task.error {
print(error.localizedDescription)
}
return nil
}
}
This is not an issue with UISearchController . it is with your data. Collection view data source method -
cellForItemAtIndexPath works only when cell is presented in the view. So you can't keep the code for creating array of titles in the cellForItemAtIndexPath
Instead of separately keeping title strings in array for search you can directly filter self.card array with search text in title with below code
self.searchArr = self.card?.filter(NSPredicate(format:"_title CONTAINS[cd] %#",searchText)).sorted(byKeyPath: "_title", ascending: true)
or
you can create array of titles before loading collection view
Some where after you create self.card

Firestore addSnapshotListener Replacing Object Data upon New Object Entry

I have the following function;
func observePosts() {
let postReference = Firestore.firestore().collection("Posts").limit(to: 20).order(by: "postTimestamp", descending: true)
postReference.addSnapshotListener { (snapshot, error) in
if error != nil {
print(error as Any)
return
} else {
guard let snapshot = snapshot else { return }
let models = snapshot.documents.flatMap({Post(dictionary: $0.data())})
self.posts = models
DispatchQueue.main.async {
self.feedCollection.reloadData()
}
}
}
}
This returns all of the documents under the collection.
However, when I add another object as per the below;
uploadMedia { (url) in
if url != nil {
let postIDReference = self.postsReference.collection("Posts").document().documentID
self.postsReference.collection("Posts").document().setData([
"postID" : postIDReference,
"postImageURL" : url!,
"postTimestamp" : 1234567890
], completion: { (error) in
if error != nil {
print(error as Any)
return
} else {
picker.dismiss(animated: true, completion: nil)
}
})
}
}
}
This overrides a cell with the new setData object. I've read some similar issues on SO but most relate to not having an auto-ID key which
self.postsReference.collection("Posts").document().setData(
gives me.
What I've tried:
Terminating the app and reopening gives me all the objects in Firestore.
Firestore dashboard has all the objects (so there isn't a problem with missing data).
Here are my Delegate & Datasource Methods
extension FeedController: UICollectionViewDelegate, UICollectionViewDataSource
{
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection
section: Int) -> Int {
return posts.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt
indexPath: IndexPath) -> UICollectionViewCell {
let feedCell = feedCollection.dequeueReusableCell(withReuseIdentifier:
"cell", for: indexPath) as! PostCell
let post = posts[indexPath.item]
feedCell.post = post
return feedCell
}
}
CollectionView Cell
import UIKit
import SDWebImage
class PostCell: UICollectionViewCell {
#IBOutlet weak var postImage: UIImageView!
var post: Post? {
didSet {
guard let post = post else { return }
let postImageURL: URL = URL(string: (post.postImageURL))!
postImage.sd_setImage(with: postImageURL) { (image, error, cache, url)
in
self.postImage.image = image
}
}
}
}
Any help appreciated - Many thanks as always.
The issue was my custom flowLayout. I believe I need to invalidate the layout when retrieving new items.

Display image to 2 Collection View in 1 View Controller

i want to display 2 images i get from different url and different API using alamofire in 2 collection view. i have 2 collection view in single view controller. first API need parameters while second API doesn't need paramters so i declare parameters once.
here's my code
class ViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource {
var url = [String]()
var secondUrl = [String]()
let parameters = [
"data": 1
]
let collectionViewAIdentifier = "CollectionViewACell"
let collectionViewBIdentifier = "CollectionViewBCell"
#IBOutlet weak var mainCollectionView: UICollectionView!
#IBOutlet weak var secondMainCollectionView: UICollectionView!
let collectionViewA = UICollectionView()
let collectionViewB = UICollectionView()
override func viewDidLoad() {
super.viewDidLoad()
collectionViewA.delegate = self
collectionViewB.delegate = self
collectionViewA.dataSource = self
collectionViewB.dataSource = self
self.view.addSubview(collectionViewA)
self.view.addSubview(collectionViewB)
Alamofire.request(.POST, "URL", parameters: parameters).responseJSON { response in
if let value = response.result.value {
let json = JSON(value)
let data = json["data"].arrayValue
let status = json["api_status"].intValue
if status == 1 {
print(json["api_message"].stringValue)
for datas in data {
self.url.append(datas["companies_photo"].stringValue)
}
} else {
print(json["api_message"].stringValue)
}
self.mainCollectionView.reloadData()
}
}
Alamofire.request(.POST, "URL").responseJSON { response in
if let value = response.result.value {
let json = JSON(value)
let data = json["data"].arrayValue
let status = json["api_status"].intValue
if status == 1 {
print(json["api_message"].stringValue)
for datas in data {
self.secondUrl.append(datas["image"].stringValue)
}
} else {
print(json["api_message"].stringValue)
}
self.secondMainCollectionView.reloadData()
}
}
}
func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
if collectionView == self.collectionViewA {
return url.count
}
return secondUrl.count
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
if collectionView == self.collectionViewA {
let cellA = collectionView.dequeueReusableCellWithReuseIdentifier(collectionViewAIdentifier, forIndexPath: indexPath) as! MainCollectionViewCell
let imageUrl:NSURL? = NSURL(string: url[indexPath.row])
if let url = imageUrl {
cellA.mainImageView.sd_setImageWithURL(url)
}
return cellA
}else {
let cellB = collectionView.dequeueReusableCellWithReuseIdentifier(collectionViewBIdentifier, forIndexPath: indexPath) as! SecondCollectionViewCell
let imageUrl:NSURL? = NSURL(string: secondUrl[indexPath.row])
if let url = imageUrl {
cellB.secondMainImageView.sd_setImageWithURL(url)
}
return cellB
}
}
i have each image outlet on different CollectionViewCell.swift
the build succeded, but it print SIGABRT error with debug
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'UICollectionView must be initialized with a non-nil layout parameter'
You haven't implemented uicollectionviewflowlayoutdelegate methods... SizeforItemAtINDEX
You forgot to register class
collectionViewA.registerClass(UICollectionViewCell.self, forCellWithReuseIdentifier: collectionViewAIdentifier)
collectionViewB.registerClass(UICollectionViewCell.self, forCellWithReuseIdentifier: collectionViewBIdentifier)