Swift Programming: Evaluate value in a string [duplicate] - cocoa

This question already has answers here:
Swift - Resolving a math operation in a string
(4 answers)
Closed 8 years ago.
My question is on SWIFT/COCOA programming. I want to evaluate the content inside a string. For example, I have
var1 = "2 + 3"
Is there any inbuilt function in SWIFT/COCOA that can evaluate content in var1 and return 5. I tried what I could understand from string interpolation but that is not helping
var myNum = "45/30"
var myNum2 = "sin(30)"
var myNum3 = "\(Float(myNum) * Float(myNum2))";
error: could not find member 'convertFromStringInterpolationSegment'
Also, got another question.. How do I detect Return key pressed in SWIFT? Any trigger similar to "func applicationWillTerminate(aNotification: NSNotification) "

For your second question, assuming you're using a textField, you can use the delegate method:
- (BOOL)textFieldShouldReturn:(UITextField *)textField
This method is executed when the return key is pressed. If don't want to hide the keyboard, you need to return NO from this function at the end.
If you're using a textView, you can refer to this thread. You implement the textView:shouldChangeTextInRange:replacementText: method of UITextViewDelegate and in that check if the replacement text is \n.

Related

Subclassing NSTextStorage breaks list editing

I have a basic Mac app with a standard NSTextView. I'm trying to implement and use a subclass of NSTextStorage, but even a very basic implementation breaks list editing behavior:
I add a bulleted list with two items
I copy & paste that list further down into the document
Pressing Enter in the pasted list breaks formatting for the last list item.
Here's a quick video:
Two issues:
The bullet points of the pasted list use a smaller font size
Pressing Enter after the second list item breaks the third item
This works fine when I don't replace the text storage.
Here's my code:
ViewController.swift
#IBOutlet var textView:NSTextView!
override func viewDidLoad() {
[...]
textView.layoutManager?.replaceTextStorage(TestTextStorage())
}
TestTextStorage.swift
class TestTextStorage: NSTextStorage {
let backingStore = NSMutableAttributedString()
override var string: String {
return backingStore.string
}
override func attributes(at location: Int, effectiveRange range: NSRangePointer?) -> [NSAttributedString.Key:Any] {
return backingStore.attributes(at: location, effectiveRange: range)
}
override func replaceCharacters(in range: NSRange, with str: String) {
beginEditing()
backingStore.replaceCharacters(in: range, with:str)
edited(.editedCharacters, range: range,
changeInLength: (str as NSString).length - range.length)
endEditing()
}
override func setAttributes(_ attrs: [NSAttributedString.Key: Any]?, range: NSRange) {
beginEditing()
backingStore.setAttributes(attrs, range: range)
edited(.editedAttributes, range: range, changeInLength: 0)
endEditing()
}
}
You have found a bug in Swift (and maybe not just in the Swift libraries, maybe in something a bit more fundamental).
So what is going on?
You will be able to see this a bit better if you create a numbered list rather than a bulleted one. You don't need to do any copy and paste, just:
Type "aa", hit return, type "bb"
Do select all and format as a numbered list
Place cursor at the end of "aa" and hit return...
What you see is a mess, but you can see the two original numbers are still there and the new middle list item you started by hitting return is where all the mess is.
When you hit return the text system has to renumber the list items, as you've just inserted a new item. First, it turns out that it performs this "renumbering" even if it is a bulleted list, which is why you see the mess in your example. Second, it does this renumbering by starting at the beginning of the list and renumbering every list item and inserting a new number for the just created item.
The Process in Objective-C
If you translate your Swift code into the equivalent Objective-C and trace through you can watch the process. Starting with:
1) aa
2) bb
the internal buffer is something like:
\t1)\taa\n\t2)\tbb
first the return is inserted:
\t1)\taa\n\n\t2)\tbb
and then an internal routine _reformListAtIndex: is called and it starts "renumbering". First it replaces \t1)\t with \t1) - the number hasn't changed. Then it inserts \t2)\t between the two new lines, as at this point we have:
\t1)\taa\n\t2)\t\n\t2)\tbb
and then it replaces the original \t2)\t with \t3)\t giving:
\t1)\taa\n\t2)\t\n\t3)\tbb
and it's job is done. All these replacements are based on specifying the range of characters to replace, the insertion uses a range of length 0, and go through:
- (void)replaceCharactersInRange:(NSRange)range withString:(NSString * _Nonnull)str
which in Swift is replaced by:
override func replaceCharacters(in range: NSRange, with str: String)
The Process in Swift
In Objective-C strings have reference semantics, change a string and all parts of the code with a reference to the string see the change. In Swift strings have value semantics and strings are copied (notionally at least) on being passed to functions etc.; if the copy is changed in called function the caller won't see that change in its copy.
The text system was written in (or for) Objective-C and it is reasonable to assume it may take advantage of the reference semantics. When you replace part of its code with Swift the Swift code has to do a little dance, during the list renumbering stage when replaceCharacters() gets called the stack will look something like:
#0 0x0000000100003470 in SwiftTextStorage.replaceCharacters(in:with:)
#1 0x0000000100003a00 in #objc SwiftTextStorage.replaceCharacters(in:with:) ()
#2 0x00007fff2cdc30c7 in -[NSMutableAttributedString replaceCharactersInRange:withAttributedString:] ()
#3 0x00007fff28998c41 in -[NSTextView(NSKeyBindingCommands) _reformListAtIndex:] ()
#4 0x00007fff284fd555 in -[NSTextView(NSKeyBindingCommands) insertNewline:] ()
Frame #4 is the Objective-C code called when return was hit, after inserting the newline it calls the internal routine _reformListAtIndex:, frame #3, to do the renumbering. This calls another Objective-C routine in frame #2, which in turn calls, frame #1, what it thinks is the Objective-C method replaceCharactersInRange:withString:, but is in fact a Swift replacement. This replacement does a little dance converting Objective-C reference semantic strings to Swift value semantics strings and then calls, frame #0, the Swift replaceCharacters().
Dancing is Hard
If you trace through your Swift code just as you did the Objective-C translation when the renumbering gets to the stage of changing the original \t2)\t to \t3)\t you will see a misstep, the range given for the original \t2)\t is what is was before the new \t2)\t was inserted in the previous step (i.e. it is off by 4 positions)... and you end up with a mess and a few more dance steps later the code crashes with a string referring error as the indices are all wrong.
This suggests that the Objective-C code is relying on reference semantics, and the choreographer of the Swift dance converting reference to value and back to reference semantics has failed to meet the Objective-C code's expectations: so either when the Objective-C code, or some Swift code which has replaced it, calculates the range of the original \t2)\t it is doing so on string which hasn't been altered by the previous insertion of the new \t2)\t.
Confused? Well dancing can make you dizzy at times ;-)
Fix?
Code your subclass of NSTextStorage in Objective-C and go to bugreport.apple.com and report the bug.
HTH (more than it makes you dizzy)

Set Limit for TextField characters in RxSwift [duplicate]

This question already has an answer here:
RxSwift/RxCocoa: prevent UITextField from having more than ... characters
(1 answer)
Closed 4 years ago.
I want to set a limit for characters in a textFiled using RxSwift,
may be i can benefit from Scan operator but i don't know how to use it in this case ,
I want my resulting code to behave as same as implementing it in shouldChangeCharacterInRage method:
func textField(_ textField: UITextField,
shouldChangeCharactersIn range: NSRange,
replacementString string: String)
-> Bool {
if textField == baseTextFiled{
let enteredString = (textField.text! as NSString).replacingCharacters(in: range, with: string)
if enteredString.count > limit{ // limit defined previously
return false
}
}
return true
}
Any help?
ReactiveX pushes data, so isn't really compatible with that particular delegate method. That said, you can still do it because .rx.text is a control property...
baseTextField.rx.text.orEmpty // pushes text
.map { String($0.prefix(limit)) } // pushes first X characters in text
.distinctUntilChanged() // only pushes if text is different than previously
.bind(to: baseTextField.rx.text) // pushes text into text field
.disposed(by: bag)

How to call a function every 3 seconds using Swift Spritekit [duplicate]

This question already has answers here:
Modify property on SKNode while moving
(2 answers)
Closed 5 years ago.
In normal swift, you can use something like this:
Foundation.Timer.scheduledTimer(timeInterval: 3.0, target: self, selector: #selector(GameViewController.hideFunc), userInfo: nil, repeats: true)
My question is what can you replace this with to work in a Sprite kit game?
Well, you could build it pretty easily using SKActions:
let pauser = SKAction.wait(forDuration: 3.0)
let trigger = SKAction.perform(#selector(GameViewController.hideFunc, onTarget: self)
let pauseThenTrigger = SKAction.sequence([ pauser, trigger ])
let repeatForever = SKAction.repeatForever(pauseThenTrigger)
node.run( repeatForever )
This approach would have the virtue of letting you reuse this action for any nodes that would need to use it, and you can easily control the action by removing or adding the node from the scene, calling removeAllActions() on it, etc.

Insert text at cursor position on button press in Swift

My last program is written about 20 years ago, therefore my programming skills are moderate or bellow. Now I decided to try Swift and have questions.
I have search field where I would like to allow a user to enter a text using buttons.
The problem: I could not find the way to insert the letter/symbol to the search field at the cursor position.
lets say:
let buttonLetter_a = "a"
#IBOutlet weak var myTextField: NSSearchField!
#IBAction func buttonLetter_a(sender: NSButton) {
// this adds the letter a at the end of the string
myTextField.stringValue += buttonLetter_a
}
How to force the button i.e. "buttonLetter_a" to insert the "a" to the string entered to "myTextField" at the cursor position?
Please note that I need Swift help for OS X.

Can't setup bindings in Swift Storyboard?

I've got some code that runs a fairly complex algorithm. I want to put together a fairly simple UI that will allow me to monitor the values of the various variables in the algorithm in some graphical ways -- think of it like a dashboard of sorts.
So, for simplicity's sake, let's say I have an algorithm like what follows. It searches a vector of values for the two values that most closely sum to a target value:
import Foundation
class algorithm {
var numbers = [Double]()
let numberOfRandoms = 1000
dynamic var a: String
dynamic var b: String
init () {
// Load initial vector with some random numbers between 0 and 1
for _ in 1...numberOfRandoms {
numbers.append(Double(arc4random()) / Double(UINT32_MAX))
}
a = " "
b = " "
}
func findTheTwoNumbersThatAddUpTheClosestToTarget(target: Double) {
//Initializing this to a very large value
var currentBestSum = 1000.0
//Begin brute force search for the optimal solution
for i in 0...numbers.count-2 {
for j in i+1...numbers.count-1 {
//Check to see if the current candidate exceeds the best solution
if abs(numbers[i] + numbers[j] - target) < currentBestSum {
//If it does, store the new champion
a = String(i)
b = String(j)
//And reset the current top score to match
currentBestSum = abs(numbers[i] + numbers[j]-target)
}
}
}
}
}
Now, this is just a simple (and silly) example, but it suits these purposes. I basically want to create a simple UI that displays the important values in the process as it runs (dynamically).
In this example, let's say that I just want to display two labels that contain the index values of the two leaders as the algorithm executes.
I created the labels in the storyboard.
Then, I created IBOutlets in the ViewController (Actually, storyboards did it for me when I Ctrl-dragged):
class ViewController: NSViewController {
#IBOutlet weak var a: NSTextField!
#IBOutlet weak var b: NSTextField!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override var representedObject: AnyObject? {
didSet {
// Update the view, if already loaded.
}
}
}
Then, I ctrl-dragged the labels to the a and b in the algorithm class to create the bindings.
Finally, I create an class variable in the view controller and instantiate it in the viewDidLoad method. This doesn't seem like the right thing to do -- maybe it is. Seems like you would want to keep separate your interface and data...
The labels do, in fact, show up -- but they never show any values of a and b. They just show the default text.
Not sure what I'm doing wrong.
Help!?
P.S., in response to Anthony Kong, I do recognize that I could manually synchronize all the view elements in the code, but I thought the whole point of using bindings was to avoid having to do this manual synchronization. I just can't figure out how to set it up.
Without commenting on your specific code I think I have experienced (and solved) the problem you describe. I was able to write an app that had two targets, one NIB-based and one Storyboard-based. As much as I was able I duplicated the code in each and shared the common data instance that I was trying to display in a TableView. The NIB-based app worked using the stock Cocoa Bindings that I set in IB. But the Storyboard-based app did not, the array controller did not see the data.
My solution was simply to add the binding for contentArray programmatically in viewDidLoad. The one line that fixed it for me is:
ac.bind("contentArray", toObject: cd, withKeyPath: "people", options: nil)
ac is the IBOutlet for the ArrayController in the Storyboard. cd is the class instance that contains the people array.
This is using XCode 6.2 (6C107a) which is Beta 3 I think.
This was the only binding that I had to set myself, the TableView to ArrayController (arrangedObjects) and TableViewCell to TableView (objectValue) didn't need any tweaking.
There are several problems with your code
1) In your code,
func applicationDidFinishLaunching(aNotification: NSNotification?) {
// Insert code here to initialize your application
var test = algorithm()
test.findTheTwoNumbersThatAddUpTheClosestToTarget(0.5)
}
The variable test goes out of scope when the function exits. Based on the wording of your question, you are expecting it to be a long running process. So this will not do what you want.
You should
a) Add test as class variable to the ViewController class
b) instantiate the variable in viewDidLoad method.
2) In your algorithm it does not actually provide any feedback to the labels. Maybe you think because the class has the ivar a and b so they are hooked to the IBOutlet by the same names. But of course it is not the case. And you do not need the keyword dynamic too.
What you should do is:
a) provide a method in the View Controller class to update the labels. It will serve as a callback function to be used by algorithm class to feedback the calculation result.
It may look like this:
func update_value_callback(vala: String, valb: String) {
a.text = vala; // updating the label here
b.text = valb;
}
b) make the algorithm class calls the callback function e.g.
func findTheTwoNumbersThatAddUpTheClosestToTarget(target: Double, viewController: ViewController) {
// do your stuff
...
// execute the callback
viewController.update_value_callback(a, b)
}

Resources