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!
Related
I am writing swift code with the goal of displaying a increasing number on every tableview cell. Right now the int is not being display. So the first tableview cell should say 1 and the 2nd should say 2. You can see in the gif below what is going along with the tableview cell and nothing is appearing in them when the button is clicked. The func below is when the button is clicked.
var pageNumber = 1
var itemName : [Player] = []
func enterData() {
theScores.reloadData()
let appDeldeaget = UIApplication.shared.delegate as! AppDelegate
let context = appDeldeaget.persistentContainer.viewContext
// Simpler way to create a new Core Data object
let theTitle = Player(context: context)
// Simpler way to set the position attribute
theTitle.positon = Int64(pageNumber)
print(pageNumber)
// pageNumber must be of type Int64, otherwise use Int64(pageNumber)
do {
try context.save()
itemName.append(theTitle)
pageNumber += 1
} catch {
// handle errors
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let title = itemName[indexPath.row]
let cell = theScores.dequeueReusableCell(withIdentifier: "MyCell", for : indexPath)
cell.selectionStyle = .default
let attr5 = title.value(forKey: "positon") as? String
let text = [" Item :", attr5].compactMap { $0 }.reduce("", +)
cell.textLabel?.text = "\(text)"
cell.textLabel?.textAlignment = .center
cell.layoutMargins = UIEdgeInsets.zero
cell.preservesSuperviewLayoutMargins = false
cell.separatorInset = UIEdgeInsets.zero
cell.layoutMargins = UIEdgeInsets.zero
return cell
}
Here's why it doesn't work. You have this:
let attr5 = title.value(forKey: "positon") as? String
let text = [" Item :", attr5].compactMap { $0 }.reduce("", +)
This is a really complicated way to try and do this, and it doesn't work as written. The problem is that the value of position is an Int64 and you need a string. But using as? like that doesn't turn it into a string. When that line of code runs, Swift says, can I just make this into a string? But it can't. So the as? String is nil, and your table cells don't include the number because the conversion failed.
A better way would be something like
if let position = title.value(forKey: "positon") {
cell.textLabel?.text = "Item : \(positon))"
}
But that's only if you really want to use value(forKey:) for some reason. You probably don't need that because normally Xcode creates a subclass of NSManagedObject for each entity with named properties. So even better would be
cell.textLabel?.text = "Item: \(title.position)"
These both work because string interpolation knows how to convert an integer to a string.
You probably should call .reloadData() after context.save()
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.
I have a messaging application written in Swift.
It does have message bubbles: if the message is longer than 200 chars it is being shortened.
Whenever the user clicks on a message it gets selected:
If the message was shortened, I replace the text with the original long text: Therefore I need to call tableView.beginUpdates() and tableView.endUpdates()
Plus I have to change the timeLabel's height constraint with UIView.animate()
But the two seems to conflict each other, and makes a weird animation: (watch the end)
https://youtu.be/QLGtUg1AmFw
Code:
func selectWorldMessage(indexPath: IndexPath) {
if let cell = tableView.cellForRow(at: indexPath) as? WorldMessageCell {
cell.messageLabel.text = data[indexPath.row].originalMessage
self.lastContentOffsetY = self.tableView.contentOffset.y
self.tableView.beginUpdates()
UIView.animate(withDuration: duration) {
cell.timeLabelHeightConstraint.constant = 18
cell.layoutIfNeeded()
}
self.tableView.endUpdates()
self.lastContentOffsetY = nil
cell.layoutIfNeeded()
WorldMessageIdsStore.shared.nearby.saveCellHeight(indexPath: indexPath, height: cell.frame.size.height, expanded : true, depending: indexPath.section == 1 ? true : false )
}
}
#objc func deselectSelectedWorldMessage(){
if (currentSelectedIndexPath == nil){
return
}
let indexPath = currentSelectedIndexPath!
tableView.deselectRow(at: indexPath, animated: false)
if let cell = tableView.cellForRow(at: indexPath) as? WorldMessageCell {
cell.messageLabel.text = data[indexPath.row].shortenedMessage
self.lastContentOffsetY = self.tableView.contentOffset.y
self.tableView.beginUpdates()
UIView.animate(withDuration: duration) {
cell.timeLabelHeightConstraint.constant = 0
cell.layoutIfNeeded()
}
self.tableView.endUpdates()
self.lastContentOffsetY = nil
cell.layoutIfNeeded()
}
currentSelectedIndexPath = nil
}
Is there a way to animate cell height change & constraint change in the same time?
I can not place self.tableView.beginUpdates() and self.tableView.endUpdates() in the UIView.animate(){ ... } part, because it would cause the new appearing rows to flicker.
.
.
.
UPDATE 1
So If I palce the self.tableView.beginUpdates() and self.tableView.endUpdates() inside the UIView.animate(){ ... }, then the animation works fine. But as I mentioned it causes flicker when new rows are appearing.
Video:
https://youtu.be/8Sex3DoESkQ
UPDATE 2 !!
So If I set the UIview.animate's duration to 0.3 everything works fine. I don't really understand why.
This animation can be sticky.
I think your main problem here is bubble shrinking more then it should. If you run your animation in slow animation mode (By the way, the simulator has this option, very useful for debugging) you will notice:
To sort this out, you can make your label vertical content compression resistants
higher, and check label has top and bottom constraints with Hi priority too, so it would not get cut this way.
https://medium.com/#abhimuralidharan/ios-content-hugging-and-content-compression-resistance-priorities-476fb5828ef
Try using the following code to overcome flicking problem
tableView.scrollToRow(at: indexPath, at: UITableView.ScrollPosition.middle, animated: true)
Alternative solution
try using this handsome method to update your cell with animation instead of beginUpdates() and endUpdates() methods
tableView.reloadRows(at: [indexPath], with: UITableView.RowAnimation.bottom )
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];
});
Just updated to the newer FirebaseUI Pod - a few things have changed but one of the big ones is the way that the FUI Table View works. I had it working well on an older version but am struggling with this below - and the lack of documentation/examples.
self.dataSource = FUITableViewDataSource(query: <#T##FIRDatabaseQuery#>, view: <#T##UITableView#>, populateCell: <#T##(UITableView, IndexPath, FIRDataSnapshot) -> UITableViewCell#>)
I don't understand where the indexpath is being called from. Do I need to make a seperate NSIndexPath to pass into that? I also don't really understand where this is supposed to live - previously, with it was FirebaseTableViewDataSource, I would set it in my viewDidLoad, and it would create the cells etc straight from that. It almost now looks as though it needs to live in my cellForRowAtIndexPath. Does anyone have any advice on this?
The test for this latest version uses a tableView:bind: method (seems like a UITableView class extension they made) and I was able to get it to work.
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
let firebaseRef = FIRDatabase.database().reference().child(/*insert path for list here*/)
let query = firebaseRef.queryOrderedByKey() /*or a more sophisticated query of your choice*/
let dataSource = self.tableView.bind(to: query, populateCell: { (tableView: UITableView, indexPath: IndexPath, snapshot: FIRDataSnapshot) -> UITableViewCell in
let cell = tableView.dequeueReusableCell(withIdentifier: "cellIdentifier", for: indexPath)
let value = snapshot.value as! NSDictionary
let someProp = value["someProp"] as? String ?? ""
cell.textLabel?.text = someProp
return cell
})
}
Also make sure you are observing your query for changes or else the tableView won't get populated
self.query?.observe(.value, with: { snapshot in
})