why is collection view header loading before viewdidload? - ios

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
}

Related

UITableView not visible until tapped on screen

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()
}

drag and drop collectionViewCell in swift

whenever I drag and drop the cell in other section app gets crashed and when I drag and drop the cell in same section it works good any ideas how to fix it thank a lot in advance. below is the full code.
// error : Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'attempt to insert item 21 into section 1, but there are only 1 sections after the update'
// ViewController
import UIKit
class ViewController: UIViewController {
var deviceName = ""
let cellName = "IconCell". // cell name
var pressActivated = false
#IBOutlet weak var collectionView: UICollectionView!
var nameArrays = [
0: ["twitter", "facebook", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", ""],
1: ["twitter", "twitter", "", "twitter", "", "", "", "", "", "", "", "twitter", "", "", "twitter", "twitter", "twitter", "twitter", "twitter", "twitter", "twitter", "twitter", "twitter", "twitter"]
]
let statusBarTappedNotification = Notification(name: Notification.Name(rawValue: "statusBarTappedNotification"))
#IBOutlet weak var pageController: UIPageControl!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
setupView()
}
private func setupView(){
self.collectionView.register(UINib.init(nibName: cellName, bundle: nil), forCellWithReuseIdentifier: cellName)
pageController.numberOfPages = nameArrays.keys.count
pageController.currentPage = 0
collectionView.dragDelegate = self
collectionView.dropDelegate = self
// collectionView.dragInteractionEnabled = true
collectionView.reorderingCadence = .fast
NotificationCenter.default.addObserver(forName: statusBarTappedNotification.name, object: .none, queue: .none) { _ in
print("status bar tapped")
self.pressActivated = false
self.collectionView.reloadData()
}
}
#objc private func longPress(){
print("long Pressed")
pressActivated = true
if pressActivated{
collectionView.dragInteractionEnabled = true
}
collectionView.reloadData()
}
private func copyItems(coordinator: UICollectionViewDropCoordinator, destinationIndexPath: IndexPath, collectionView: UICollectionView)
{
collectionView.performBatchUpdates({
var indexPaths = [IndexPath]()
for (index, item) in coordinator.items.enumerated()
{
let indexPath = IndexPath(row: destinationIndexPath.item + index, section: destinationIndexPath.section)
print(indexPath.section)
print(indexPath.item)
let key = self.nameArrays.keys.sorted()[indexPath.section]
self.nameArrays[key]!.insert(item.dragItem.localObject as! String, at: indexPath.item)
//
indexPaths.append(indexPath)
}
collectionView.insertItems(at: indexPaths)
})
}
private func reorderItems(coordinator: UICollectionViewDropCoordinator, destinationIndexPath: IndexPath, collectionView: UICollectionView)
{
let items = coordinator.items
if items.count == 1, let item = items.first, let sourceIndexPath = item.sourceIndexPath
{
var dIndexPath = destinationIndexPath
if dIndexPath.row >= collectionView.numberOfItems(inSection: 0)
{
dIndexPath.row = collectionView.numberOfItems(inSection: 0) - 1
}
collectionView.performBatchUpdates({
self.nameArrays.removeValue(forKey: sourceIndexPath.item)
collectionView.deleteItems(at: [sourceIndexPath])
collectionView.insertItems(at: [dIndexPath])
print(dIndexPath) // till here it works good
})
coordinator.drop(items.first!.dragItem, toItemAt: dIndexPath)
}
}
}
extension ViewController: UICollectionViewDelegate,UICollectionViewDataSource,UICollectionViewDelegateFlowLayout {
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
for cell in collectionView.visibleCells {
let indexPath = collectionView.indexPath(for: cell)
if let indexPath = indexPath {
print("\(indexPath)")
self.pageController.currentPage = indexPath.section
}
}
}
func numberOfSections(in collectionView: UICollectionView) -> Int {
return nameArrays.keys.count. // number of sections in collection view
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
let key = nameArrays.keys.sorted()[section]
return nameArrays[key]!.count // number of rows in collection View
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell: IconCell = collectionView.dequeueReusableCell(withReuseIdentifier: cellName, for: indexPath) as! IconCell
// setting data to cells
let holdGesture = UILongPressGestureRecognizer()
holdGesture.addTarget(self, action: #selector(longPress))
holdGesture.minimumPressDuration = 0.5
cell.addGestureRecognizer(holdGesture)
cell.backgroundColor = .clear
let key = nameArrays.keys.sorted()[indexPath.section]
cell.appName.text = nameArrays[key]![indexPath.item]
cell.appIcon.image = UIImage.init(named: nameArrays[key]![indexPath.item])
if pressActivated{
let transformAnim = CAKeyframeAnimation(keyPath:"transform")
transformAnim.values = [NSValue(caTransform3D: CATransform3DMakeRotation(0.04, 0.0, 0.0, 1.0)),NSValue(caTransform3D: CATransform3DMakeRotation(-0.04 , 0, 0, 1))]
transformAnim.autoreverses = true
transformAnim.duration = Double(indexPath.row).truncatingRemainder(dividingBy: 2) == 0 ? 0.115 : 0.105
transformAnim.repeatCount = Float.infinity
cell.layer.add(transformAnim, forKey: "transform")
cell.closeButton.isHidden = false
if cell.appName.text == ""{
cell.closeButton.isHidden = true
}
}
else {
cell.closeButton.isHidden = true
}
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: collectionView.bounds.width/4 - 10, height: 80)
}
}
class CustomCollectionViewFlowLayout: UICollectionViewFlowLayout
{
override func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint
{
if let collectionViewBounds = self.collectionView?.bounds
{
let halfWidthOfVC = collectionViewBounds.size.width * 0.5
let proposedContentOffsetCenterX = proposedContentOffset.x + halfWidthOfVC
if let attributesForVisibleCells = self.layoutAttributesForElements(in: collectionViewBounds)
{
var candidateAttribute : UICollectionViewLayoutAttributes?
for attributes in attributesForVisibleCells
{
let candAttr : UICollectionViewLayoutAttributes? = candidateAttribute
if candAttr != nil
{
let a = attributes.center.x - proposedContentOffsetCenterX
let b = candAttr!.center.x - proposedContentOffsetCenterX
if abs(a) < abs(b)
{
candidateAttribute = attributes
}
}
else
{
candidateAttribute = attributes
continue
}
}
if candidateAttribute != nil
{
return CGPoint(x: candidateAttribute!.center.x - halfWidthOfVC, y: proposedContentOffset.y);
}
}
}
return CGPoint.zero
}
}
extension ViewController : UICollectionViewDragDelegate
{
func collectionView(_ collectionView: UICollectionView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem]
{
let key = nameArrays.keys.sorted()[indexPath.section]
let item = self.nameArrays[key]![indexPath.item]
let itemProvider = NSItemProvider(object: item as NSString)
let dragItem = UIDragItem(itemProvider: itemProvider)
dragItem.localObject = item
return [dragItem]
}
func collectionView(_ collectionView: UICollectionView, itemsForAddingTo session: UIDragSession, at indexPath: IndexPath, point: CGPoint) -> [UIDragItem]
{
let key = nameArrays.keys.sorted()[indexPath.section]
let item = self.nameArrays[key]![indexPath.item]
let itemProvider = NSItemProvider(object: item as NSString)
let dragItem = UIDragItem(itemProvider: itemProvider)
dragItem.localObject = item
return [dragItem]
}
func collectionView(_ collectionView: UICollectionView, dragPreviewParametersForItemAt indexPath: IndexPath) -> UIDragPreviewParameters?
{
let previewParameters = UIDragPreviewParameters()
previewParameters.visiblePath = UIBezierPath(rect: CGRect(x: 25, y: 25, width: 120, height: 120))
return previewParameters
}
}
// MARK: - UICollectionViewDropDelegate Methods
extension ViewController : UICollectionViewDropDelegate
{
func collectionView(_ collectionView: UICollectionView, canHandle session: UIDropSession) -> Bool
{
return session.canLoadObjects(ofClass: NSString.self)
}
func collectionView(_ collectionView: UICollectionView, dropSessionDidUpdate session: UIDropSession, withDestinationIndexPath destinationIndexPath: IndexPath?) -> UICollectionViewDropProposal
{
if collectionView.hasActiveDrag
{
return UICollectionViewDropProposal(operation: .move, intent: .insertAtDestinationIndexPath)
}
else
{
return UICollectionViewDropProposal(operation: .forbidden)
}
}
func collectionView(_ collectionView: UICollectionView, performDropWith coordinator: UICollectionViewDropCoordinator)
{
let destinationIndexPath: IndexPath
if let indexPath = coordinator.destinationIndexPath
{
destinationIndexPath = indexPath
}
else
{
// Get last index path of table view.
let section = collectionView.numberOfSections - 1
let row = collectionView.numberOfItems(inSection: section)
destinationIndexPath = IndexPath(row: row, section: section)
}
switch coordinator.proposal.operation
{
case .move:
print("move")
self.reorderItems(coordinator: coordinator, destinationIndexPath:destinationIndexPath, collectionView: collectionView)
break
case .copy:
print("copy")
self.copyItems(coordinator: coordinator, destinationIndexPath: destinationIndexPath, collectionView: collectionView)
default:
return
}
}
}
you must use beginInteractiveMovementForItemAtIndexPath:
and endInteractiveMovement
see Reordering Items Interactively section in UICollectionView reference

how to call popviewcontroller in UITableViewCell

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.

UICollectionView reloadData() does not update cells in the collection view

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.

View is calling viewDidLoad() when navigating back

When the user selects a cell in the collection view, it pushes to a new view controller for that cell.
The problem is that when the user swipes back, the collection view controller runs viewDidLoad() instead of viewDidAppear(). This causes the whole collection view to reload and go back up to the top (first cell) and the user has to scroll all the way back down to get to where they were before.
Does anyone know why this is happening??
import UIKit
import FirebaseStorage
class CollectionViewController: UICollectionViewController, UICollectionViewDelegateFlowLayout {
let reuseIdentifier = "PostCell"
var post: [Post] = [Post]()
var imageURLs: [URL] = [URL]()
override func viewDidLoad() {
super.viewDidLoad()
self.navigationController?.hidesBarsOnSwipe = true
collectionView?.backgroundColor = UIColor(red: 0.91, green: 0.91, blue: 0.91, alpha: 1.00)
// Uncomment the following line to preserve selection between presentations
self.clearsSelectionOnViewWillAppear = false
// Register cell classes
collectionView?.register(PostCell.self, forCellWithReuseIdentifier: reuseIdentifier)
//setupHorizontalBar()
setupCollectionView()
// Get all of the posts
loadPosts()
print("loaded")
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(true)
print("appeared")
collectionView.index
}
func setupCollectionView() {
if let layout = collectionView?.collectionViewLayout as? UICollectionViewFlowLayout {
layout.scrollDirection = .vertical
layout.minimumLineSpacing = 0
}
}
func loadPosts() {
postRef.observe(.value, with: { (snapshot) in
for eachPost in snapshot.value as! [String: Any] {
let dict: Dictionary<String, Any> = [eachPost.key: eachPost.value]
let post = Post(postDictionary: dict)
print(post)
self.posts.append(post)
self.collectionView?.reloadData()
}
})
}
override func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return posts.count
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath) as! PostCell
//cell.backgroundColor = .brown
let post = posts[indexPath.row]
cell.nameLabel.text = post.name
let details = "\n\(post.address!)\n\n\(post.time!) \(post.date!)"
cell.postDetailTextView.text = details
if let imageURL = post.image {
print(imageURL)
cell.postImageView.sd_setImage(with: URL(string: imageURL))
}
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let height = (self.view.frame.width - 20) * 9 / 16
return CGSize(width: self.view.frame.width, height: height + 5 + 140)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 0
}
When didSelectItemAt is called, I want the navigation controller to push to another view controller for the cell selected.
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
print(indexPath)
let detailVC = DetailViewController()
self.navigationController?.pushViewController(detailVC, animated: true)
}
}

Resources