My application code is pretty lengthly so I would like to try and get this problem resolved without having to show a bunch of code....but here is what is going on..
I have a tableview with dynamic cells all set up just fine. There is a textfield in the cell. When data is entered into the textfield, it is then sent to a label within that same cell. This happen when a "done" button is pressed. This all works just find until I have about 6 rows added to the table. At that point, when the "done" button is pressed, the data gets all mixed up. Sometimes the data is sent to the wrong cell label and other times it doesn't send at all. This only happens when I get to about 6 rows. I am very confused. The cells are displayed based on a Core Data entity.count...the textfield has the delegate set as itself....I have no clue what could be causing this...only after 6 rows!?
Thank you in advance for your help...like I said, I would like to try and avoid pasting my code here but if I have to provide some I will or I can talk you through what is going on in my code...
EDIT
Here is some of my code in cellForRowAt that deals with the textFields in the cell content
if searchResults.count > 1 {
if showInGrams {
if foods[indexPath.row].units != "g" && foods[indexPath.row].units != "grams" && foods[indexPath.row].units != "Grams" {
if cell.changeServingTextField.text == "" {
cell.labelServing.text = String(format: "%0.2f", (100 / foods[indexPath.row].coefficient) * searchResults[indexPath.row].doubleValue)
cell.labelUOM.text = "grams"
} else {
if cell.changeServingSegmentedControl.selectedSegmentIndex == 0 {
cell.labelServing.text = String(format: "%0.2f", Double(cell.changeServingTextField.text!)!)
cell.labelUOM.text = cell.changeServingSegmentedControl.titleForSegment(at: 0)
//show default values
} else {
cell.labelServing.text = String(format: "%0.2f", Double(cell.changeServingTextField.text!)!)
cell.labelUOM.text = cell.changeServingSegmentedControl.titleForSegment(at: 1)
}
}
} else {
if cell.changeServingTextField.text == "" {
cell.labelServing.text = String(format: "%0.2f", searchResults[indexPath.row].doubleValue * foods[indexPath.row].serving)
cell.labelUOM.text = "grams"
}
}
} else {
if cell.changeServingTextField.text == "" {
cell.labelServing.text = String(format: "%0.2f", searchResults[indexPath.row].doubleValue * foods[indexPath.row].serving)
cell.labelUOM.text = foods[indexPath.row].units
} else {
if cell.changeServingSegmentedControl.selectedSegmentIndex == 0 {
cell.labelServing.text = String(format: "%0.2f", Double(cell.changeServingTextField.text!)!)
cell.labelUOM.text = cell.changeServingSegmentedControl.titleForSegment(at: 0)
} else {
cell.labelServing.text = String(format: "%0.2f", Double(cell.changeServingTextField.text!)!)
cell.labelUOM.text = cell.changeServingSegmentedControl.titleForSegment(at: 1)
}
}
}
I hope this helps, thank you
What you are seeing is a symptom of UITableView's reuse strategy. May be what you want to do is store the information entered in your UITextField in a collection and then every time the tableView "reloads", you pick it out from that collection for the current indexPath and load it.
A line like this is never going to work:
if cell.changeServingTextField.text == ""
The problem is that cells are reused. Thus, in its previous life, if the text field had text, it still has text and the test will fail.
Instead, your logic needs to be based entirely on what row this is and what the data corresponding to that row is. And it needs to cover every case, because the cell can be reused.
Things would be clearer if you moved most of this logic into the cells themselves. You can start off by making all your cell's IBOutlets fileprivate and assigning a model object or dictionary as a property of the cell. That way you're separating the logic for creating the cells from their display - and will prevent you using cell's outlets to determine state.
While this might not solve your problem directly, separating the logic might clarify where to look.
Related
I want to show multiple UIViews in a collection cell based on the logic statements below.
This function is for a Calendar view in my tabbar application. I'm reading events from a database on Firebase Firestore and saving it in calData.
func configureEventDotFor(cell: CalendarCell, cellState: CellState) {
let dateString = self.globalFormatter.string(from: cellState.date)
for event in self.calData.events! {
let eventDateString = self.globalFormatter.string(from: event.startDate)
if dateString != eventDateString {
cell.holidayBar.isHidden = true
cell.birthdayBar.isHidden = true
cell.defaultBar.isHidden = true
} else if event.category == "birthday" {
cell.birthdayBar.isHidden = false
} else if event.category == "holiday" {
cell.holidayBar.isHidden = false
} else if event.category == "default" {
cell.defaultBar.isHidden = false
} else {
return
}
}
}
There are four events in calData, 2 default, 1 holiday, and 1 birthday. The birthday and holiday events are on the same day and therefore both birthdayBar and holidayBar should be visible for that day. I also expect to see a defaultBar for the other two events however only the holidayBar is visible when I run the app.
The problem is that you appear to be trying to mess directly with the subviews of the cells of a UICollectionView. Don't do that. Just have the shown/hidden state of each subview be set in the cellForItemAt method, based on some easily accessible state (typically the contents of your data model). If the state changes, simply reload the collection view.
I want to create a console in my App in order to display activities or information on the screen. It will begin with a simple string, "Awaiting new messages..." and when different things happen, new messages will be added to that string. When the console fills up, the field will scroll with each addition so that the user is always seeing the most recent message at the bottom of the console, and old messages disappear off the view at the top.
Is there a way with "Truncate Head" and a multi-line UILabel? I attempted to do this with UILabel first, but I could find no way to always be viewing the END of the string. Truncate head actually works line-by-line, so it would show me the first five lines of the string and then the tail of the last visible line. I tried various alignment and wrapping settings but nothing worked... am I missing something? Is there a way to have a UILabel always display the end of a string and let the contents just disappear off the top?
Cut the string to fit each time? Maybe I could just cut the string to the last thousand characters or similar? But I don't know how big the UILabel will be (on different screens)... and even if I did, with fonts being what they are, I doubt I could know exactly the number of characters I should trim the string to. I can't trim it to a given amount of SPACE and get the amount of space in my UILabel, can I?
OR, I could use a UITextView and Scroll Maybe this is what I have to do. I can grab the entire value of the my text view's text and add the new string to it and put it back into the UITextView, and then scroll to the bottom using NSMakeRange and .scrollRangeToBottom.
func updateConsole(switchType: String) {
//unwind the console's text
if let tempString = consoleZe.text {
currentText = tempString
}
consoleZe.text = currentText + "A new message here! Something clever taken from \(switchType).\n"
//Scroll to the bottom
let bottom = NSMakeRange(consoleZe.text.characters.count - 1, 1)
consoleZe.scrollRangeToVisible(bottom)
}
That seems like a lot of work for my little update console. I don't care about scrolling to see past values. I'd even prefer the console didn't scroll... So grabbing, adding, pasting, getting the bottom and then scrolling seems like a lot of extra, unwanted baggage.
All thoughts on implementing a minimal console, using UILabel or UITextView or any other way are welcome, thank you!
I have implemented a “Console View Controller” using a tableview and a “ConsoleBuffer” class as the datasource. A tableview corresponds well to the line-by-line oriented nature of a message-logging console—and makes auto-scrolling easy.
The ConsoleBuffer is a singleton class which holds the console messages in a simple array of strings and some helper functions attached. Please see below the complete ConsoleBufferimplementation:
class ConsoleBuffer {
struct Prefs {
static let defaultLines = 100
static let maxLines = 1000
}
static let shared = ConsoleBuffer()
private var buffer = [String]() {
didSet {
if buffer.count > lines {
buffer.removeFirst(buffer.count - lines)
}
tableView?.reloadData()
NSAnimationContext.runAnimationGroup({ (context) in
if let tableView = self.tableView {
if let scrollView = tableView.enclosingScrollView {
let range = tableView.rows(in: scrollView.contentView.visibleRect)
let lastRow = range.location + range.length
if lastRow == oldValue.count - 1 {
context.allowsImplicitAnimation = true
tableView.scrollRowToVisible(buffer.count - 1)
}
}
}
}, completionHandler: nil)
}
}
var lines = ConsoleBuffer.Prefs.defaultLines {
didSet {
if lines > ConsoleBuffer.Prefs.maxLines {
lines = ConsoleBuffer.Prefs.maxLines
}
}
}
var count: Int {
get {
return buffer.count
}
}
var tableView: NSTableView?
private init() { }
func line(_ n: Int) -> String {
if n >= 0 && n < buffer.count {
return buffer[n]
} else {
return ""
}
}
func add(_ line: String) {
let dateStampedLine = "\(Date()) \(line)"
buffer.append(dateStampedLine)
}
func clear() {
buffer.removeAll()
}
}
These two statements make ConsoleBuffer a singleton:
static let shared = ConsoleBuffer()
private init() { }
Having a singleton makes it easy adding new console lines anywhere in your project without the need of having a reference to an instance of the class. Making init private prevents anyone from calling ConsoleBuffer()—rather you are forced to use its singleton instance: ConsoleBuffer.shared.
The console line strings are held in the buffer array which is private to keep its implementation hidden. When adding new lines to this array, the tableview smoothly scrolls to the last line added but only if previously the last line was displayed. Otherwise the scrolling position remains unchanged.
The datasource is now easy to implement:
func numberOfRows(in tableView: NSTableView) -> Int {
return ConsoleBuffer.shared.count
}
func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
let cell = tableView.make(withIdentifier: "ConsoleCell", owner: self) as? NSTableCellView
cell?.textField?.stringValue = ConsoleBuffer.shared.line(row)
return cell
}
In the tableview controller’s viewDidLoad function you need to set the tableView property of ConsoleBuffer to the tableview used. Also, this is the place to set the desired maximum number of lines to store in the buffer array:
ConsoleBuffer.shared.tableView = tableView
ConsoleBuffer.shared.lines = 500
Now you can add new lines to the console like this:
ConsoleBuffer.shared.add("console message")
Hope this gets you going into the right direction.
I am new to reactive programming, and Bond framework specifically. I know I may be doing things that are wrong in my basic understanding of this programming technique. This is the situation:
I have a UITextView and an "approve" UIButton.
I want the approve button to be enabled only when the text in the textView is not nil. I have tried adding these lines of code into my viewDidLoad method in my ViewController.
textView.reactive.text.observeNext{(text) in
self.message = text
print(text)
}
textView.reactive.text.map { $0 != nil}.bind(to: approveButtonOutlet.reactive.isEnabled)
The first action works (printing the text is happening successfully on every input change).
The second one does not work, the button is enabled both when the text is not nil and when it is.
Any help appreciated.
You can try like
RAC(self.approveButtonOutlet, enabled) = [self.textView.rac_textSignal map:^id(NSString *text) {
return #(text.length > 0);
}];
I'm not sure how it will be in swift 3 just try like
RAC(self.approveButtonOutlet, enabled) = self.textView.rac_textSignal.map({(text: String) -> void in
return (text.length > 0)
})
I found the issue was that I had a placeholder in my textView, which prevented from the text really being nil. So what eventually I did is this:
textView.reactive.text.map {
if $0 == placeholder {
return false
} else if $0 != nil {
return $0!.characters.count > 0
} else{
return false
}
}.bind(to: approveButtonOutlet.reactive.isEnabled)
I am working on UITests using XCode. I have multiple CollectionView cells.
When I perform Count in the collectionView it shows the certain count.
I can able to access first two cells but coming to the 3rd cell as 3(depends on device size). It says that specific button I am looking for in 3rd Cell as exists.
But isHittable is false.
Is there any way I can tap on the button on the 3rd Cell.
I have tried using the extension for forceTapElement() which is available online, it didn’t help.
Extension Used:
extension XCUIElement{
func forceTapElement(){
if self.isHittable{
self.tap()
}else{
let coordinate: XCUICoordinate = self.coordinate(withNormalizedOffset: .zero)
coordinate.tap()
}
}
}
Tried to perform swipeUp() and access the button. it still shows isHittable as false
The only way I've found is to swipe up untile the isHittable will be true.
app.collectionViews.cells.staticTexts["TEST"].tap()
Thread.sleep(forTimeInterval: 3)
let collectionView = app.otherElements.collectionViews.element(boundBy: 0)
let testAds = collectionView.cells
let numberOfTestAds = testAds.count
if numberOfTestAds > 0 {
let tester = collectionView.cells.element(boundBy: 2).buttons["ABC"]
for _ in 0..<100 {
guard !tester.isHittable else {
break;
}
collectionView.swipeUp()
}
}
Please note that the swipeUp() method will only move few pixels. If you want to use more comprehensive methods you can get AutoMate library and try swipe(to:untilVisible:times:avoid:from:):
app.collectionViews.cells.staticTexts["TEST"].tap()
Thread.sleep(forTimeInterval: 3)
let collectionView = app.otherElements.collectionViews.element(boundBy: 0)
let testAds = collectionView.cells
let numberOfTestAds = testAds.count
if numberOfTestAds > 0 {
let tester = collectionView.cells.element(boundBy: 2).buttons["ABC"]
collectionView.swipe(to: .down, untilVisible: tester)
// or swipe max 100 times in case 10 times is not enough
// collectionView.swipe(to: .down, untilVisible: tester, times: 100)
}
This is refrence link:
https://www.raywenderlich.com/107439/uicollectionview-custom-layout-tutorial-pinterest
I have implemented load more in UICollectionView at last cell , the data is getting downloaded, after download complete i want to reload collection
collectionView.collectionViewLayout.invalidateLayout()
self.collectionView.reloadData()
let concurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
dispatch_async(concurrentQueue) {
DataManager.sharedInstance.homeRequest(Dictionary: params, onSuccess: { (dict) in
self.downloadPageIndex += 1
let response = HomeObject(dictionary: dict)
for (_,e) in (response.dataDict?.enumerate())!{
self.dataArray.append(e)
}
dispatch_async(dispatch_get_main_queue()) {
self.collectionView.reloadData()
self.collectionView.collectionViewLayout.invalidateLayout()
}
self.activityIndicatorCell.stopAnimating()
}, onError: { (error) in
self.activityIndicatorCell.stopAnimating()
})
like this collectionview reloadata
Can any one help me out?
Ran into your question and am currently facing the same problem, after some digging i figured it out!
The problem is within the PintrestLayout file
Inside you will find code like (PROBLEM) :
guard cache.isEmpty == true, let collectionView = collectionView else {
return
}
Basically, if the attributes that you are applying to the CollectionView are empty (they are at the start), apply the PintrestLayout to it, and thats perfect! BUT If you are reloading the data and adding to the collectionView, you need to apply the layout again, but the cache isnt empty when you try to apply the layout to the collectionView again, thus it returns and doesn't work at all...
SOLUTION :
replace that code with :
guard cache.isEmpty == true || cache.isEmpty == false, let collectionView = collectionView else {
return
}
Now you are saying "i dont care if its empty or not, just apply the damn layout to my collectionView"
And Done! Now reloadData() will update how many cells you got...
This is pretty expensive though, if you wanna make it faster, and trim some memory etc... Then look into invalidateFlowLayout Documentation.