In ViewController - I have two text fields, like text field for text wihch user wants to reverse and text field for exclusions, and result-UILabel which shows the result.
In first text field user typing some text for reverse and result-UILabel shows the result of reversed text.
In the second text field, I want to make an exception for letters which shouldn't be reversed in result-UILabel. They should be untouchable in the word of the reverse at the time of reversing the text from the first field and should remain in their places.
The model function is in another swift file in another class.
Model function:
import Foundation
class ReverseWords {
public func reverse(textField: String) -> String {
if textField.isEmpty {
return ""
}
return textField.trimmingCharacters(in: .whitespacesAndNewlines)
.components(separatedBy: " ")
.map { String ( $0.reversed() ) }
.joined(separator: " ")
}
}
Using model function for first text field in ViewController:
resultLabel.text = reverseWords.reverse(textField:
reverseTextField.text!.trimmingCharacters(in: .whitespacesAndNewlines))
Example:
First text field (for reverse) print:
FogmEistEr
Second text field (for exclusions) letters which should be untouchable of reverse, print:
E
And result label shows:
rtsiEmgoEF
How can I implement this?
How can I call exceptionTextField in model to check his characters inside?
Actually, I don't want to do this between classes, but I would like to look at the solution.
I think it would be better to do it in ViewController, but I got confused…
Have you any ideas, how to implement this?
I'd just make an extension on String:
extension String {
func reversed(omittingCharactersIn characterSet: CharacterSet) -> String {
var reversed = reversed()
.filter { String($0).rangeOfCharacter(from: characterSet) == nil }
let excluded = enumerated()
.filter { String($0.element).rangeOfCharacter(from: characterSet) != nil }
for (i, char) in excluded {
reversed.insert(char, at: reversed.index(reversed.startIndex, offsetBy: i))
}
return String(reversed)
}
}
All this is doing is reversing all of the characters right away, but removing any characters that should not be reversed. Then it finds and maintains the indices and characters should not move. Finally, it inserts the characters that should not be reversed back to their original position.
Usage:
let text = "FogmEistEr"
let omit = "E"
print(text.reversed(omittingCharactersIn: CharacterSet(charactersIn: omit)))
Prints:
rtsiEmgoEF
Here's how I'd plug it into a viewController assuming the reversal would happen either by a button push or by the text simply changing:
class ViewController: UIViewController {
#IBOutlet weak var originalTextField: UITextField!
#IBOutlet weak var exclusionTextField: UITextField!
#IBOutlet weak var reversedLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
originalTextField.addTarget(self, action: #selector(textChanged(_:)), for: .editingChanged)
exclusionTextField.addTarget(self, action: #selector(textChanged(_:)), for: .editingChanged)
}
#objc func textChanged(_ sender: UITextField) {
tryReverse()
}
#IBAction func didTapButton(_ sender: UIButton) {
tryReverse()
}
private func tryReverse() {
guard let originalText = originalTextField.trimmedTextNilIfEmpty,
let exclusionText = exclusionTextField.trimmedTextNilIfEmpty else {
reversedLabel.text = ""
return
}
reversedLabel.text = originalText.components(separatedBy: " ")
.map {
$0.reversed(omittingCharactersIn: CharacterSet(charactersIn: exclusionText))
}.joined(separator: " ")
}
}
extension UITextField {
var trimmedTextNilIfEmpty: String? {
if let trimmed = text?.trimmingCharacters(in: .whitespacesAndNewlines) {
return trimmed.isEmpty ? nil : trimmed
}
return nil
}
}
Here you can take reference for logic (It can be optimized). We can pass exclude reverse function. Rather than put this logic in the view controller I would suggest keeping the logic in ReverseWords class.
class ReverseWords {
public func reverse(textField: String, excludeWord: String) -> String {
if textField.isEmpty {
return ""
}
return textField.trimmingCharacters(in: .whitespacesAndNewlines)
.components(separatedBy: " ")
.map { $0 == excludeWord ? String($0) : reverseWordWithExclude(currentWord: String($0), excludeWord: excludeWord) }
.joined(separator: " ")
}
private func reverseWordWithExclude(currentWord: String, excludeWord: String) -> String {
// return direct reversed string if the exclude word not contain in string
if !currentWord.contains(excludeWord) {
return String(currentWord.reversed())
}
// Replace whole exception word with single space char which is never included in the current word.
// Easy to find the current index for a single space or for a char.
let replaceWord = currentWord.replacingOccurrences(of: excludeWord, with: " ")
var reverseString = ""
var exceptionIndexes: [Int] = []
// Find the index of exclude word and reverse string without include exclude word
replaceWord.enumerated().forEach { index, char in
if char == " " {
exceptionIndexes.append(index)
} else {
reverseString.insert((char), at: reverseString.startIndex)
}
}
// Now replace single space with actual exclude word at their position.
exceptionIndexes.forEach{ index in
reverseString.insert(contentsOf: excludeWord, at: reverseString.index(reverseString.startIndex, offsetBy: index))
}
return reverseString
}
}
Related
My code seems too long and it's not even complete!
I am trying to avoid a duplicate in a search bar.
I have it working for the first entry only.
Been playing around with this for a while but I'm sure it's more simple than what I seem to be making it.
var search : String = ("1, ")
let numbers = search.map { String($0) }
let duplicates = Array(Set(numbers.filter({ (i: String) in numbers.filter({ $0 == i }).count > 1})))
for item in duplicates
{
search = search.replacingOccurrences(of: item, with: "")
}
if mainSearchBar.text != search
{
mainSearchBar.text = (mainSearchBar.text ?? "") + search
}
The first number entered (with the comma and blank, is a total of 3 characters) doesn't get duplicated when entered on the second occasion which is great.
I want any number already in the search bar to not be duplicated at all.
The above code is the function of button 1.
There are a few buttons.
There is a UISearchBarDelegate method that can help you with that. I hope this helps you:
with this method you ensure no duplicated character will be in your new String
func removeDuplicates(in searchText: String) -> String {
var newString = ""
searchText.forEach { character in
if !newString.contains(character) {
newString.append(character)
}
}
return newString
}
And then you have to inherit from UISearchBarDelegate
extension **YourControllerName**: UISearchBarDelegate {
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
searchBar.text = removeDuplicates(in: searchText)
}
}
In order to get this working you must set mainSearchBar.delegate = self somewhere in your code. Usually in viewDidLoad.
Thanks to emelagumat, I managed to get it working like this.
func removeDuplicates(in searchText: String) -> String
{
let search : String = (mainSearchBar.text ?? "")
var newString = search
searchText.forEach
{
character in
if !newString.contains(searchText)
{
newString.append(searchText)
}
}
return newString
}
#objc func button1Tap()
{
let search : String = ("1, ")
mainSearchBar.text = removeDuplicates(in: search)
}
This question already has answers here:
How to check if a text field is empty or not in swift
(16 answers)
Checking if textfields are empty Swift
(6 answers)
Closed 4 years ago.
I'm currently working on a project where I use lots of UITextFields. For validation I need to check if the UITextFields are empty. I got a working solution, but it's not that elegant. Maybe someone knows a better way of doing it.
Here is my solution:
// Check if text field is empty
if let text = textField.text, !text.isEmpty {
// Text field is not empty
} else {
// Text field is empty
}
Is there a faster way without unwrapping the text attribute of the text field to find out if it's empty?
Thanks!
How about extending UITextField…
extension UITextField {
var isEmpty: Bool {
if let text = textField.text, !text.isEmpty {
return false
}
return true
}
}
so then…
if myTextField.isEmpty {
}
You can use UIKeyInput property hasText. It works for both UITextField and UITextView:
if textField.hasText {
// Text field is not empty
} else {
// Text field is empty
}
If you would like to check if the text has not only spaces on it:
extension UITextField {
var isEmpty: Bool {
return text?.trimmingCharacters(in: .whitespacesAndNewlines) == ""
}
}
let tf = UITextField()
tf.text = " \n \n "
tf.isEmpty // true
If you have several textfields that you want to check, you could put them all in a guard statement
guard let text1 = textField1.text, let text2 = textField2.text, let text3 = textField3.text, !text1.isEmpty, !text2.isEmpty, !text3.isEmpty else {
//error handling
return
}
//Do stuff
I like to validate each text field depending on the content that should be provided by the user, i.e. emailTextField should contain a valid email address etc. While Ashley Mills answer is convenient, if you regard whitespace " " as text this will return false.
In your case, since you need to validate multiple text fields in the same way, why not extend UITextField as Ashley did with a static class method that can validate each text field passed as an array, in addition to this have other validation methods for each type of text field. Instead of returning a Boolean value I've learned to use guard instead. In this way guard let can be used to check if the validation fails (is nil) and execute the proper code, such as displaying a prompt to the user, or otherwise continue execution.
UITextFieldExtension.swift
import Foundation
import UIKit
extension UITextField {
/// Validates all text field are non-nil and non-empty, Returns true if all fields pass.
/// - Returns: Bool
static func validateAll(textFields:[UITextField]) -> Bool {
// Check each field for nil and not empty.
for field in textFields {
// Remove space and new lines while unwrapping.
guard let fieldText = field.text?.trimmingCharacters(in: .whitespacesAndNewlines) else {
return false
}
// Are there no other charaters?
if (fieldText.isEmpty) {
return false
}
}
// All fields passed.
return true
}
//A function that validates the email address...
func validateEmail(field: UITextField) -> String? {
guard let trimmedText = field.text?.trimmingCharacters(in: .whitespacesAndNewlines) else {
return nil
}
//email addresses are automatically detected as links in i0S...
guard let dataDetector = try? NSDataDetector(types: NSTextCheckingResult.CheckingType.link.rawValue) else {
return nil
}
let range = NSMakeRange(0, NSString(string: trimmedText).length)
let allMatches = dataDetector.matches(in: trimmedText,
options: [],
range: range)
if allMatches.count == 1,
allMatches.first?.url?.absoluteString.contains("mailto:") == true
{
return trimmedText
}
return nil
}
func validateUserName(field: UITextField) -> String? {
guard let text:String = field.text else {
return nil
}
/* 3 to 12 characters, no numbers or special characters */
let RegEx = "^[^\\d!##£$%^&*<>()/\\\\~\\[\\]\\{\\}\\?\\_\\.\\`\\'\\,\\:\\;|\"+=-]+$"
let Test = NSPredicate(format:"SELF MATCHES %#", RegEx)
let isValid = Test.evaluate(with: text)
if (isValid) {
return text
}
return nil
}
/*6 to 16 Characters */
func validatePassword(field: UITextField) -> String?{
guard let text:String = field.text else {
return nil
}
/*6-16 charaters, and at least one number*/
let RegEx = "^(?=.*\\d)(.+){6,16}$"
let Test = NSPredicate(format:"SELF MATCHES%#", RegEx)
let isValid = Test.evaluate(with: text)
if (isValid) {
return text
}
return nil
}
}
Meanwhile, elsewhere...
if (UITextField.validateAll(textFields: [emailTextField, nameTextField])) {
// Do something
}
I wrote a method that removes all 2 duplicate characters in String, for example
I need delete only char that contains twice, for example
"bndkss" -> "bndk"
"nnmmhj" - > "hj"
"aaabbaac" -> "ac
"abba" -> ""
I wrote on objc and everything works, but Swift is not working, help please, where did I go wrong?
override func viewDidLoad() {
super.viewDidLoad()
let string = "baab"
print("before: \(string)")
let stringAfter = checkString(string: string)
print("after: \(stringAfter)")
}
func checkString(string : String) -> String {
var tempString = string
for (index, element) in string.characters.enumerated() {
for (index2, element2) in string.characters.enumerated() {
if element == element2 && index != index2 {
if index > index2 {
tempString.remove(at: tempString.index(tempString.startIndex, offsetBy: index))
tempString.remove(at: tempString.index(tempString.startIndex, offsetBy: index2))
} else {
tempString.remove(at: tempString.index(tempString.startIndex, offsetBy: index2))
tempString.remove(at: tempString.index(tempString.startIndex, offsetBy: index))
}
if tempString.characters.count < 1 {
return ""
} else {
checkString(string: tempString)
}
} else {
if index == tempString.characters.count - 1 && index2 == tempString.characters.count - 1 {
return tempString
}
}
}
}
return ""
}
Updates:
just need
return checkString(string: tempString)
instead
checkString(string: tempString)
There are two problems in your code, such as
After removing characters in tempString, the indices index and index2 to long refer to the original characters in tempString.
Wrong characters are removed as a consequence.
You call checkString() recursively but discard the result.
Update: As you already noticed in the meantime, return checkString(string: tempString) solves these problems.
Here is an alternative implementation. The idea is to use a dictionary
to remember where a character has been seen last, and an index set
which keeps track of the positions of the characters which are to
be preserved. Instead of two nested loops and recursion, two "simple"
loops are used here, plus the cost of the dictionary and set operations.
func removeDuplicateCharacters(string: String) -> String {
var seen = [Character: Int]()
var keep = IndexSet(integersIn: 0..<string.characters.count)
for (idx, c) in string.characters.enumerated() {
if let prevIndex = seen[c] {
keep.remove(prevIndex)
keep.remove(idx)
seen.removeValue(forKey: c)
} else {
seen[c] = idx
}
}
return String(keep.map { string[string.index(string.startIndex, offsetBy: $0)] })
}
Examples:
print(removeDuplicateCharacters(string: "bndkss")) // ""bndk"
print(removeDuplicateCharacters(string: "nnmmhj")) // "jh"
print(removeDuplicateCharacters(string: "abba")) // ""
print(removeDuplicateCharacters(string: "aaabbaac")) // "ac"
Martin wrote a much cleaner version than myself, but I worked on this for a little while so I figured I'd post it to show you another way it could have been accomplished.
func removeDuplicates(from original: String) -> String {
var originalString = original
var newString = ""
for character in originalString.characters {
if !newString.contains("\(character)") {
newString.append(character)
originalString = originalString.characters.filter { $0.description != "\(character)" }.map { "\($0)" }.joined(separator: "")
} else {
newString = newString.characters.filter { $0.description != "\(character)" }.map { "\($0)" }.joined(separator: "")
}
}
return newString
}
i'm new the forum and also new to the swift language. I've been playing around with xcode and wanted to create an app that uses a fenceloop to display the factors of a number as a "solution." The app currently uses a label to display, a text for input, and a button to initiate. I have what i think to be functioning code but i can't see to get it to work because from what i understand, i have to convert the input that's a string into an int. If anyone has any ideas how to get this working; since i feel like i've done what i can.
The problem in particular i am getting is it is saying that "Cannot convert value of type 'UITextField!; to expected argument type 'Int'. What i intend to happen is that on the button click, it solves for the factors of whatever is in the text box and displays it as a string in the label. Any help is appreciated!
import UIKit
class ViewController: UIViewController {
#IBOutlet var input1 : UITextField!
#IBOutlet var label : UILabel!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
#IBAction func printFactors(n: Int) {
var result: String = ""
for i in 1...n {
guard n % i == 0 else {continue}
result += i == 1 ? "1" : " and \(i)"
}
print(result)
let outputText = printFactors(n: input1)
label.text = outputText
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
You have a lot of issues and confusion in your printFactors method. Lets split it up and setup things properly.
First, make a separate method to do the math:
func calculateFactors(n: Int) -> String {
var result: String = ""
for i in 1...n {
guard n % i == 0 else {continue}
result += i == 1 ? "1" : " and \(i)"
}
print(result)
return result
}
Now lets setup the button action:
#IBAction func factorAction(sender: UIButton) {
if let text = input1.text {
if let num = Int(text) {
let factor = calculateFactors(n: num)
label.text = factor
} else {
// Show the user that the entered text isn't a number
}
} else {
// There's no text
}
}
Setup your button to use the new factoryAction: method instead of the old printFactors: method.
Swift 3
Can reduce this code down to two lines with some functional magic.
func factors(of number: Int) -> [Int] {
return (1...number).filter { number % $0 == 0 }
}
print(factors(of: 24))
textField.text.isEmpty
textField.text != ""
These two functions regard spaces as characters. However, I would like my to recognise when the text field has no spaces or is not empty.
Just check if the trimmed string is empty
let isEmpty = str.stringByTrimmingCharactersInSet(.whitespaceAndNewlineCharacterSet()).isEmpty
You can even put it in an String extension:
extension String {
var isReallyEmpty: Bool {
return self.stringByTrimmingCharactersInSet(.whitespaceAndNewlineCharacterSet()).isEmpty
}
}
It even works for a string like var str = " " (that has spaces, tabs and zero-width spaces).
Then simply check textField.text?.isReallyEmpty ?? true.
If you wanna go even further (I wouldn't) add it to an UITextField extension:
extension UITextField {
var isReallyEmpty: Bool {
return text?.stringByTrimmingCharactersInSet(.whitespaceAndNewlineCharacterSet()).isEmpty ?? true
}
}
Your code becomes textField.isReallyEmpty.
In Swift 4,
extension String {
var isReallyEmpty: Bool {
return self.trimmingCharacters(in: .whitespaces).isEmpty
}
}
I used this from #Pieter21 's link.
It works for as many blank spaces a user could enter.
let myString = TextField.text
let trimmedString = myString!.stringByTrimmingCharactersInSet(
NSCharacterSet.whitespaceAndNewlineCharacterSet()
)
if trimmedString != "" {
// Code if not empty
} else {
// Code if empty
}