Swift - Characters in text field are not visible - swift

In my application I have several custom designed games to practice spelling and grammar. The whole process was working just fine until iOS 11 released.
An overview of how my application works:
the game receives a random word and picture from our server and automatically creates a stack of text fields (in a horizontal stack view)
the amount of text fields is similar to the amount of letters of the received word (e.g. the word "throw" would create five text fields next to each other)
the view is linked to a custom keyboard that is displayed until all questions are answered correctly
the text fields reset themselves if the user's input is not similar to the expected answer
The problem since iOS 11
Only the first two questions don't show the user's input in their respective text fields (the first letter always seem to work). The console also confirms that the input and the comparison of the letters does indeed work, but the text fields stay empty all the time. The game even resets perfectly and goes to the next question if the word has been answered correctly. The weird thing is that question 3, 4 and 5 do not have this problem.
In the example below you can see what is going on. The first question ("plant") only shows the first letter. The second question ("clap") does the same thing. The third question ("bite") does not have this problem. My custom cursor works just fine and automatically jumps to the next box.
Swift Code (functionality only)
let TEXT_FIELD_TAG = 1000
let CURSOR_INDEX = 0
var game: GameDictionary? {
didSet {
self.m_Client.GetMediaImage(game?.Image) { (image) in
self.imageView.image = image
}
self.selectedIndex = 0
self.answerChars = (game?.Content?.map { String($0).lowercased() })!
self.views = (0..<self.answerChars.count).map { _ in UITextField() }
var index: NSInteger = TEXT_FIELD_TAG
for textField in self.views {
textField.backgroundColor = .white
textField.textColor = Constants.MAIN_THEME_COLOR
textField.font = UIFont(name: Constants.GAME_FONT_TYPE_1, size: scaledFontSize(fontSize: 36))
textField.textAlignment = .center
textField.delegate = self
textField.layer.cornerRadius = 10
textField.autocapitalizationType = .none
textField.tag = index
textField.isUserInteractionEnabled = true
// Create the cursor object and animate the blinking
let cursor = UIView(frame: CGRect(x: scaledViewSize(viewSize: 10), y: scaledViewSize(viewSize: 10), width: 2, height: scaledViewSize(viewSize: 30)))
cursor.backgroundColor = Constants.MAIN_THEME_COLOR
cursor.isHidden = true
UIView.animate(withDuration: 1, delay: 0, options: .repeat, animations: {() -> Void in
cursor.alpha = 0
}, completion: {(_ animated: Bool) -> Void in
cursor.alpha = 1
}
)
textField.addSubview(cursor)
self.container.addArrangedSubview(textField)
index+=1
}
}
}
// MARK: Question handlers
override func prepareForReuse() {
super.prepareForReuse()
inputArray.removeAll()
answerChars.removeAll()
self.selectedIndex = 0
for view in self.views {
view.removeFromSuperview()
}
self.views.removeAll()
}
override func onFocus() {
self.resetCursorTextField()
keyboardLauncher.onKeyPressed = { character in
self.views[self.selectedIndex].text = character
self.textChanged(sender: self.views[self.selectedIndex])
}
keyboardLauncher.showKeyboard()
keyboardLauncher.enableKeyboard()
}
func resetCursorTextField() {
for (index, textfield) in self.views.enumerated() {
textfield.subviews[CURSOR_INDEX].isHidden = (index == 0) ? false : true
}
}
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
guard let text = textField.text else { return true }
let newLength = text.count + string.count - range.length
return newLength <= 1
}
func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool {
return false
}
func resetInputViews() {
inputArray.removeAll()
self.selectedIndex = 0
self.resetCursorTextField()
for tag in TEXT_FIELD_TAG...(TEXT_FIELD_TAG + (self.answerChars.count) - 1) {
let textField = self.container.viewWithTag(tag) as! UITextField!
textField?.text = ""
if (tag == TEXT_FIELD_TAG) {
textField?.becomeFirstResponder()
}
}
}
func textChanged(sender: UITextField) {
if (sender.text?.count)! > 0 {
inputArray.append((sender.text?.lowercased())!)
if self.answerChars[self.selectedIndex] != inputArray.last! {
self.delegate?.updateAnswer(inputArray.joined(), false)
self.resetInputViews()
} else {
self.selectedIndex+=1
let nextField = sender.superview?.viewWithTag(sender.tag + 1) as! UITextField!
nextField?.becomeFirstResponder()
sender.subviews[CURSOR_INDEX].isHidden = true
nextField?.subviews[CURSOR_INDEX].isHidden = false
if self.answerChars.count == inputArray.count {
playCorrectAnswer()
isCompleted = true
keyboardLauncher.disableKeyboard()
self.textField?.isEnabled = false
self.delegate?.updateAnswer(inputArray.joined(), true)
self.delegate?.nextQuestion(self)
}
}
}
}
CellForItemAtIndexpath
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if indexPath.item == self.m_Collection?.count {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: gameScoreOverviewCell, for: indexPath) as! GameScoreOverviewCell
cell.closeViewButton.addTarget(self, action: #selector(closeView(sender:)), for: .touchUpInside)
cell.rulesButton.addTarget(self, action: #selector(handleAlertScoreOverview(sender:)), for: .touchUpInside)
return cell
}
let game = self.m_Collection![indexPath.item]
if game.StarryGrade == StarryGrade.Grade4 && game.Level == Level.Level1 {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: fourthGradeCellLevel1, for: indexPath) as! GameCellFourthGradeLevel1
cell.cancelButton.addTarget(self, action: #selector(closeViewWithAlert(sender:)), for: .touchUpInside)
cell.questionLabel.text = "Question \(indexPath.item + 1) of \(self.m_Collection!.count)"
cell.game = game
cell.delegate = self
keyBoardIsVisible = true
return cell
}
}

This may not be a programmatic issue, but an interface issue. Try the following:
Ensure that you are compiling the app for Debug
Recreate the scenario in the left image below, where you have typed some letters and they are hidden:
Click this button in the Debug Navigator:
Select View UI Hierarchy.
In the deconstruction of your UI in Xcode, click on a text field that contains a hidden character.
You should now check a few things:
in the Object Inspector in the right panel, check to make sure that the Text Field's Title attribute is the character you typed (if not, the Text Field isn't capturing the keyboard input)
also in the Object Inspector, make sure that the View Visibility attribute is set to Not Hidden
in the Size Inspector (also in the right panel), check to make sure that the Text Field is big enough to show the character
I've had a similar issue before, and these steps allowed me to determine the cause of the problem. I hope this helps.

Related

Search Bar crashing app when inputting characters

I have a UITableView that is populating locations and a Search Bar set as the header of that UITableView.
Whenever certain characters are entered, or a certain amount of characters are entered, the app crashes, giving me no error code.
Sometimes the app crashes after inputting one character, maybe 2 characters, maybe 3, or maybe 4. There seems to be no apparent reason behind the crashing.
The search function properly searches and populates the filtered results, but for no apparent reason, crashes if a seemingly arbitrary amount of characters are inputted.
I have tried using the exception breakpoint tool already, and it is providing me with no new information. I think it has something to do with if there are no search results.
override func viewDidLoad() {
super.viewDidLoad()
searchController.searchResultsUpdater = self
searchController.dimsBackgroundDuringPresentation = false
searchController.searchBar.placeholder = "Search Locations..."
navigationItem.hidesSearchBarWhenScrolling = false
searchController.hidesNavigationBarDuringPresentation = false
locationTableView.tableHeaderView = searchController.searchBar
searchController.searchBar.sizeToFit()
searchController.searchBar.showsCancelButton = false
searchController.searchBar.barTintColor = UIColor.white
filteredData = locationList
// Sets this view controller as presenting view controller for the search interface
definesPresentationContext = true
locationList = createArray()
// Reload the table
let range = NSMakeRange(0, self.locationTableView.numberOfSections)
let sections = NSIndexSet(indexesIn: range)
self.locationTableView.reloadSections(sections as IndexSet, with: .fade)
}
func updateSearchResults(for searchController: UISearchController) {
filterContentForSearchText(searchController.searchBar.text!)
}
func searchBarIsEmpty() -> Bool {
// Returns true if the text is empty or nil
return searchController.searchBar.text?.isEmpty ?? true
}
func filterContentForSearchText(_ searchText: String) {
filteredData = locationList.filter({( locationName : Location) -> Bool in
return locationName.locationName.lowercased().contains(searchText.lowercased())
})
let range = NSMakeRange(0, self.locationTableView.numberOfSections)
let sections = NSIndexSet(indexesIn: range)
self.locationTableView.reloadSections(sections as IndexSet, with: .fade)
}
func isFiltering() -> Bool {
return searchController.isActive && !searchBarIsEmpty()
}
func locationTableView(_ locationTableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if isFiltering() {
return filteredData.count
}
return locationList.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let locationCell = locationTableView.dequeueReusableCell(withIdentifier: "locationCell", for: indexPath) as! locationCell
let location: Location
if isFiltering() {
location = filteredData[indexPath.row]
} else {
location = locationList[indexPath.row]
}
locationCell.setLocation(location: location)
return locationCell
}
The expected result is that the UITableView should populate with filtered results. Instead, it populates them and crashes if too many characters are inputted (usually 1-4 characters).
EDIT 1: I have found through debugging the error:
Thread 1: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)
appears on Line 2 on this block of code:
if isFiltering() {
location = filteredData[indexPath.row]
} else {
location = locationList[indexPath.row]
}
EDIT 2: This is the tutorial I used.
https://www.raywenderlich.com/472-uisearchcontroller-tutorial-getting-started
Seems like you are expecting the tableView to provide YOU with the number of sections... it is supposed to be driven by your own datasource.
Since you are not providing a numberOfSections in your data source I'm assuming it is 1. If all of your rows are in 1 section, all of the nifty reloading you are doing could be greatly simplified.
I suggest you read up on UITableView dataSource protocol at https://developer.apple.com/documentation/uikit/uitableviewdatasource
Reviewing the tutorial you are reading, it seems it is using a reloadData() which forces the tableView to ignore previous number of rows and reload its content with a new number of rows. And based on your findings so far, I would assume that is part of the root cause, with the tableview wrongly assuming a pre-determined number of rows and attempting to retrieve cells that are no longer within range.

After setting layout constant for label it stops working

I have a table view with automaticDimension for height and 2 cells.
One cell has a read more button, which updates the layout constraint constant for a label (as IBOutlet). It works fine and the cell height updates depending on the label height, but when I try to update the layout constraint first time on cellForRowAt method delegate it works, but after that it stops working from the read more button (and the layout constraint outlet doesn't become nil).
if cellType == .movie {
let movieCell = cell as! MovieDescriptionCell
movieCell.updateCellWith(movie: selectedMovie!)
movieCell.infoLabelHeight.constant = 200. ***(here is the problem, if I don't have this the read more button works)***
movieCell.readMorePressedBlock = { [weak self] (tag) in
guard let strongSelf = self else { return }
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(70), execute: {
strongSelf.tableView.reloadData()
})
}
}
#IBOutlet var infoLabelHeight: NSLayoutConstraint!
#IBAction func onReadMoreButton(button: UIButton) {
self.readMoreState = !self.readMoreState
setReadMoreImageFor(state: readMoreState)
let infoText = self.infoLabel.text
let expectedHeight = infoText?.height(withConstrainedWidth: infoLabelWidth.constant, font: self.infoTextFont) ?? self.infoDefaultHeight
self.infoLabelHeight.constant = self.readMoreState ? expectedHeight : self.infoDefaultHeight
readMorePressedBlock?(button.tag)
}
How can I make this work? If I delete the line "movieCell.infoLabelHeight.constant = 200" the read more button works, but I need to set the constant at the beginning too.
Thanks.
I found a solution, if I set the layout constraint in awakeFromNib method the read more button works (the possibility to change the constraint again)
override func awakeFromNib() {
super.awakeFromNib()
self.infoLabelHeight.constant = 200
}

How do I select column and row from a NSTableView?

I am writing a NSTableView based view. And I try to use:
let selectedRowNumber = tableViewMesos.selectedRow and
let selectedColumnNumber = tableViewMesos.selectedColumn to identify the textField that is in the cell View that I want to save into my array:
taulaDeConsumsMesos[selectedColumnNumber].filesConsum![selectedRowNumber] = sender.doubleValue
But a problem happens here, because the return value of selectedColumnNumber is -1 which is the value of non-selected Column for any row selected of any column.
But the selectedRowNumberreturns the index of the row properly.
So the program crashes when finishing the edition of a textfield in a cell. Because the selectedColumnNumber is -1 out of the limits of the array. See the code below to identify where this happens exactly.
Could you help me? I lost too many hours on this..
I'm new on OSX programing I hope you could help me.
The app layout
Here is the completed code:
class BalancGlobalViewController: NSViewController, NSTableViewDataSource, NSTableViewDelegate{
#IBOutlet weak var tableViewMesos: NSTableView!
let mesos = ColumnaDeConsumsMes() // contains an array of doubles called .filesConsum that represent the rows that I want to show
let consum = ColumnaDeConsumsMes()
var taulaDeConsumsMesos = [ColumnaDeConsumsMes]() // array of arrays that represent the columns with rows in it that will be the tableView data
func setUpMesos(){ // init table columns
mesos.filesConsum = [1, 2, 3, 4, 5, 6, 7, 8 , 9, 10, 11, 12, nil]
consum.filesConsum = [100, 100, 100, nil, nil, nil, nil, nil , nil, nil, nil, nil, nil]
taulaDeConsumsMesos.append(mesos)
taulaDeConsumsMesos.append(consum)
}
func setUpGradient(){
...not important here
}
override func viewDidLoad() {
super.viewDidLoad()
setUpMesos()
tableViewMesos.setDelegate(self)
tableViewMesos.setDataSource(self)
setUpGradient()
}
override func viewDidLayout() {
super.viewDidLayout()
setUpGradient()
}
#IBAction func alEditarUnTextFieldDeConsum(sender: NSTextField) { // this is the IBAction of the TextField of the Consum Column (see the image)
let selectedRowNumber = tableViewMesos.selectedRow
let selectedColumnNumber = tableViewMesos.selectedColumn
print("SelectedRowNumber! = \(selectedRowNumber)")
print("SelectedColumnNumber! = \(selectedColumnNumber)")
// the selectedColumnNumber return always -1 except when clicking the header of the column, then return 0 or 1 depending on the column. But it should return 0 even when the user edit a row of the index 0 column !!!
let numero = tableViewMesos.numberOfColumns
print("numero de columnes: \(numero)") // returns 2
if selectedRowNumber != -1 { //-1 is returned when no row is selected in the TableView
taulaDeConsumsMesos[selectedColumnNumber].filesConsum![selectedRowNumber] = sender.doubleValue //try to save here the value edited by the user on my array of arrays of doubles
//here the program crashes! selectedColumnNumber = -1
print("Sender! = \(sender.doubleValue)") // the sender returns the information correctly
//print("SelectedRowNumber! = \(selectedRowNumber)") // this works too!
reloadTaula()
}
}
func tableViewSelectionDidChange(notification: NSNotification) {
print("columna: \(tableViewMesos.selectedColumn)")
print("row: \(tableViewMesos.selectedRow)")
print("tag: \(tableViewMesos.selectedTag())")
}
// the tableViewMesos.selectedColumn return always -1 except when clicking the header of the colum, then return 0 or 1 depending on the column. But i should return 0 even when I edit a row of the index 0 column !!!
func reloadTaula() {
tableViewMesos.reloadData()
}
// MARK: - Datasource
func numberOfRowsInTableView(tableView: NSTableView) -> Int {
if (tableView.identifier == "TaulaMesos") {
return 13
}
}
// MARK: - Delegate
func tableView(tableView: NSTableView, viewForTableColumn tableColumn: NSTableColumn?, row: Int) -> NSView? {
var valorCela: Double
var cellIdentifier:String=""
if tableColumn == tableView.tableColumns[0] {
if taulaDeConsumsMesos[0].filesConsum![row] == nil {
return nil
}else {
valorCela = taulaDeConsumsMesos[0].filesConsum![row]!
cellIdentifier = "MesCellID"
}
} else {
if taulaDeConsumsMesos[1].filesConsum![row] == nil {
return nil
}else {
valorCela = taulaDeConsumsMesos[1].filesConsum![row]!
cellIdentifier = "ConsumCellID"
}
}
if let cell = tableView.makeViewWithIdentifier(cellIdentifier, owner: nil ) as? NSTableCellView {
cell.textField?.doubleValue = valorCela
return cell
}
return nil
}
}
Table views do not support selecting individual cells. Both styles of table view support selecting rows. Old-style NSCell-based table views support selecting columns, but modern view-based table views no longer support selecting columns. But it would never have been the case that both selectedRow and selectedColumn were valid at the same time.
From the 10.7 AppKit release notes, where view-based table views were introduced:
Note that the View Based TableView does not support column selection.
To get the row and column of the cell whose text field sent the action, you should use tableViewMesos.rowForView(sender) and tableViewMesos.columnForView(sender). Note, especially, that there's nothing guaranteeing that the text field that's sending the action method is in a selected row. So, don't think of it as selected. It's just the one that's been manipulated by the user.

How to limit text field entry to a certain range of numbers in Swift

enter image description hereI have managed to prevent the user from entering more than 2 digits in the 'month' field, using a text delegate function:
Swift code
However, I also want to prevent the user entering a number greater than 12. Can anyone point me in the right direction?
Thanks!
Add Int(myString) < 13 in your return condition with && operator.
in didload
txt_field.delegate=self
txt_field.addTarget(self, action:"submit:", forControlEvents: UIControlEvents.EditingChanged)
then define "submit" method as
#IBAction func submit(sender: AnyObject) {
let a:Int? = txt_field.text.toInt()
if a > 12{
print("number is greater than 12")
}
else{
print("number is less than 12")
}
}
"submit" method is called each time user stops editing the textfield. Hence you can check what user is entering and prevent him from entering value greater than 12.
Hope it helps.
Happy Coding.
textEdit.delegate = self from your view controllar
extension UserProfileViewController: UITextFieldDelegate {
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
let numberFiltered = string.components(separatedBy: NSCharacterSet(charactersIn: "0123456789").inverted).joined(separator: "")
guard string == numberFiltered, range.location < 2 else {
return false
}
if let newValue = textField.text?.intValue, let currentValue = string.intValue {
let totalValue = newValue*10 + currentValue
switch totalValue {
case 16..<80:
return true
default:
textField.text = ""
return false
}
}
return true
} }

Swift checking textfield input live

I've been struggling to find how to check the input of a textfield as a user is typing.
if a user types a word, it should change a label and an image according to some defined rules.
my code is working, but I'm always a step behind. (as it reads the content always before the next character is entered.
If it just to check the length I could use countElements(textfield) + 1, but I want it to also show that a user cannot use certain characters as they are typing, therefore that would not work for checking undesired characters.
I am assuming the function I am using is not the right one "shouldChangeCharacters". So I am a bit lost as to what to use. Is there a way to read a println or NSLog command to return to an outlet?
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
let passwordcheck = UserPasswordTextField.text
if UserPasswordTextField.isFirstResponder() {
if isValidPassword(passwordcheck) {
PWimg.image = UIImage(named: "passwordapprovedicon")
} else if passwordcheck.isEmpty {
PWimg.image = UIImage(named: "passwordiconwrong")
} else {
PWimg.image = UIImage(named: "passwordiconwrong")
}
}
func isValidPassword(testStr2:String) -> Bool {
println("validate password: \(testStr2)")
let passwordRegEx = "[A-Z0-9a-z._%+-:/><#]{6,30}"
if let passwordTest = NSPredicate(format: "SELF MATCHES %#", passwordRegEx) {
return passwordTest.evaluateWithObject(testStr2)
}
return false
Listen for UIControlEventEditingChanged events from the text field. Register for them either with
the Interface Builder: drag from the text field to the file, and select the action type "Editing Changed"
the following code:
override func viewDidLoad() {
super.viewDidLoad()
// ...
textField.addTarget(self, action:"edited", forControlEvents:UIControlEvents.EditingChanged)
}
func edited() {
println("Edited \(textField.text)")
}
Updated for swift 3:
override func viewDidLoad() {
super.viewDidLoad()
//...
textField.addTarget(self, action: #selector(textFieldDidChange), for:.editingChanged)
}
func textFieldDidChange(){
print(textField.text)
}
Updated for swift 4.2: just add #objc to func textFieldDidChange()
override func viewDidLoad()
{
super.viewDidLoad()
textField.addTarget(self,
action : #selector(textFieldDidChange),
for : .editingChanged)
}
#objc func textFieldDidChange()
{ print(textField.text ?? "Doh!") }