I am new to swift,
I am working on an application
I Have 2 different API to get banner data and tableView Data, for the Images I am getting URL from server .
After the Banner is loaded and running I am populating the UITableView,
problem is either the UITableView visible after sometime (around 1 min) or I have to tap on the screen to make it visible
Also the banner is not scrolling properly when I switch between screens.
Can someone review the code and help me whats wrong.
Here is what I have done so far
Controller Code
class CompanyViewController: UIViewController,IndicatorInfoProvider {
#IBOutlet weak var tableView: UITableView!
#IBOutlet weak var sliderCollectionView: UICollectionView!
#IBOutlet weak var pageView: UIPageControl!
var activityIndicator: UIActivityIndicatorView=UIActivityIndicatorView()
var imgArr = [UIImage(named: "LoginHeader")]
var imgArrTemp = [String]()
var sectionHeaderName = [String]()
var imageArray = [[String]]()
var sectionImage = [String]()
var timer = Timer()
var counter = 0
override func viewDidLoad() {
super.viewDidLoad()
pageView.numberOfPages = imgArr.count
pageView.currentPage = 0
tableView.delegate = self
tableView.dataSource = self
tableView.estimatedRowHeight = 120
tableView.tableFooterView = UIView()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
self.getBannerData()
DispatchQueue.main.async() {
self.getCenterData()
self.tableView.reloadData()
}
}
func indicatorInfo(for pagerTabStripController: PagerTabStripViewController) -> IndicatorInfo {
return IndicatorInfo(title: "COMPANY")
}
#objc func changeImage() {
if counter < imgArr.count {
let index = IndexPath.init(item: counter, section: 0)
self.sliderCollectionView.scrollToItem(at: index, at: .centeredHorizontally, animated: true)
pageView.currentPage = counter
counter += 1
} else {
counter = 0
let index = IndexPath.init(item: counter, section: 0)
self.sliderCollectionView.scrollToItem(at: index, at: .centeredHorizontally, animated: true)
pageView.currentPage = counter
counter = 1
}
}
func getCenterData(){
let appId = LocalStorage.getStringDataFromLocalStorage(key:Constants.APPID)
let token = "Bearer "+LocalStorage.getStringDataFromLocalStorage(key: Constants.TOKEN)
if let url = URL(string: Constants.EXPERIENCE_CENTER+appId){
var request = URLRequest(url: url)
request.httpMethod = "GET"
request.setValue(token, forHTTPHeaderField:"Authorization")
APIManager.sharedInstance.getCall(request: request){
(data) in
if(!data.isEmpty){
do{
let _object = try JSONDecoder().decode(ExperienceCenterModel.self, from: Data(data.utf8))
let data = try JSONEncoder().encode(_object.practice)
let jsonString = String(data: data, encoding: .utf8)!
LocalStorage.saveStringDataInLocalStorage(key: Constants.PRACTICE, value: jsonString)
let technology = try JSONEncoder().encode(_object.technology)
let technologyJsonString = String(data: technology, encoding: .utf8)!
LocalStorage.saveStringDataInLocalStorage(key: Constants.TECTNOLOGY, value: technologyJsonString)
for sectionHeader in _object.practice.data{
self.sectionHeaderName.insert(sectionHeader.practiceName!, at: self.sectionHeaderName.count)
self.sectionImage.removeAll()
for images in sectionHeader.experience{
let url = images.experienceImage.addingPercentEncoding(withAllowedCharacters: CharacterSet.urlQueryAllowed)
self.sectionImage.insert(url!, at: self.sectionImage.count)
}
self.imageArray.insert(self.sectionImage, at: self.imageArray.count)
}
self.tableView.reloadData()
}catch{
let nsError = error as NSError
print(nsError.localizedDescription)
}
}
}
}
}
func getBannerData(){
let defaults = UserDefaults.standard
let appId = defaults.string(forKey: Constants.APPID)
let token = "Bearer "+defaults.string(forKey: Constants.TOKEN)!
if let url = URL(string: Constants.EXPERIENCE_BANNER_CENTER+appId!){
var request = URLRequest(url: url)
request.httpMethod = "GET"
request.setValue(token, forHTTPHeaderField:"Authorization")
APIManager.sharedInstance.getCall(request: request){
(data) in
if(!data.isEmpty){
do{
let _object = try JSONDecoder().decode(SliderModel.self, from: Data(data.utf8))
self.imgArr.removeAll()
for images in _object.data{
let url = images.experience_image.addingPercentEncoding(withAllowedCharacters: CharacterSet.urlQueryAllowed)
let imageUrlString = url
let imageUrl:URL = URL(string: imageUrlString!)!
// self.imgArrTemp.insert(url!, at: self.imgArrTemp.count)
// self.pageView.numberOfPages = self.imgArrTemp.count
DispatchQueue.main.async() {
let imageData:NSData = NSData(contentsOf: imageUrl)!
self.imgArr.insert(UIImage(data: imageData as Data), at: self.imgArr.count)
self.pageView.numberOfPages = self.imgArr.count
}
}
self.stopActivityIndicator()
}
catch{
self.stopActivityIndicator()
self.showAlertMessage(alertMessage: Constants.COMMON_ERROR)
}
DispatchQueue.main.async {
self.timer = Timer.scheduledTimer(timeInterval: 2.0, target: self, selector: #selector(self.changeImage), userInfo: nil, repeats: true)
}
}
}
}
}
func startActivityIndicator(){
activityIndicator.center = CGPoint(x: 180, y: -40)
activityIndicator.hidesWhenStopped = true
activityIndicator.style = UIActivityIndicatorView.Style.gray
view.addSubview(activityIndicator)
activityIndicator.startAnimating()
UIApplication.shared.beginIgnoringInteractionEvents()
}
func stopActivityIndicator(){
activityIndicator.stopAnimating()
UIApplication.shared.endIgnoringInteractionEvents()
}
func showAlertMessage(alertMessage:String){
self.stopActivityIndicator()
let alert = UIAlertController(title: "Alert", message: alertMessage, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .default, handler: { action in
switch action.style{
case .default:
print("default")
case .cancel:
print("cancel")
case .destructive:
print("destructive")
}}))
self.present(alert, animated: true, completion: nil)
}
}
extension CompanyViewController: UICollectionViewDelegate, UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 10
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath)
if(self.imgArr.count > 0){
if let vc = cell.viewWithTag(111) as? UIImageView {
if(self.imgArr.count == indexPath.row){
vc.image = self.imgArr[indexPath.row-1]
}else{
vc.image = self.imgArr[indexPath.row]
}
}
}
return cell
}
}
extension CompanyViewController: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
return UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let size = sliderCollectionView.frame.size
return CGSize(width: size.width, height: size.height)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 0.0
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
return 0.0
}
}
extension CompanyViewController: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
if self.sectionHeaderName.count>0 {
return self.sectionHeaderName[section]
}else{
return ""
}
}
//NUMBER OF SECTION WE WANT IN A TABLE
func numberOfSections(in tableView: UITableView) -> Int {
return self.sectionHeaderName.count
}
/*NUMNBER OF ROWS IN A SECTION*/
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 130
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "tableViewCell", for: indexPath) as? TableViewCell else {fatalError("Unable to create table view cell")}
cell.cellDataURI = self.imageArray[indexPath.section]
cell.collectionView.reloadData()
return cell
}
}
Code for Table View Cell
class TableViewCell: UITableViewCell, UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
var cellData = [UIImage]()
var cellDataURI = [String]()
lazy var imageCache: NSCache<NSString,UIImage> = {
return NSCache<NSString,UIImage>.init()
}()
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return self.cellDataURI.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "collectionCell", for: indexPath) as? CollectionViewCell
let url = cellDataURI[indexPath.row]
guard let image = self.imageCache.object(forKey: NSString(string: url))else{
return loadImage(url: URL(string: url)!, cell: cell!, indexPath: indexPath)
}
cell?.imageView.image = image//cellDataURI[indexPath.row]
return cell!
}
func loadImage(url: URL,cell: CollectionViewCell, indexPath: IndexPath) -> CollectionViewCell{
URLSession.shared.dataTask(with: url){
(rawData, _, _) in
guard let data = rawData else { return }
guard let image = UIImage.init(data: data) else{
return
}
self.imageCache.setObject(image, forKey: NSString(string: url.absoluteString) )
DispatchQueue.main.async {
guard let visCell = self.collectionView.cellForItem(at: indexPath) as? CollectionViewCell else{ return }
visCell.imageView.image = image
}
}.resume()
return cell
}
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize{
let size = CGSize(width: 150, height: 300)
return size
}
#IBOutlet weak var collectionView: UICollectionView!
override func awakeFromNib() {
super.awakeFromNib()
self.collectionView.delegate = self
self.collectionView.dataSource = self
}
}
Any help would be appreciated
You can use dispatch group to run multiple apis and get notification when all tasks are completed. Then reload tableview
let dispatchGroup = DispatchGroup()
dispatchGroup.enter()
firstApiCall(completion:{
dispatchGroup.leave()
})
dispatchGroup.enter()
secondApiCall(completion:{
dispatchGroup.leave()
})
dispatchGroup.notify(queue: .main) {[weak self] in
print("All Data fetched")
self?.tableView.reloadData()
}
Related
I have this two structs, the data I am getting from a Firebase Database and I don't know how to get each tableview cell to display a different collection view, I have both cells (TableviewCell and CollectionViewCell) in xib Files but can't get them the way I want it to.
I have attached my ViewController where they are both in and the TableViewCell class. Please help
struct Clothes {
var price: Double
var imgClUrl: String
var id: String
var isActive: Bool = true
var timeStamp: Timestamp
var stock: Double
var name: String
init(data : [String : Any]) {
self.price = data["price"] as? Double ?? 0
self.imgClUrl = data["imgClUrl"] as? String ?? ""
self.id = data["id"] as? String ?? ""
self.isActive = data["isActive"] as? Bool ?? true
self.timeStamp = data["timeStamp"] as? Timestamp ?? Timestamp()
self.stock = data["stock"] as? Double ?? 0
self.name = data["name"] as? String ?? ""
}
}
struct TypesOfCLothing {
var title : String
var id : String
var clothes : [Clothes]
init (data : [String : Any]){
self.title = data["title"] as? String ?? ""
self.id = data["id"] as? String ?? ""
self.clothes = data["Clothes"] as! [Clothes]
}
}
class eachStoreTableViewCell: UITableViewCell{
#IBOutlet weak var eachRowCollView: UICollectionView!
#IBOutlet weak var eachRowLbl: UILabel!
var clothes = [Clothes]()
override func awakeFromNib() {
super.awakeFromNib()
eachRowCollView?.reloadData()
eachRowCollView?.delegate = self
eachRowCollView?.dataSource = self
self.eachRowCollView?.register(UINib(nibName: "RowColectionView", bundle: nil), forCellWithReuseIdentifier: Identifiers.ClothesCell)
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
}
func configureCell(clothingType: TypesOfCLothing) {
eachRowLbl?.text = clothingType.title}}
extension eachStoreTableViewCell: UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout{
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return clothes.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if let cell = collectionView.dequeueReusableCell(withReuseIdentifier: Identifiers.ClothesCell, for: indexPath) as? RowColectionView{
cell.configureCell(clothes: clothes[indexPath.item])
return cell
}
return UICollectionViewCell()
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: 160, height: 180)
}
class EachStoreVC: UIViewController {
#IBOutlet weak var StoreTblView: UITableView!
var typesOfClothing = [TypesOfCLothing]()
var tienda: Tiendas!
let db = Firestore.firestore()
var listener : ListenerRegistration!
override func viewDidLoad() {
super.viewDidLoad()
// StoreTblView?.delegate = self
// StoreTblView?.dataSource = self
StoreTblView?.register(UINib(nibName: "eachStoreTblViewCell", bundle: nil), forCellReuseIdentifier: Identifiers.ClothingTypeCell)
StoreTblView?.cellLayoutMarginsFollowReadableWidth = false
}
override func viewDidAppear(_ animated: Bool) {
}
override func viewDidDisappear(_ animated: Bool) {
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}}
extension EachStoreVC : UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return typesOfClothing.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if let cell = tableView.dequeueReusableCell(withIdentifier: Identifiers.ClothingTypeCell, for: indexPath) as? eachStoreTableViewCell {
cell.configureCell(clothingType: typesOfClothing[indexPath.row])
cell.frame = CGRect(x: 0, y: 0, width: StoreTblView.frame.size.width, height: cell.frame.size.height)
return cell
}
return UITableViewCell()
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 200
}}
EDIT
The collectionView and Tableview now show fine but I'm having trouble getting the data from firebase, I have an array of document references inside each TypesOfClothing document, the code is as follows. I have also updated my 2 Structs.
func getRowData(){
listener = db.collection("TypesOfClothing").addSnapshotListener({ (snap, error) in
if let error = error {
debugPrint(error.localizedDescription)
return
}
snap?.documentChanges.forEach({ (change) in
let data = change.document.data()
let typesOfClothing = TypesOfCLothing.init(data: data)
switch change.type {
case.added:
self.onDocumentAdded(change: change, category: typesOfClothing)
case.modified:
self.onDocumentModified(change: change, category: typesOfClothing)
case.removed:
self.onDocumentRemoved(change: change)
}
})
}) }
func onDocumentAdded(change: DocumentChange, category: TypesOfCLothing){
let newIndex = Int(change.newIndex)
typesOfClothing.insert(category, at: newIndex)
StoreTblView?.insertRows(at: [IndexPath(row: newIndex, section: 0)], with: UITableView.RowAnimation.left)
}
func onDocumentModified(change: DocumentChange, category: TypesOfCLothing){
if change.newIndex == change.oldIndex {
let index = Int(change.newIndex)
typesOfClothing[index] = category
StoreTblView?.reloadRows(at: [IndexPath(row: index, section: 0)], with: UITableView.RowAnimation.left)
} else {
let oldIndex = Int(change.oldIndex)
let newIndex = Int(change.newIndex)
typesOfClothing.remove(at: oldIndex)
typesOfClothing.insert(category, at: newIndex)
StoreTblView?.moveRow(at: IndexPath(row: oldIndex, section: 0), to: IndexPath(row: newIndex, section: 0))
}
}
func onDocumentRemoved(change: DocumentChange){
let oldIndex = Int(change.oldIndex)
typesOfClothing.remove(at: Int(oldIndex))
StoreTblView?.deleteRows(at: [IndexPath(row: oldIndex, section: 0)], with: UITableView.RowAnimation.fade)
}
Struct use like this:
struct TypesOfClothing {
var title : String
var clothes: [Clothes]
}
struct Clothes {
var price: Double
var imgClUrl: String
var id: String
var isActive: Bool = true
var timeStamp: Timestamp
var stock: Double
var name: String
}
in Main View controller: try code like this:
var typesOfClothing: [TypesOfClothing] = []
in numberOfRowsInSection
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
return typesOfClothing.count
}
in cellForRowAt
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if let cell = tableView.dequeueReusableCell(withIdentifier: Identifiers.ClothingTypeCell, for: indexPath) as? eachStoreTableViewCell {
if indexPath.row < typesOfClothing?.count ?? 0 {
let cellData = typesOfClothing?[indexPath.row]
cell.configureCell(cellData)
}
return cell
}
return UITableViewCell()
}
in UITableViewCell class try like this:
class eachStoreTableViewCell: UITableViewCell{
#IBOutlet weak var eachRowCollView: UICollectionView!
#IBOutlet weak var eachRowLbl: UILabel!
var clothes = [Clothes]()
override func awakeFromNib() {
super.awakeFromNib()
eachRowCollView?.delegate = self
eachRowCollView?.dataSource = self
self.eachRowCollView?.register(UINib(nibName: "RowColectionView", bundle: nil), forCellWithReuseIdentifier: Identifiers.ClothesCell)
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
}
func configureCell(_ clothingType: TypesOfClothing) {
eachRowLbl?.text = clothingType.title
clothes = clothingType.clothes
eachRowCollView?.reloadData()
}
}
extension eachStoreTableViewCell: UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout{
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return clothes.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if let cell = collectionView.dequeueReusableCell(withReuseIdentifier: Identifiers.ClothesCell, for: indexPath) as? RowColectionView{
cell.configureCell(clothes: clothes[indexPath.item])
return cell
}
return UICollectionViewCell()
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: 160, height: 180)
}
}
it'll help you.
You can make one structs for load tableView Data
struct ClothingData
{
var typesOfClothing : String = ""
var clothes : [Clothes] = []
}
Load this structArray for tabelView
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
return ClothingData.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
let cell = tableView.dequeueReusableCell(withIdentifier: Identifiers.ClothingTypeCell, for: indexPath) as? eachStoreTableViewCell
let row = ClothingData[indexPath.row]
cell.eachRowLbl?.text = row.typesOfClothing
cell.clothes = row.clothes
// Here you can pass clothesList to tabelViewCell for load collectionView
return cell
}
I want to get a JSON response and display data in my viewcontroller.
I have this JSON response:
{"status":1,"data":{"blocks":[{"name":"CustomBlock","description":"CustomDescription","items":[1]}], "items":[{"id:"1", name: "testgame"}]}
I have blocks with name, description, and array of items. Also, all items is passing here with the key "items"
I have created this tableview class
BlocksTableView.swift
class BlocksTableView : UITableView, UITableViewDataSource,
UITableViewDelegate
{
var blocks = [Block]()
var items : BlockItem!
override func awakeFromNib() {
self.delegate = self
self.dataSource = self
self.loadBlocks()
}
func loadBlocks()
{
guard let url = URL(string : "myURL") else { return }
URLSession.shared.dataTask(with: url) { (data, response, err) in
guard let data = data else { return }
do {
let response = try JSONDecoder().decode(APIResponse.self, from: data)
self.blocks = response.data.blocks;
self.items = response.data.items;
DispatchQueue.main.async {
self.reloadData()
}
} catch let jsonErr {
print(jsonErr)
}
}.resume()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return blocks.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "BlockCell") as? BlockCell else { return UITableViewCell() }
cell.blockName.text = blocks[indexPath.row].name
cell.blockDescription.text = blocks[indexPath.row].description
cell.gameCollectionView.reloadData()
return cell
}
}
Now I want to display items in collectionview inside tableviewcell, but I have no idea how to do this. Since each block has different count of items, I need to pass blocks and items variables to my CollectionViewClass, right? But how to do this properly?
Here is my collectionviewclass
class GameCollectionView : UICollectionView,
UICollectionViewDataSource, UICollectionViewDelegate
{
override func awakeFromNib() {
self.delegate = self
self.dataSource = self
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
//here i need to return number of items in current block, something like blocks[0].items.count
return 0
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "GameCollectionCell", for: indexPath) as? GameCollectionCell else { return
UICollectionViewCell()
}
return cell
}
You can try
class BlocksTableView : UITableView, UITableViewDataSource,UITableViewDelegate,UICollectionViewDataSource, UICollectionViewDelegate {
var blocks = [Block]()
var items : BlockItem!
override func awakeFromNib() {
self.delegate = self
self.dataSource = self
self.loadBlocks()
}
func loadBlocks()
{
guard let url = URL(string : "myURL") else { return }
URLSession.shared.dataTask(with: url) { (data, response, err) in
guard let data = data else { return }
do {
let response = try JSONDecoder().decode(APIResponse.self, from: data)
self.blocks = response.data.blocks;
self.items = response.data.items;
DispatchQueue.main.async {
self.reloadData()
}
} catch let jsonErr {
print(jsonErr)
}
}.resume()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return blocks.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "BlockCell") as? BlockCell else { return UITableViewCell() }
cell.blockName.text = blocks[indexPath.row].name
cell.blockDescription.text = blocks[indexPath.row].description
cell.gameCollectionView.delegate = self
cell.gameCollectionView.dataSource = self
cell.gameCollectionView.tag = indexPath.row
cell.gameCollectionView.reloadData()
return cell
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return blocks[collectionView.tag].items.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "GameCollectionCell", for: indexPath) as? GameCollectionCell else { return
UICollectionViewCell() }
// here use blocks[collectionView.tag].items[indexPath.row] for each item
return cell
}
}
The current structure is
firstViewController -> pushViewController(SecondViewController)
-UIViewController ->filename : secondViewController
-UITableView
-UITableViewCell (custom cell) ->filename : secondTableViewCell
-UICollectionView
-UICollectionViewCell (custom cell) ->filename : secondCollectionViewCell
When 'didSelectItemAt' is done in the 'UICollectionView'
I would like to call the 'popviewcontroller' from UIViewController.
And I want to pass the data on the selected item to firstViewController.
but i don't know how
** ViewController **
struct cellStat{
var opened = Bool()
var title = String()
var sectionData = [ItemModel]()
}
//선물 카테고리 리스트 보여주는 모달
class GiftCategoryModalVC: UIViewController {
#IBOutlet weak var tableView: UITableView!
private var categoryTitleModels: [ItemModel] = []
private var giftItemModels: [ItemModel] = []
private var titleCellData: [cellStat] = []
//LIFE CYCLE
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
tableView.register(UINib(nibName: GiftCategoryTBCell.reusableIdentifier, bundle: nil), forCellReuseIdentifier: GiftCategoryTBCell.reusableIdentifier)
tableView.register(UINib(nibName: GiftCategoryListTBCell.reusableIdentifier, bundle: nil), forCellReuseIdentifier: GiftCategoryListTBCell.reusableIdentifier)
//get gift categofy title
GiftPageAPIService.shared.selectCode(code: "CD005") { (itemModels) in
self.categoryTitleModels = itemModels
itemModels.forEach { (itemModel) in
self.titleCellData.append(cellStat(opened: false, title: itemModel.codeNm!, sectionData: []))
}
self.tableView.reloadData()
}
setupLayout()
}
//LAYOUT
fileprivate func setupLayout(){
setNavbar()
}
fileprivate func setNavbar(){
self.navigationItem.leftBarButtonItem = UIBarButtonItem(image: #imageLiteral(resourceName: "icon_arrow_left"), style: UIBarButtonItemStyle.plain, target: self, action: #selector(handleEndButton))
self.navigationController?.navigationBar.barTintColor = UIColor.init(hex: 0xececec)
self.navigationController?.navigationBar.tintColor = UIColor.init(hex: 0x979797)
}
//MAKR:- ACTION
#objc func handleEndButton(){
self.navigationController?.popViewController(animated: true)
}
}
extension GiftCategoryModalVC: UITableViewDelegate, UITableViewDataSource{
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if titleCellData[section].opened == true{
return 2
}else{
return 1
}
}
func numberOfSections(in tableView: UITableView) -> Int {
return titleCellData.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.row == 0{
let cell = tableView.dequeueReusableCell(withIdentifier: GiftCategoryTBCell.reusableIdentifier, for: indexPath) as! GiftCategoryTBCell
cell.setCategotyNm = categoryTitleModels[indexPath.section].codeNm
if titleCellData[indexPath.section].opened == true{
cell.setImgView = UIImage(named: "arrow_up")
}else{
cell.setImgView = UIImage(named: "arrow_down")
}
return cell
}else{
//this cell is call tableCell
let cell = tableView.dequeueReusableCell(withIdentifier: GiftCategoryListTBCell.reusableIdentifier, for: indexPath) as! GiftCategoryListTBCell
cell.setItems = titleCellData[indexPath.section].sectionData
return cell
}
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if indexPath.row == 0{
if titleCellData[indexPath.section].opened == true{
titleCellData[indexPath.section].opened = false
let sections = IndexSet.init(integer: indexPath.section)
tableView.reloadSections(sections, with: .automatic)
}else{
titleCellData[indexPath.section].opened = true
GiftPageAPIService.shared.selectCode(code: categoryTitleModels[indexPath.section].codeCd!) { (items) in
self.titleCellData[indexPath.section].sectionData = items
let sections = IndexSet.init(integer: indexPath.section)
tableView.reloadSections(sections, with: .automatic)
}
}
}
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
if indexPath.row == 0{
return 50
}else{
let cellHeight: Int = (titleCellData[indexPath.section].sectionData.count + 2) / 3
return self.view.frame.height * 0.24 * CGFloat(cellHeight)
}
}
}
** TableViewCell **
class GiftCategoryListTBCell: UITableViewCell {
#IBOutlet weak var collectionView: UICollectionView!
var items: [ItemModel]?
var category: ItemModel?
var setCategory: ItemModel?{
didSet{
self.category = setCategory!
getCategoryData()
}
}
var setItems: [ItemModel]?{
didSet{
self.items = setItems!
collectionView.reloadData()
}
}
let flowlayout = UICollectionViewFlowLayout()
override func awakeFromNib() {
super.awakeFromNib()
collectionView.delegate = self
collectionView.dataSource = self
collectionView.register(UINib(nibName: GiftItemCVCell.reusableIdentifier, bundle: nil), forCellWithReuseIdentifier: GiftItemCVCell.reusableIdentifier)
setupLayout()
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
}
//MARK:- LAYOUT
fileprivate func setupLayout(){
collectionView.backgroundColor = UIColor.init(hex: 0xf7f7f7)
collectionView.isScrollEnabled = false
}
fileprivate func getCategoryData(){
GiftPageAPIService.shared.selectCode(code: (category?.codeCd)!) { (items) in
self.items = items
self.collectionView.reloadData()
}
}
}
extension GiftCategoryListTBCell: UICollectionViewDataSource,UICollectionViewDelegate{
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
if items == nil{
return 0
}else{
return (items?.count)!
}
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: GiftItemCVCell.reusableIdentifier, for: indexPath) as! GiftItemCVCell
cell.setItem = items?[indexPath.item]
return cell
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
/*
I tried to do the work here.
*/
}
}
extension GiftCategoryListTBCell: UICollectionViewDelegateFlowLayout{
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
return UIEdgeInsets(top: contentView.frame.width * 0.07, left: contentView.frame.width * 0.07, bottom: contentView.frame.width * 0.07, right: contentView.frame.width * 0.07)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return contentView.frame.width * 0.03
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let width = (contentView.frame.width) * 0.25
return CGSize(width: width, height: width * 1.5)
}
}
try this :
let window = UIApplication.shared.keyWindow
window?.topMostWindowController?.navigationController?.popViewController(animated: true)
You mean something like this?
Create a protocol in the tableviewcell file
class myTableCell: UITableViewCell{
var myString = "mystring"
var delegate = myNewDelegate?
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
delegate?.didSelect(text: self.myString)
}
}
protocol myNewDelegate {
func didSelect(text: String)
}
This goes in the view controller class that the table cell is in.
class MyTableViewController: UITableViewController, myNewDelegate{
var stringToPass = String()
func didSelectText(text: String){
stringToPass = text
self.performSegue(withIdentifier: "editProfileSegue", sender: self)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "editProfileSegue"{
let viewcontroller = segue.destination as! FirstViewController
viewcontroller.myPassedString = self.stringToPass
}
}
}
And then id the receiving class
class FirstViewController: UIViewController{
var myPassedString = String()
}
I understand what your mean.
If you want to pop your controller to previous controller in tablecell just to get its viewcontroller which control the cell and try to pop and if you try this method, why not override delegate func in your viewcontroller ?May a little bit hard to read the code, but useful, and you can use
self.navigationcontroller?.popViewController
If you have already use the viewcontroller to delegate the other tableview, just to judge the tableview name in your delegate func
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if tableView == blah blah
{
do what you want
}
}
And if you need to pass the data between the different viewController especially pass the data to pre-viewController you need to write delegate, or need to __block callback.
I am trying to add a activity indicator but I am not able to do so successfully because my collection view header is loading before viewdidload so therefore I have things showing before my activity indicator even starts. Can someone please help me or point me in the right direction. I have posted my code below
viewDidLoad
override func viewDidLoad() {
super.viewDidLoad()
fetchUser()
profileCollectionView?.register(guestHeaderCollectionViewCell.self, forSupplementaryViewOfKind: UICollectionElementKindSectionHeader, withReuseIdentifier: "headerId")
profileCollectionView?.register(photoViewCollectionViewCell.self, forCellWithReuseIdentifier: cellId)
}
func fetchUser() {
self.activityIndicator.startAnimating()
if let uid = selectedUser.uid{
Database.fetchUserWithUID(uid: uid) { (user) in
self.selectedUser = user
self.navigationItem.title = self.selectedUser?.fullName
self.profileCollectionView?.reloadData()
self.activityIndicator.stopAnimating()
self.activityIndicator.isHidden = true
self.fetchOrderedPosts()
}
}
}
Collection view header thats being called before viewDidLoad
func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
let header = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: "headerId", for: indexPath) as! guestHeaderCollectionViewCell
header.user = self.selectedUser
header.delegate = self
return header
}
view controller
class newGuestPhotoViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout, UICollectionViewDataSource, guestProfileHeaderDelegate, HomePostCellDelegate {
let cellId = "cellId"
let homePostCellId = "homePostCellId"
#IBOutlet weak var profileCollectionView: UICollectionView!
#IBOutlet weak var activityIndicator: UIActivityIndicatorView!
var user: userAccount?
var selectedUser: userAccount!
var posts = [Post]()
let stringID = NSUUID().uuidString
var isGridView = true
func didChangeToGridView() {
isGridView = true
profileCollectionView?.reloadData()
}
func didChangeToListView() {
isGridView = false
profileCollectionView?.reloadData()
}
var showDataFlag : Bool = false!
override func viewDidLoad() {
super.viewDidLoad()
fetchUser()
profileCollectionView?.register(guestHeaderCollectionViewCell.self, forSupplementaryViewOfKind: UICollectionElementKindSectionHeader, withReuseIdentifier: "headerId")
profileCollectionView?.register(photoViewCollectionViewCell.self, forCellWithReuseIdentifier: cellId)
profileCollectionView?.register(newsfeedCollectionViewCell.self, forCellWithReuseIdentifier: homePostCellId)
}
func fetchUser() {
self.activityIndicator.startAnimating()
if let uid = selectedUser.uid{
Database.fetchUserWithUID(uid: uid) { (user) in
self.selectedUser = user
self.navigationItem.title = self.selectedUser?.fullName
self.showDataFlag = true;
self.profileCollectionView?.reloadData()
self.activityIndicator.stopAnimating()
self.activityIndicator.isHidden = true
self.fetchOrderedPosts()
}
}
}
fileprivate func fetchOrderedPosts() {
guard let uid = selectedUser?.uid else { return }
let ref = Database.database().reference().child("post").child(uid)
ref.queryOrdered(byChild: "dateOfPost").observe(.childAdded, with: { (snapshot) in
guard let dictionary = snapshot.value as? [String: Any] else { return }
let uid = dictionary["postUserUID"] as? String
Database.fetchUserWithUID(uid: uid!, completion: { (userSnap) in
let post = Post(user:userSnap, dictionary: dictionary)
self.posts.insert(post, at: 0)
guard let uid = Auth.auth().currentUser?.uid else {return}
Database.database().reference().child("likes").child(post.postID!).child(uid).observeSingleEvent(of: .value, with: { (snapshot) in
print(snapshot)
if let value = snapshot.value as? Int, value == 1 {
post.hasLiked = true
} else {
post.hasLiked = false
}
DispatchQueue.main.async(execute: {
self.profileCollectionView?.reloadData()
})
}, withCancel: { (err) in
print("Failed to fetch like info for post:", err)
})
})
}) { (err) in
print("Failed to fetch ordered posts:", err)
}
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
if(!showDataFlag){
return 0
}
return posts.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if isGridView {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as! photoViewCollectionViewCell
cell.post = posts[indexPath.item]
return cell
} else {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: homePostCellId, for: indexPath) as! newsfeedCollectionViewCell
cell.post = posts[indexPath.item]
cell.delegate = self
return cell
}
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
return 1
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 1
}
func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
let header = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: "headerId", for: indexPath) as! guestHeaderCollectionViewCell
header.user = self.selectedUser
header.delegate = self
return header
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
return CGSize(width: view.frame.width, height: 250)
}
As I said in mi comment, you can add a flag and after your fetchUser() you can set this flag true and your collectionView will show your data
something like this
var showDataFlag : Bool = false
override func viewDidLoad() {
super.viewDidLoad()
fetchUser()
profileCollectionView?.register(guestHeaderCollectionViewCell.self, forSupplementaryViewOfKind: UICollectionElementKindSectionHeader, withReuseIdentifier: "headerId")
profileCollectionView?.register(photoViewCollectionViewCell.self, forCellWithReuseIdentifier: cellId)
}
func fetchUser() {
self.activityIndicator.startAnimating()
if let uid = selectedUser.uid{
Database.fetchUserWithUID(uid: uid) { (user) in
self.selectedUser = user
self.navigationItem.title = self.selectedUser?.fullName
self.showDataFlag = true;
self.profileCollectionView?.reloadData()
self.activityIndicator.stopAnimating()
self.activityIndicator.isHidden = true
self.fetchOrderedPosts()
}
}
}
//In your Collection View Data Source Implementation
func numberOfSections(in collectionView: UICollectionView) -> Int {
if(!showDataFlag){
return 0
}
return yourNeededNumberOfSections
}
Here's high level description of what I'm trying to achieve;
1. fetch data
2. save the fetched data in an array object
3. update collection view with the size of the array
Here's my code
class ItemcollectionViewController:UICollectionViewController, UICollectionViewDelegateFlowLayout {
let cellId = "CellId"
var categories = [Category]()
let viewOptionVar:ViewOptionBar = {
let vOV = ViewOptionBar()
vOV.translatesAutoresizingMaskIntoConstraints = false
return vOV
}()
private func fetchData() {
let categoryController = CategoryController()
categoryController.getAllCategory(username: "blah", password: "password") {(returnedCategories, error) -> Void in
if error != nil {
print(error)
return
}
self.categories = returnedCategories!
print("size of the array is \(self.categories.count)")
OperationQueue.main.addOperation{self.collectionView?.reloadData()}
}
}
override func viewDidLoad() {
super.viewDidLoad()
fetchData()
collectionView?.backgroundColor = UIColor.white
collectionView?.register(ItemCell.self, forCellWithReuseIdentifier: cellId)
collectionView?.contentInset = UIEdgeInsetsMake(50, 0, self.view.frame.height, self.view.frame.width)
collectionView?.scrollIndicatorInsets = UIEdgeInsetsMake(50, 0, 0, self.view.frame.width)
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
print("in the method \(self.categories.count)")
return self.categories.count
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as! ItemCell
cell.category = categories[indexPath.item]
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: 111, height: 111)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 0
}
private func setupViweOptionBar() {
view.addSubview(viewOptionVar)
view.addConstraintsWithFormat(format: "H:|[v0]|", views: viewOptionVar)
view.addConstraintsWithFormat(format: "V:|[v0(50)]", views: viewOptionVar)
}
}
In the log I could see the following statements:
in the method 0
size of the array is 3
and could not see any cell from my view.
Can someone advise me what I've done wrong?
Thanks in advance.
EDIT 1
Now I'm fetching data after registering the customised cells. However, it still doesn't work
updated code:
class ItemcollectionViewController:UICollectionViewController, UICollectionViewDelegateFlowLayout {
let cellId = "CellId"
var categories = [Category]()
let viewOptionVar:ViewOptionBar = {
let vOV = ViewOptionBar()
vOV.translatesAutoresizingMaskIntoConstraints = false
return vOV
}()
private func fetchData() {
let categoryController = CategoryController()
categoryController.getAllCategory(username: "blah", password: "password") {(returnedCategories, error) -> Void in
if error != nil {
print(error)
return
}
self.categories = returnedCategories!
print("size of the array is \(self.categories.count)")
}
}
override func viewDidLoad() {
super.viewDidLoad()
collectionView?.backgroundColor = UIColor.white
collectionView?.register(ItemCell.self, forCellWithReuseIdentifier: cellId)
collectionView?.contentInset = UIEdgeInsetsMake(50, 0, self.view.frame.height, self.view.frame.width)
collectionView?.scrollIndicatorInsets = UIEdgeInsetsMake(50, 0, 0, self.view.frame.width)
collectionView?.dataSource = self
collectionView?.delegate = self
fetchData()
DispatchQueue.main.async{self.collectionView?.reloadData()}
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
print("in the method \(self.categories.count)")
return self.categories.count
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as! ItemCell
cell.category = categories[indexPath.item]
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: 111, height: 111)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 0
}
private func setupViweOptionBar() {
view.addSubview(viewOptionVar)
view.addConstraintsWithFormat(format: "H:|[v0]|", views: viewOptionVar)
view.addConstraintsWithFormat(format: "V:|[v0(50)]", views: viewOptionVar)
}
}
EDIT 2
The following code is my querying method
func getAllCategory(username:String, password:String, callback: #escaping ([Category]?, String?) -> Void){
var categories = [Category]()
let fetchCategories = URL(string: userURL + "all")
URLSession.shared.dataTask(with: fetchCategories!, completionHandler: { (data, response, error) in
if let err = error {
print(err)
return
}
do {
let jsonCategoryObj = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as! [[String: AnyObject]]
for categoryDictionary in jsonCategoryObj {
let category = Category()
category.categoryId = categoryDictionary["categoryId"] as? String
category.categoryName = categoryDictionary["categoryName"] as? String
category.categoryDescription = categoryDictionary["categoryDescription"] as? String
let categoryRegisteredDateString = categoryDictionary["categoryRegisteredDate"] as? String
let df = DateFormatter()
df.dateFormat = self.shapeDateFormat
let categoryRegisteredDate = df.date(from: categoryRegisteredDateString!)!
category.categoryRegisteredDate = categoryRegisteredDate
categories.append(category)
}
callback(categories, nil)
}catch let jsonError {
callback(nil, String(describing: jsonError))
}
}).resume()
}
FYI: I know I'm not using passed user credential, it's just a copy and paste error from my different query method
When the DataSource changes, reloadData does not update the cell that has been displayed in the view. Reload visible items will do this job.
self.collectionView.reloadData()
self.collectionView.performBatchUpdates({ [weak self] in
let visibleItems = self?.collectionView.indexPathsForVisibleItems ?? []
self?.collectionView.reloadItems(at: visibleItems)
}, completion: { (_) in
})
I'm not sure how this resolved this issue. But I just added
print("size of the array is \(self.categories?.count)")
just next to
OperationQueue.main.addOperation{self.collectionView?.reloadData()}
and it magically works.. even thought when I go back and come back to the screen, it does not show anything.
I'll investigate it more and try to find out why this is happening
Updated
Using
DispatchQueue.main.sync
instead of
DispatchQueue.main.async
resolved the problem.
DispatchQueue.main.async{self.collectionView?.reloadData()}
collectionView?.backgroundColor = UIColor.white
collectionView?.register(ItemCell.self, forCellWithReuseIdentifier: cellId)
You are reloading data before you even have registered your cell.
Register your cell and datasource and delegate methods FIRST, and reload data LAST.
EDIT:
I see you edited your post.
But again, you are fetchData() before you even have registered your cell. So, again, move the fetchData method AFTER you have registered all cells , datasources and delegates.