swift UICollectionView showing empty sometimes bug even header - swift

I have a UIcollectionView that has a header and Footer and sometimes a weird bug just shows the screen empty. It is strange because even on the View Debugger it shows as empty but the Header is a static image that is on the app not something is getting from API and that does not show either.
Also the console does not give any errors methods. I try going to another view and forcing a reloadData() but still does not show anything. Any way I can debug this better or make sure it does not happen?
This is how the view debugger looks like:
You can see the header and footer empty reusable views:
Edit:
This is how the supplementary views are being created using RxDataSources
dataSource.supplementaryViewFactory = { (dataSource, collectionView, kind, indexPath) in
switch kind {
case UICollectionElementKindSectionHeader:
let headerView = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: CellReuseId.contentHeaderCollectionView, for: indexPath ) as! ContentHeaderView
headerView.imageView.image = ImageAssets.contentBanner
let tapGestureRecognizer = UITapGestureRecognizer(target:self, action: #selector(self.showListOfEvents(_:)))
headerView.addGestureRecognizer(tapGestureRecognizer)
return headerView
case UICollectionElementKindSectionFooter:
let footerView = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: CellReuseId.contentFooterCollectionView, for: indexPath) as! ContentFooterView
footerView.setUpObjects()
//setUp Appropiate label or animation
let showFooter: Bool = !(self.centerActivityIndicator?.isAnimating ?? false)
footerView.setUpAppropiateDependingOnResults(isThereMoreResults: self.isThereMoreResults, showFooter: showFooter)
return footerView
default: break
}
//return default
return UICollectionReusableView()
}
And this is the code that gets the models for the API and binds them to the collectionView
let results = Observable.of(paginationObserver, offlineObserver).merge()
//calls method that calls DB with the appropiate data
.flatMapLatest { [unowned self] parametersChanged -> Driver<LecturesStateResults> in
//since this is being called again, we make sure to clean out "old cache data" on view model
self.videoObject.lecturesResults.value.removeAll(keepingCapacity: true)
return self.setupLectures().asDriver(onErrorJustReturn: LecturesStateResults.empty)
}
results
//Bind the result observable to the UIcollectionView
//UiCollection view only wants an array not an Observable
.map {
if !$0.results.isEmpty { self.centerActivityIndicator?.stopAnimating()}
return [SectionModel(model: "", items: $0.results)]
}
.bind(to: lectureViewSquare.rx.items(dataSource: dataSource))
.addDisposableTo(disposeBag)
lectureViewSquare.rx.setDelegate(self)
.addDisposableTo(disposeBag)

I had the same problem with the same ui rendering, but without using RxSwift.
The code that fixed the issue was
dispatch_async(dispatch_get_main_queue(), ^{
[self.collectionView reloadData];
});

Related

Cannot Add Sections to Table View with Rx Swift and MVVM

I 'm practicing SwiftRx with MVVM and TableView where i wrote down simple program to download data from remote api https://jsonplaceholder.typicode.com/photos .Now i want to group rows with data from api i-e albumId but stuck in it.Please help me
i tried with seperate datasource but i am completely new and have not much understanding.
viewModel.photoCells.bind(to: self.tableView.rx.items) {
tableView, index, element in
let indexPath = IndexPath(item: index, section: 0)
switch element {
case .normal(let viewModel):
guard let cell = tableView.dequeueReusableCell(withIdentifier: "photoCell", for: indexPath) as? PhotosViewCell
else {
return UITableViewCell()
}
cell.viewModel = viewModel
return cell
case .error(let message):
let cell = UITableViewCell()
cell.isUserInteractionEnabled = false
cell.textLabel?.text = message
return cell
case .empty:
let cell = UITableViewCell()
cell.isUserInteractionEnabled = false
cell.textLabel?.text = "No data available"
return cell
}
}.disposed(by: disposeBag)
For this sort of work, your best bet is to use the RxDataSources SDK. Read the documentation on it and find some tutorials too. If you have any more specific questions about it. Post them here or on the RxSwift slack channel.

scroll back to the previous collectionview cell

I am using the code below to scroll to the next cell which works perfectly but how do I scroll back to the previous cell?
let cellItems = CollectionView.indexPathsForVisibleItems
CollectionView.scrollToItem(at: cellItems.max()!, at: .centeredVertically, animated: true)
First of all, the indexPathsForVisibleItems method does not guarantee order. You need to sort it firstly:
let sortedIndexes = collectionView.indexPathsForVisibleItems.sorted(<)
If you want to scroll to the previous cell, you need to store previous cell IndexPath somewhere in your class:
var previousCellIndexPath: IndexPath?
And than you can scroll to this cell:
func scrollToPreviousCell() {
guard let previousCellIndexPath = self.previousCellIndexPath else { return }
collectionView.scrollToItem(at: previousIndexPath, at: centeredVertically, animated: true)
}

unit testing cell is nil

Issue:
I loaded collectionView with 3 Dummy items. However Cell came back nil, is it because view was never loaded? How do you guys test your collectionViewCell type?
Code
var window: UIWindow?
var sut: QuestsDataProvider!
var collectionView: UICollectionView!
override func setUp() {
super.setUp()
bulletinController = BulletinController(collectionViewLayout: UICollectionViewFlowLayout())
sut = QuestsDataProvider(acceptedQuests: false, completedQuests: false)
bulletinController.collectionView?.dataSource = sut
bulletinController.collectionView?.delegate = sut
window = UIWindow()
window?.makeKeyAndVisible()
window?.rootViewController = bulletinController
}
func testCellIsQuestCell() {
let indexPath = IndexPath(item: 1, section: 0)
let cell = collectionView.cellForItem(at: indexPath)
guard let count = sut.questManager?.quests.count else {return XCTFail()}
XCTAssertTrue(cell is QuestCell)
}
Edit:
Upon Further testing, I'm able to see the dummy Cell inside my simulator and get a accurate count from numberOfitems(InSection: Int). However I have no visible Cell.
2nd Edit:
After further research, I found out the issue is collectionView.cellForItem(at: indexPath) only shows visible cell. Is there any other method for unit testing collection view cell type?
You need to access the view object of the view controller before it and its subview components will be fully initialised.
You should be able to just do let _ = bulletinController.view in your setup function. it is quite a common approach, see here
Relevant parts included below
func setupCreateOrderViewController()
{
let bundle = NSBundle(forClass: self.dynamicType)
let storyboard = UIStoryboard(name: "Main", bundle: bundle)
createOrderViewController = storyboard.instantiateViewControllerWithIdentifier("CreateOrderViewController") as! CreateOrderViewController
_ = createOrderViewController.view
}
Quote from link:
But there are two very, very important things happening on the last line:
Asking for the view property of createOrderViewController causes the view to be loaded. The viewDidLoad() method is called as a result.
After the view is loaded, all the IBOutlets are also set up and ready to be used in out tests. For example, you can assert that a text field outlet’s text equal to a string you expect.
EDIT:
You can also just call loadViewIfNeeded() on the view controller, which will do the same thing.
Loads the view controller’s view if it has not yet been loaded.

Swift 3: TPKeyboardAvoidingScrollView not working properly on one UITextField in UITableViewCell

I'm having this problem where I have three cells, all created by the same function, but for some reason the third one has issues with scrolling (last name cell).
I'm using TPKeyboardAvoidingTableView, which is usually the greatest thing ever imagined, but for some reason it does not like this cell. Here is the code:
guard let cell = tableView.dequeueReusableCell(withIdentifier: SignUp_Constants.DetailsCellIdentifier, for: indexPath) as? DetailsCell else { return UITableViewCell() }
cell.isUserInteractionEnabled = true
cell.detailsInputTextField.removeTarget(nil, action: nil, for: .allEvents)
cell.detailsInputTextField.addTarget(cell, action: #selector(DetailsCell.textFieldDidChange(_:)), for: .editingChanged)
cell.detailsInputTextField.autocapitalizationType = .none
cell.detailsInputTextField.spellCheckingType = .default
cell.detailsInputTextField.isSecureTextEntry = false
cell.detailsInputTextField.isUserInteractionEnabled = true
cell.detailsInputTextField.keyboardType = .default
cell.delegate = self
var title = SignUp_Constants.getTitle(forCellType: currentSectionView, atRow: indexPath.row)
cell.setTextFieldText(toValue: signUpDictionary[title] as? String ?? "")
cell.setTitleLabel(to: title)
return cell
Every other cell that is created by the same code in the ENTIRE project works fine.
Anything helps and thanks in advance!
After a lot of hassle and banging my head on the table, I decided to throw in tableView.scrollToRow(row: x, atIndexPath: indexPath) when the user clicked on the textfield, and the error went away! I've used this pod many times and never had to use that function with it, so if anyone has any other solutions it would be great to hear them!

CollectionView Cell and Progress Bar - Progress Bar showing in wrong Cell after scrolling

Ive been searching for a answer to this one for days now and cant seem to figure it out. I have a Collection View with custom cell. When you double tap a cell in the Collection View it will either download a file or delete it if its been downloaded before.
During the download a progress bar displays the progress of the download then displays a small icon in the top left corner. When deleting it removes the icon.
If you download from one cell and delete from another while first download is in progress it works fine but only if both cells were visible within the collection view.
if i download from one cell, then scroll offscreen and delete from a cell that is not in same screen as the cell that is being download from, it removes the corner image as usual then displays the progress bar of the cell that is being download from.
I don't know if this is an error with how i am reusing cells??? It doesn't seem to have anything to do with how i am updating the cell or collection view which works in all cases except after scrolling.
Below is 2 functions that download or delete file:
func downloadDataToDevice(cell: JourneyCollectionViewCell, selectedIndexPath: IndexPath){
let downloadedAudio = PFObject(className: "downloadedAudio")
// save all files with unique name / object id
let selectedObjectId = self.partArray[selectedIndexPath.item].id
let selectedPartName = self.partArray[selectedIndexPath.item].name
let query = PFQuery(className: "Part")
query.whereKey("objectId", equalTo: selectedObjectId)
query.getFirstObjectInBackground { (object, error) in
if error != nil || object == nil {
print("No object for the index selected.")
} else {
//print("there is an object, getting the file.")
downloadedAudio.add(object?.object(forKey: "partAudio") as! PFFile, forKey: selectedPartName)
let downloadedFile = object?.object(forKey: "partAudio") as! PFFile
// get the data first so we can track progress
downloadedFile.getDataInBackground({ (success, error) in
if (success != nil) {
// pin the audio if there is data
downloadedAudio.pinInBackground(block: { (success, error) in
if success {
// reload the cell
self.reloadCell(selectedIndexPath: selectedIndexPath, hideProgress: true, hideImage: false, cell: cell)
self.inProgress -= 1
cell.isUserInteractionEnabled = true
}
})
}
// track the progress of the data
}, progressBlock: { (percent) in
self.activityIndicatorView.stopAnimating()
cell.progessBar.isHidden = false
//cell.progessBar.transform = cell.progessBar.transform.scaledBy(x: 1, y: 1.1)
cell.contentView.bringSubview(toFront: cell.progessBar)
cell.progessBar.setProgress(Float(percent) / Float(100), animated: true)
cell.isUserInteractionEnabled = false
})
}
}
}
func removeDataFromDevice(cell: JourneyCollectionViewCell, selectedIndexPath: IndexPath, object: PFObject) {
let selectedPartName = self.partArray[selectedIndexPath.item].name
// unpin the object from the LocalDataStore
PFObject.unpinAll(inBackground: [object], block: { (success, error) in
if success {
// reduce inProgress
self.inProgress -= 1
self.reloadCell(selectedIndexPath: selectedIndexPath, hideProgress: true, hideImage: true, cell: cell)
}
})
}
and this is how I'm reloading the cell
func reloadCell(selectedIndexPath: IndexPath, hideProgress: Bool, hideImage: Bool, cell: JourneyCollectionViewCell) {
cell.progessBar.isHidden = hideProgress
cell.imageDownloaded.isHidden = hideImage
self.collectionView.reloadItems(at: [selectedIndexPath])
}
----------- EDIT -------------
This is my cellForItem at function. Presently i am using a query to look on local drive and see if the file exists and then adding the corner image if it is. This is the first time i have used a query in this place, usually it is a query at login to populate an array but that is for a more static collection of data than what i am trying to achieve here by letting the user download and delete files.
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell: JourneyCollectionViewCell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath) as! JourneyCollectionViewCell
cell.imageCell.file = self.partArray[indexPath.item].image
cell.imageCell.loadInBackground()
cell.imageCell.layer.masksToBounds = true
// not sure if its good to run a query here as its constantly updated.
// query if file is on LDS and add image to indicate
let cellPartName = self.partArray[indexPath.item].name
let checkQuery = PFQuery(className: "downloadedAudio")
checkQuery.whereKeyExists(cellPartName)
checkQuery.fromLocalDatastore()
checkQuery.getFirstObjectInBackground(block: { (object, error) in
if error != nil || object == nil {
//print("The file does not exist locally on the device, remove the image.")
cell.imageDownloaded.isHidden = true
cell.imageDownloaded.image = UIImage(named: "")
cell.progessBar.isHidden = true
} else {
//print("the file already exists on the device, add the image.")
cell.contentView.bringSubview(toFront: cell.imageDownloaded)
cell.imageDownloaded.isHidden = false
cell.imageDownloaded.image = UIImage(named: "download-1")
}
})
return cell
}
This is a normal feature of "reuse" cells, for efficient memory management purposes. What you need to do is reset the cell values in below function:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
}
By reset, I mean set the cells to their default state, prior to you making any updates such as adding the left corner icon or the status bar.
You need to make sure the arrays that you are feeding the collectionview data from is maintained properly. For example, if you have an array A =[1,2,3] and you delete A[1], then array A needs to be [1,3].
So i tried placing the progress view programatically, i tried prepareForReuse in the custom cell class, neither resolved this issue directly, though i will keep using prepareForReuse as i think its a cleaner way to manage the cell than i had been.
What seems to have worked was relocating the cell within the progressBlock
if let downloadingCell = self.collectionView.cellForItem(at: selectedIndexPath) as? JourneyCollectionViewCell { downloadingCell.progessBar.isHidden = false
downloadingCell.contentView.bringSubview(toFront: downloadingCell.progessBar)
downloadingCell.progessBar.setProgress(Float(percent) / Float(100), animated: true)
downloadingCell.setNeedsDisplay()
downloadingCell.isUserInteractionEnabled = false
}