Calculator in Swift a method [rangeOfString] - objective-c

I am making calculator in Swift for IOS 8. All operand and operator works very well. Only problem is with "."
For example, 192.168.0.1 is invalid. The "." should only display once. Instead 192.16801 is valid.
See below code -
//Assume user has entered 192.168
//User cannot press "." button again now, "." should not appear again
var display.text 192.168`
if display.text.rangeOfString(".") != nil{
println("exists")
}

import UIKit
class ViewController: UIViewController, UITextFieldDelegate {
#IBOutlet weak var textField: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
self.textField.delegate = self
}
//Textfield delegates
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool { // return NO to not change text
switch string {
case "0","1","2","3","4","5","6","7","8","9":
return true
case ".":
let array = Array(textField.text)
var decimalCount = 0
for character in array {
if character == "." {
decimalCount++
}
}
if decimalCount == 1 {
return false
} else {
return true
}
default:
let array = Array(string)
if array.count == 0 {
return true
}
return false
}
}
}
the textField functions will return a value of true if you have entered a valid number, it will return a value of false if you input anything else than a number or if you enter a second “.”. If this functions returns false it will not change the value of the text field. Example: if a user (tries to) input 3.14a9 the a will be ignored and the textfield text will be 3.149. If a user inputs 3.45.12 the textfield will have a text value of 3.45.12

I’m not sure this is the question, but if you’re looking for a second “.” you can use the find function with a where for this:
let decimal: Character = "."
if let first = find(display.text, decimal)
where (find(display.text[first.successor()..<display.text.endIndex], decimal) != nil) {
println("second exists")
}
else {
println("none or one")
}

You can use regular expression to validate your textfield whenever its value changes.
Example of a regulator
/*
^ //begin string
([0-9]+)? //optional: has 0 or more digits
(\\.)? //optional: has a point
([0-9]+)? //optional: has 0 or more digits
$ //end string
*/
extension String {
func isValidNumber() -> Bool {
let regex = NSRegularExpression(pattern: "^([0-9]+)?(\\.)?([0-9]+)?$", options: .CaseInsensitive, error: nil)
return regex?.firstMatchInString(self, options: nil, range: NSMakeRange(0, countElements(self))) != nil
}
}
Used in the call-back of your UITextField:
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
return textField.text.isValidNumber()
}

var text = "192.168.2"
var isValid = true
let range = text.rangeOfString(".")
if let range = range {
let subText = text.substringFromIndex(range.endIndex)
let range2 = subText.rangeOfString(".")
if range2 != nil {
isValid = false
}
}
println("isValid: \(isValid)") // isValid: false

Related

SwiftUI: Is it possible to automatically move to the next textfield after 1 character is entered?

I trying to make a SwiftUI app where after entering one letter in a TextField the cursor automatically moves to the next TextField. The UI is pretty much like this.
In Swift/IB, it looks like this was done with delegates and adding a target like in this post:
How to move to the next UITextField automatically in Swift
But can't find any documentation for using delegates/targets in SwiftUI.
I tried following this post:
SwiftUI TextField max length
But this has not worked for me. Setting the .prefix(1) does not seem to make a difference. The TextField still accepts any amount of characters and when moved to the next TextField does not reduce the characters entered to only the first character.
In SwiftUI's current state, is it possible to automatically move to the next TextField after 1 character is entered?
Thanks for any help!
It can be done in iOS 15 with FocusState
import SwiftUI
///Sample usage
#available(iOS 15.0, *)
struct PinParentView: View {
#State var pin: Int = 12356
var body: some View {
VStack{
Text(pin.description)
PinView(pin: $pin)
}
}
}
#available(iOS 15.0, *)
struct PinView: View {
#Binding var pin: Int
#State var pinDict: [UniqueCharacter] = []
#FocusState private var focusedField: UniqueCharacter?
var body: some View{
HStack{
ForEach($pinDict, id: \.id, content: { $char in
TextField("pin digit", text:
Binding(get: {
char.char.description
}, set: { newValue in
let newest: Character = newValue.last ?? "0"
//This check is only needed if you only want numbers
if Int(newest.description) != nil{
char.char = newest
}
//Set the new focus
DispatchQueue.main.async {
setFocus()
}
})
).textFieldStyle(.roundedBorder)
.focused($focusedField, equals: char)
})
}.onAppear(perform: {
//Set the initial value of the text fields
//By using unique characters you can keep the order
pinDict = pin.description.uniqueCharacters()
})
}
func setFocus(){
//Default to the first box when focus is not set or the user reaches the last box
if focusedField == nil || focusedField == pinDict.last{
focusedField = pinDict.first
}else{
//find the index of the current character
let idx = pinDict.firstIndex(of: focusedField!)
//Another safety check for the index
if idx == nil || pinDict.last == pinDict[idx!]{
focusedField = pinDict.first
}else{
focusedField = pinDict[idx! + 1]
}
}
//Update the Binding that came from the parent
setPinBinding()
}
///Updates the binding from the parent
func setPinBinding(){
var newPinInt = 0
for n in pinDict{
if n == pinDict.first{
newPinInt = Int(n.char.description) ?? 0
}else{
newPinInt = Int(String(newPinInt) + n.char.description) ?? 0
}
}
pin = newPinInt
}
}
//Convert String to Unique characers
extension String{
func uniqueCharacters() -> [UniqueCharacter]{
let array: [Character] = Array(self)
return array.uniqueCharacters()
}
func numberOnly() -> String {
self.trimmingCharacters(in: CharacterSet(charactersIn: "-0123456789.").inverted)
}
}
extension Array where Element == Character {
func uniqueCharacters() -> [UniqueCharacter]{
var array: [UniqueCharacter] = []
for char in self{
array.append(UniqueCharacter(char: char))
}
return array
}
}
//String/Characters can be repeating so yu have to make them a unique value
struct UniqueCharacter: Identifiable, Equatable, Hashable{
var char: Character
var id: UUID = UUID()
}
#available(iOS 15.0, *)
struct PinView_Previews: PreviewProvider {
static var previews: some View {
PinParentView()
}
}

Trouble converting NSData Objective-C code to Swift

I've been having issues converting an Objective-C snippet to Swift that uses NSData and CoreBluetooth. I have looked at this question and a couple others dealing with NSData in Swift but haven't had any success.
Objective-C Snippet:
- (CGFloat) minTemperature
{
CGFloat result = NAN;
int16_t value = 0;
// characteristic is a CBCharacteristic
if (characteristic) {
[[characteristic value] getBytes:&value length:sizeof (value)];
result = (CGFloat)value / 10.0f;
}
return result;
}
What I have so far in Swift (not working):
func minTemperature() -> CGFloat {
let bytes = [UInt8](characteristic?.value)
let pointer = UnsafePointer<UInt8>(bytes)
let fPointer = pointer.withMemoryRebound(to: Int16.self, capacity: 2) { return $0 }
value = Int16(fPointer.pointee)
result = CGFloat(value / 10) // not correct value
return result
}
Does the logic look wrong here? Thanks!
One error is in
let fPointer = pointer.withMemoryRebound(to: Int16.self, capacity: 2) { return $0 }
because the rebound pointer $0 is only valid inside the closure and must
not be passed to the outside. Also the capacity should be 1 for a
single Int16 value. Another problem is the integer division in
result = CGFloat(value / 10)
which truncates the result (as already observed by the4kman).
Creating an [UInt8] array from the data is not necessary, the
withUnsafeBytes() method of Data can be used instead.
Finally you could return nil (instead of "not a number") if no
characteristic value is given:
func minTemperature() -> CGFloat? {
guard let value = characteristic?.value else {
return nil
}
let i16val = value.withUnsafeBytes { (ptr: UnsafePointer<Int16>) in
ptr.pointee
}
return CGFloat(i16val) / 10.0
}
You should make the return value optional and check if characteristic is nil in the beginning with a guard. You should also explicitly convert the value to CGFloat, then divide it by 10.
func minTemperature() -> CGFloat? {
guard characteristic != nil else {
return nil
  }
let bytes = [UInt8](characteristic!.value)
let pointer = UnsafePointer<UInt8>(bytes)
let fPointer = pointer.withMemoryRebound(to: Int16.self, capacity: 2) { return $0 }
let value = Int16(fPointer.pointee)
result = CGFloat(value) / 10
return result
}

NSComboBox items misplaced if user drag an app window on mac OS

I work at desktop application for OS X and macOS and have issue with native NSComboBox (not subclass).
It is the problem that comboBox items misplaces as you can see on screenshot below.
Also a have a warning in XCode console output:
This application is trying to draw a very large combo box, 28 points tall. Vertically resizable combo boxes are not supported, but it happens that 10.4 and previous drew something that looked kind of sort of okay. The art in 10.5 does not break up in a way that supports that drawing. This application should be revised to stop using large combo boxes. This warning will appear once per app launch.
Could this be a warning associated with this problem? I tried to fix warning but failed.
I use Xcode 8.2.1 and OS X El Capitan 10.11.6.
Edited:
Current ViewController contained in custom navigation controller and have combobox delegate/datasource implementation.
View added as below to navigation container:
addChildViewController(viewController)
viewController.view.frame.size = contentView != nil ? contentView!.frame.size : CGSize.zero
viewController.view.frame.origin = CGPoint.zero
viewController.view.autoresizingMask = [.viewWidthSizable, .viewHeightSizable]
if let animation = animation {
contentView?.layer?.add(animation, forKey: animation.type)
}
contentView?.addSubview(viewController.view)
ComboBox datasource implementation
public func numberOfItems(in comboBox: NSComboBox) -> Int {
switch comboBox {
case companyComboBox:
return filteredCompanies?.count ?? 0
case projectComboBox:
return filteredProjects?.count ?? 0
default:
return 0
}
}
public func comboBox(_ comboBox: NSComboBox, objectValueForItemAt index: Int) -> Any? {
switch comboBox {
case companyComboBox:
return company(at: index)?.name
case projectComboBox:
return project(at: index)?.name
default:
return nil
}
}
public func comboBox(_ comboBox: NSComboBox, indexOfItemWithStringValue string: String) -> Int {
switch comboBox {
case companyComboBox:
return selectedCompany == nil ? -1 : comboBox.indexOfSelectedItem
case projectComboBox:
return selectedProject == nil ? -1 : comboBox.indexOfSelectedItem
default:
return -1
}
}
public func comboBox(_ comboBox: NSComboBox, completedString string: String) -> String? {
switch comboBox {
case companyComboBox:
let bestMatched = bestMatchedCompany(for: string)
filteredCompanies = filterCompanies(by: string)
return bestMatched?.name
case projectComboBox:
let bestMatched = bestMatchedProject(for: string)
filteredProjects = filterProjects(by: string)
return bestMatched?.name
default:
return nil
}
}
ComboBox delegate implementation:
//MARK: NSComboBoxDelegate
func comboBoxSelectionDidChange(_ notification: Notification) {
let comboBox = notification.object as! NSComboBox
let selectedIndex = comboBox.indexOfSelectedItem
guard selectedIndex >= 0 else { return }
switch comboBox {
case companyComboBox:
selectedCompany = company(at: selectedIndex)
case projectComboBox:
selectedProject = project(at: selectedIndex)
default:
break
}
}
func control(_ control: NSControl, textView: NSTextView, doCommandBy commandSelector: Selector) -> Bool {
guard let comboBox = control as? NSComboBox, comboBox.stringValue.characters.count > 0 else { return false }
if (commandSelector == #selector(insertBacktab(_:))) {
switch comboBox {
case companyComboBox:
filteredCompanies = filterCompanies(by: comboBox.stringValue)
case projectComboBox:
filteredProjects = filterProjects(by: comboBox.stringValue)
default:
break
}
} else if commandSelector == #selector(deleteBackward(_:)) {
//Need to filter by substring of comboBox.stringValue because before deleteBackward comboBox has old text value.
let selectedTextRange = textView.selectedRanges.first as! NSRange
let endIndex = comboBox.stringValue.index(comboBox.stringValue.startIndex,
offsetBy: selectedTextRange.length > 0 ? selectedTextRange.location : selectedTextRange.location - 1)
let filterValue = comboBox.stringValue.substring(with: comboBox.stringValue.startIndex..<endIndex)
switch comboBox {
case companyComboBox:
filteredCompanies = filterCompanies(by: filterValue)
case projectComboBox:
filteredProjects = filterProjects(by: filterValue)
default:
break
}
} else if commandSelector == #selector(insertNewline(_:)) || commandSelector == #selector(insertTab(_:)) {
switch comboBox {
case companyComboBox:
let company = bestMatchedCompany(for: comboBox.stringValue)
if comboBox.stringValue == company?.name {
selectedCompany = company
}
case projectComboBox:
let project = bestMatchedProject(for: comboBox.stringValue)
if comboBox.stringValue == project?.name {
selectedProject = project
}
default:
break
}
}
return false
}
Do you have an idea how to fix it?
Thanks!

Swift equivalent to Objective-C FourCharCode single quote literals (e.g. 'TEXT')

I am trying replicate some Objective C cocoa in Swift. All is good until I come across the following:
// Set a new type and creator:
unsigned long type = 'TEXT';
unsigned long creator = 'pdos';
How can I create Int64s (or the correct Swift equivalent) from single quote character literals like this?
Types:
public typealias AEKeyword = FourCharCode
public typealias OSType = FourCharCode
public typealias FourCharCode = UInt32
I'm using this in my Cocoa Scripting apps, it considers characters > 0x80 correctly
func OSTypeFrom(string : String) -> UInt {
var result : UInt = 0
if let data = string.dataUsingEncoding(NSMacOSRomanStringEncoding) {
let bytes = UnsafePointer<UInt8>(data.bytes)
for i in 0..<data.length {
result = result << 8 + UInt(bytes[i])
}
}
return result
}
Edit:
Alternatively
func fourCharCodeFrom(string : String) -> FourCharCode
{
assert(string.count == 4, "String length must be 4")
var result : FourCharCode = 0
for char in string.utf16 {
result = (result << 8) + FourCharCode(char)
}
return result
}
or still swiftier
func fourCharCode(from string : String) -> FourCharCode
{
return string.utf16.reduce(0, {$0 << 8 + FourCharCode($1)})
}
I found the following typealiases from the Swift API:
typealias FourCharCode = UInt32
typealias OSType = FourCharCode
And the following functions:
func NSFileTypeForHFSTypeCode(hfsFileTypeCode: OSType) -> String!
func NSHFSTypeCodeFromFileType(fileTypeString: String!) -> OSType
This should allow me to create the equivalent code:
let type : UInt32 = UInt32(NSHFSTypeCodeFromFileType("TEXT"))
let creator : UInt32 = UInt32(NSHFSTypeCodeFromFileType("pdos"))
But those 4-character strings doesn't work and return 0.
If you wrap each string in ' single quotes ' and call the same functions, you will get the correct return values:
let type : UInt32 = UInt32(NSHFSTypeCodeFromFileType("'TEXT'"))
let creator : UInt32 = UInt32(NSHFSTypeCodeFromFileType("'pdos'"))
Adopt the ExpressibleByStringLiteral protocol to use four-character string literals directly:
extension FourCharCode: ExpressibleByStringLiteral {
public init(stringLiteral value: StringLiteralType) {
if let data = value.data(using: .macOSRoman), data.count == 4 {
self = data.reduce(0, {$0 << 8 + Self($1)})
} else {
self = 0
}
}
}
Now you can just pass a string literal as the FourCharCode / OSType / UInt32 parameter:
let record = NSAppleEventDescriptor.record()
record.setDescriptor(NSAppleEventDescriptor(boolean: true), forKeyword: "test")
In Swift 4 or later, I use this code - if the string is not 4 characters in size, it will return an OSType(0):
extension String {
public func osType() -> OSType {
var result:UInt = 0
if let data = self.data(using: .macOSRoman), data.count == 4
{
data.withUnsafeBytes { (ptr:UnsafePointer<UInt8>) in
for i in 0..<data.count {
result = result << 8 + UInt(ptr[i])
}
}
}
return OSType(result)
}
}
let type = "APPL".osType() // 1095782476
// check if this is OK in a playground
let hexStr = String(format: "0x%lx", type) // 0x4150504c -> "APPL" in ASCII
Swift 5 Update:
extension String {
func osType() -> OSType {
return OSType(
data(using: .macOSRoman)?
.withUnsafeBytes {
$0.reduce(into: UInt(0)) { $0 = $0 << 8 + UInt($1) }
} ?? 0
)
}
}
Here's a simple function
func mbcc(foo: String) -> Int
{
let chars = foo.utf8
var result: Int = 0
for aChar in chars
{
result = result << 8 + Int(aChar)
}
return result
}
let a = mbcc("TEXT")
print(String(format: "0x%lx", a)) // Prints 0x54455854
It will work for strings that will fit in an Int. Once they get longer it starts losing digits from the top.
If you use
result = result * 256 + Int(aChar)
you should get a crash when the string gets too big instead.
Using NSHFSTypeCodeFromFileType does work, but only for 4-character strings wrapped with single quotes, aka 6-character strings. It returns 0 for unquoted 4-character strings.
So wrap your 4-character string in ' ' before passing it to the function:
extension FourCharCode: ExpressibleByStringLiteral {
public init(stringLiteral value: StringLiteralType) {
switch (value.count, value.first, value.last) {
case (6, "'", "'"):
self = NSHFSTypeCodeFromFileType(value)
case (4, _, _):
self = NSHFSTypeCodeFromFileType("'\(value)'")
default:
self = 0
}
}
}
Using the above extension, you can use 4-character or single-quoted 6-character string literals:
let record = NSAppleEventDescriptor.record()
record.setDescriptor(NSAppleEventDescriptor(boolean: true), forKeyword: "4444")
record.setDescriptor(NSAppleEventDescriptor(boolean: true), forKeyword: "'6666'")
It would be even better to limit the string literal to 4-character strings at compile time. That does not seem to currently be possible, but is being discussed for Swift here:
Allow for Compile-Time Checked Intervals for Parameters Expecting Literal Values

Why does shouldChangeTextInRange get called multiple times using predictive input?

The predictive-input of iOS8 calls the following delegate method of UITextView multiple times resulting in the selected word being inserted multiple times into the view.
This code works for typing single letters and copy/paste but not when using the predictive-input bar; why not?
- (BOOL) textView:(UITextView*)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString*)text
{
textView.text = [textView.text stringByReplacingCharactersInRange:range withString:text];
return false;
}
With this code; if I enter an empty UITextView and tap on "The" in the predictive text (autocomplete) view it inserts "The The" into the view by way of making three calls on this method. The parameters passed in for each call are:
range : {0,0} text : #"The"
range : {0,0} text : #"The"
range : {3,0} text : #" "
The space I can understand; but why insert "The" twice?
I got this same issue. It appears that with predictive text, setting textView.text in that delegate method triggers an immediate call to that delegate method again (this only happens with predictive text as far as I know).
I fixed it by just surrounding my textView changes with a guard:
private var hack_shouldIgnorePredictiveInput = false
func textView(textView: UITextView!, shouldChangeTextInRange range: NSRange, replacementText text: String!) -> Bool {
if hack_shouldIgnorePredictiveInput {
hack_shouldIgnorePredictiveInput = false
return false
}
hack_shouldIgnorePredictiveInput = true
textView.text = "" // Modify text however you need. This will cause shouldChangeTextInRange to be called again, but it will be ignored thanks to hack_shouldIgnorePredictiveInput
hack_shouldIgnorePredictiveInput = false
return false
}
I modified the accepted answer from Richard Venable, because, as JLust noted in the comment, that 3rd call with the space was throwing me off.
I added
private var predictiveTextWatcher = 0
And
if predictiveTextWatcher == 1 {
predictiveTextWatcher = 0
return false
}
if hack_shouldIgnorePredictiveInput {
predictiveTextWatcher += 1
hack_shouldIgnorePredictiveInput = false
return false
}
It's all pretty hacky, but better than nothing.
Best,
Not an answer, but a safer workaround:
class TextViewTextChangeChecker {
private var timestamp: TimeInterval = 0
private var lastRange: NSRange = NSRange(location: -1, length: 0)
private var lastText: String = ""
func shouldChange(text:String,in range: NSRange) -> Bool {
let SOME_SHORT_TIME = 0.1
let newStamp = Date().timeIntervalSince1970
let same = lastText == text && range == lastRange && newStamp - timestamp < SOME_SHORT_TIME
timestamp = newStamp
lastRange = range
lastText = text
return !same
}
}
still this didn't helped me because changing the textView from the shouldChangeTextInRange function changed the autocapitalizationType to .word (only by behaviour, not the field itself).