How to override NumberFormatter string and number methods - ios

NumberFormatter has a couple methods, number and string which seem to imply they are responsible for converting the value to and from each type. However, when trying to override these methods I can't get them to fire.. (the print statements are never seen).
Am I missing something? The only progress I made was with the getObjectValue(_:for:range:) method in setting the numerical value, but never the string.
import SwiftUI
class NumberProxy : NumberFormatter {
override func string(from number: NSNumber) -> String? {
print("hello from", number)
return "HELLO!"
}
override func number(from string: String) -> NSNumber? {
print("to number...", string)
return NSNumber(value: 123)
}
// override func getObjectValue(_ obj: AutoreleasingUnsafeMutablePointer<AnyObject?>?, for string: String, range rangep: UnsafeMutablePointer<NSRange>?) throws {
// try super.getObjectValue(obj, for: string, range: rangep)
// print(obj)
// obj?.pointee = NSNumber(value: 4.0) // this worked
// }
}
struct Test: View {
#State private var myNumber: Int = 0
var body : some View {
TextField("Current Balance", value: $myNumber, formatter: NumberProxy())
}
}

What you need to override is Formatter's string(for:) method:
override func string(for obj: Any?) -> String? {
class NumberProxy: NumberFormatter {
override func string(for obj: Any?) -> String? {
guard let value = (obj as? NSNumber)?.intValue else { return nil }
return .init(value)
}
}
let number = NSNumber(value: 123.456)
let string = NumberProxy().string(from: number) // "123"
edit/update:
To input Double as integers:
override func getObjectValue(_ obj: AutoreleasingUnsafeMutablePointer<AnyObject?>?, for string: String, range rangep: UnsafeMutablePointer<NSRange>?) throws {
try super.getObjectValue(obj, for: string, range: rangep)
obj?.pointee = Int(string.filter(\.isWholeNumber)) as AnyObject?
}
let integer = NumberProxy().number(from: "123.45")!.intValue // 12345

Related

Swift. Missing argument label 'bytes:' in call` while using Hasher.combine with string

Hasher.combine fails with compile error: Error:(31, 24) Missing argument label 'bytes:' in call when I try to pass a string. Here is the code:
private class AssetCacheKey : NSObject {
let id: String
let size: CGSize
init(id: String, size: CGSize) {
self.id = id
self.size = size
}
private func isEqual(_ other: AssetCacheKey) -> Bool {
return other.id == id && other.size == size
}
override func isEqual(_ object: Any?) -> Bool {
return (object as? AssetCacheKey)?.isEqual(self) ?? false
}
override class func hash() -> Int {
var hasher = Hasher()
hasher.combine(id)
//hasher.combine(size)
return hasher.finalize()
}
}

Type casting value from UserDefault throws error

I am trying to fetch a data from UserDefault but when I am doing this I am getting error
var sharedPreference: UserDefaults = UserDefaults.init(suiteName: "user-key-value")!
func getLastLoginClientId() -> Int64? {
for (key, value) in sharedPreference.dictionaryRepresentation() {
if key == LAST_USER {
return value as! Int64
}
}
return nil
}
I am getting that my key is having some value but when returning it, it throws error.
This is how I save
func setLastLoginClientId(clientId: Int64) {
sharedPreference.set(clientId, forKey: LAST_USER)
sharedPreference.synchronize()
}
I think you could do something as simple as
func getLastLoginClientId() -> Int64? {
return sharedPreference.value(forKey: LAST_USER) as? Int64
}
Here is what I've tested
struct CustomUserDefaults {
var sharedPreference : UserDefaults = UserDefaults.init(suiteName: "user-key-value")!
let LAST_USER = "test"
func test() {
let value = Int64(20.0)
self.setLastLoginClientId(value)
let testValue = getLastLoginClientId()
print(testValue) // 20.0
}
func setLastLoginClientId(_ value: Int64) {
sharedPreference.set(value, forKey: LAST_USER)
}
func getLastLoginClientId() -> Int64? {
return sharedPreference.value(forKey: LAST_USER) as? Int64
}
}

String initializer for optional primitive types: Int? Double? Float? etc

Is it possible to have an extension for all of the above types without specifying each type individually?
For example, here is such extension for Double:
extension String {
init?(_ value: Double?) {
if let nonOpt = value {
self.init(nonOpt)
} else {
return nil
}
}
}
let nonOpt: Double = 1
let opt: Double? = 1
let string = String(opt)
print(string)
I'd like to allow string initialization with optional type if it is possible to initialize string with the original type.
Do you mean something like this
extension String {
init?<T : CustomStringConvertible>(_ value : T?) {
guard let value = value else { return nil }
self.init(describing: value)
}
}
or
extension String {
init?<T : LosslessStringConvertible>(_ value : T?) {
guard let value = value else { return nil }
self.init(value)
}
}
Rather than declaring an custom initializer, just use map to map the value to a String as such:
let optDouble: Double? = nil
let optionalDoubleString = opt.map { String($0) }
let optInt: Int? = nil
let optionalIntString = opt.map { String($0) }

Searching a UITableView and ignoring punctuation

Is there a way to search through a UITableView and ignore certain characters like commas or dots?
I.e. I would like to search for "St George" but my data set contains "St. George" so the result is always zero.
EDITED Q:
func filteredArray(searchText: NSString) {
if searchText == "" {
array_search = array_all
} else {
showTableViewResults()
array_search.removeAll()
for i in 0 ..< array_all.count {
let object:MyObject = array_all[i]
let languageSearchString = object.myObjectName
let searchStr:String = languageSearchString!
if searchStr.lowercased().contains(searchText.lowercased) {
array_search.append(object)
}
}
}
tableView.reloadData()
recordsFoundLabel.text = "records found: \(array_search.count)"
}
You can filter all characters thats not a letter out of your String before performing your search. The same applies to the table view data source elements. Also as mentioned by rmaddy you should implement a case insensitive search:
edit/update Swift 5.2 or later
extension StringProtocol {
func caseInsensitiveContains<S: StringProtocol>(_ string: S) -> Bool { range(of: string, options: .caseInsensitive) != nil }
}
extension StringProtocol where Self: RangeReplaceableCollection {
var letters: Self { filter(\.isLetter) }
}
Testing:
let search = "st george"
let tableViewSource = ["Apple", "Orange", "Banana", "St. George"]
let filtered = tableViewSource.filter {
$0.letters.caseInsensitiveContains(search.letters)
}
print(filtered) // ["St. George"]
If you would like to literally just remove punctuation from your String (note that would keep the spaces in your String), you can do as follow:
extension StringProtocol where Self: RangeReplaceableCollection {
mutating func removePunctuation() { removeAll(where: \.isPunctuation) }
}
extension Bool {
var negated: Bool { !self }
}
extension StringProtocol where Self: RangeReplaceableCollection {
var removingPunctuation: Self { filter(\.isPunctuation.negated) }
}
Testing:
let filtered = tableViewSource.filter {
$0.removingPunctuation.caseInsensitiveContains(search.removingPunctuation)
}
print(filtered) // ["St. George"]
If you would like to implement the same logic as Xcode autocomplete you would need to do a search for each character and change the startIndex of the string searched:
extension StringProtocol where Self: RangeReplaceableCollection {
func containsCharactersInSequence<S: StringProtocol>(_ string: S, options: String.CompareOptions = []) -> (result: Bool, ranges: [Range<Index>]) {
var found = 0
var startIndex = self.startIndex
var index = string.startIndex
var ranges: [Range<Index>] = []
while index < string.endIndex,
let range = self[startIndex...].range(of: string[index...index], options: options) {
ranges.append(range)
startIndex = range.upperBound
string.formIndex(after: &index)
found += 1
}
return (found == string.count, ranges)
}
}
Playground Testing:
let search = "stgre"
let tableViewSource = ["Apple", "Orange", "Banana", "St. George"]
let filtered = tableViewSource.filter {
$0.containsCharactersInSequence(search, options: .caseInsensitive).result
}
print(filtered) // ["St. George"]
I think you should simply implement a function that for any given string, will return te same string without any point, (or whatever you want to erase); such as :
func erase(characters: [String], fromText text: String) -> String {
var result = String()
for character in text.characters {
if !characters.contains(character) {
result += String(character)
}
}
return result
}
(I cant test it from where i am but you get the idea right ?)
Hope it helps

Class that subclasses PFObject and conforms to JSQMessageData

I currently have a JSQMessagesViewController, but am running to the problem of storing my JSQMessages in Parse.com, since Parse.com won't allow this type of object. I've been trying to research ways of creating a class that subclasses PFObject while conforming to JSQMessageData so that I can create messages that are of type PFObject and thus able to be stored in Parse.com.
Below is my Messages class. In my JSQMessagesViewController I would typically call
var newMessage = JSQMessage(senderId: "user", displayName: "user", text: "testing chat")
But now how would I do this with my Messages class?
import Foundation
import UIKit
import Parse
class Message: PFObject, PFSubclassing, JSQMessageData {
var senderId_ : String!
var senderDisplayName_ : String!
var date_ : NSDate
var isMediaMessage_ : Bool
var hash_ : Int = 0
var text_ : String
override class func initialize() {
var onceToken : dispatch_once_t = 0;
dispatch_once(&onceToken) {
self.registerSubclass()
}
}
class func parseClassName() -> String {
return "Messages"
}
convenience init(senderId: String?, text: String?) {
self.init(senderId: senderId!, senderDisplayName: senderId, isMediaMessage: false, hash: 0, text: text!)
}
init(senderId: String, senderDisplayName: String?, isMediaMessage: Bool, hash: Int, text: String) {
self.senderId_ = senderId
self.senderDisplayName_ = senderDisplayName
self.date_ = NSDate()
self.isMediaMessage_ = isMediaMessage
self.hash_ = hash
self.text_ = text
super.init()
}
func senderId() -> String? {
return senderId_;
}
func senderDisplayName() -> String? {
return senderDisplayName_;
}
func date() -> NSDate? {
return date_;
}
func isMediaMessage() -> Bool {
return isMediaMessage_;
}
func hash() -> UInt? {
return UInt(hash_);
}
func text() -> String! {
return text_;
}
func messageHash() -> UInt {
return UInt(hash_)
}
}
And my implementation in my ChatView that is a JSQMessagesViewController:
override func viewDidLoad(){
super.viewDidLoad()
var myObject = PFObject(className: "messages")
var myMessage = Message(senderId: "User", text: "Some text")
var messageArray:NSMutableArray = [myMessage]
myObject["myArray"] = messageArray
myObject.save()
}
Currently receiving error
fatal error: use of unimplemented initializer 'init()' for class Message

Resources