How do I display the value from a custom range slider to the user as the user drags the knob?
Context
I am new to iOS dev, so please forgive me if I am missing something obvious. I am creating a custom range slider, and now I am at the point where I would like to display the value from the slider to the user in the UI as the user drags the knob.
For background, I have completed this tutorial and am now attempting to take the values printed in the console and convert them into a readable string to display to the user. The values are printed via this function:
func rangeSliderValueChanged(rangeSlider: RangeSlider) {
print("Range slider value changed: (\(rangeSlider.lowerValue) \(rangeSlider.upperValue))")
}
Which is called in viewDidLoad in the ViewController:
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(rangeSlider)
rangeSlider.addTarget(self, action: "rangeSliderValueChanged:", forControlEvents: .ValueChanged)
}
ValueChanged is in an override of continueTrackingWithTouch in RangeSlider.swift:
override func continueTrackingWithTouch(touch: UITouch,
withEvent event: UIEvent?) -> Bool {
// .. other stuff
self.sendActionsForControlEvents(UIControlEvents.ValueChanged)
return true
}
My Attempt
At this point I figured the way to solve this was to add a text field in the view, then update the text field with the value from the slider.
I created a text field in viewDidLoad:
var lowNumber: UITextField = UITextField(frame: CGRect(x: 0, y: 0, width: 200.00, height: 40.00))
override func viewDidLoad() {
lowNumber.backgroundColor = UIColor.redColor()
lowNumber.text = "some string"
lowNumber.borderStyle = UITextBorderStyle.Line
self.view.addSubview(lowNumber)
// .. more stuff
}
Then I tried to change the text field string in the rangeSliderValueChanged function:
func rangeSliderValueChanged(rangeSlider: RangeSlider) {
lowNumber.text = rangeSlider.lowerValue
}
Error
At this point I encountered an error which states:
Cannot assign a value of type 'Double' to a value of type 'String?'
Double is used in the RangeSlider class such as:
class RangeSlider: UIControl {
// .. stuff
var lowerValue: Double = 0.2 {
didSet {
updateLayerFrames()
}
}
// .. more stuff
}
This is where I am stuck, and the googling I have done on converting a double to a string has left me wondering how I could apply those solutions to my case. More fundamentally, I am wondering if I am approaching the problem in the right fashion.
Question
How do I solve the problem of displaying the value from the slider to the user? Do I need to use a different type of layer than UITextField? Have I approached the problem correctly, or would you recommend a different approach?
You need to convert the number to String. For example:
lowNumber.text = "\(rangeSlider.lowerValue)"
Related
I have created a subclass of NSView and assigned that to the main ViewController's view.
I have two NSTableViews on that view. These subviews have NSTextFields.
I start editing one of these text fields and press Cmd C.
performKeyEquivalent is triggered.
How do I know which text field triggered it?
I haved added this line
override func performKeyEquivalent(with event: NSEvent) -> Bool {
super.performKeyEquivalent(with:event)
let firstResponder = self.window?.firstResponder
but as expected it does not helper.
macOS tells me that firstResponder is a NSTextView, but I have no text view on the project.
tag does not helper either. I have the cells of my tableViews with tags equal to 100, 200, and 300 but
let tag = (firstResponder! as! NSTextView).tag
gives me -1 (?)
any ideas?
NSTextField is a delegate of own editor, which is NSTextView, so here how it should go
override func performKeyEquivalent(with event: NSEvent) -> Bool {
if let editor = self.window?.firstResponder as? NSTextView,
let textField = editor.delegate as? NSTextField {
print("Identified by: \(textField.tag)")
}
return super.performKeyEquivalent(with:event)
}
I'm trying to add accessibility to a simple UISlider. I read the Apples adjustable document and saw that I need to implement two functions from the UIAccessibilityAction protocol; accessibilityIncrement() and accessibilityDecrement().
The problem I'm having is that even if I set the slider to be an accessibility element in viewDidLoad and setting slider.accessibilityTraits = .adjustable, the two override functions aren't called even if I change the values.
I also tried to set slider.accessibilityLabel = "test", but it's still not reading the label. Only how far the slider has come. For instance "80%".
Any idea on how I can make this work? I also read these two posts on stackOverflow, but none of them worked for me. accessibilityIncrement / Decrement not called and Accessibility accessibilityDecrement() not getting called
I can also mention that I also tried setting breakpoints at the accessibilityIncrement() and accessibilityDecrement(), but nothing happened.
My code
override func viewDidLoad() {
super.viewDidLoad()
slider.isAccessibilityElement = true
slider.accessibilityLabel = "test"
slider.accessibilityTraits = .adjustable
}
override func accessibilityIncrement() {
slider.accessibilityValue = textField.text!
}
override func accessibilityDecrement() {
slider.accessibilityValue = textField.text!
}
#IBAction func sliderValueChanged(_ sender: UISlider) {
guard let unit = question?.unit else { return }
let currentValue = Int(sender.value + 0.5)
textField.text = "\(currentValue) \(unit)"
slider.accessibilityLabel = textField.text!
}
You implement the accessibilityIncrement() and accessibilityDecrement() methods in your view controller but they should belong to the created slider whose trait should be .adjustable.
I suggest you take a look at this accessibility site where a complete example about adjustable values with code snippets and illustrations is provided for both ObjC and Swift.
Following this example will allow to call the accessibilityIncrement() and accessibilityDecrement() methods with your slider.
everyone knows that when you drag outside a button it don't cancel the highlight state right away by UIButton's default. UIControlEventTouchDragExit triggers when 70 pixels away. I want that distance to be 0. So after searching the solution of it, I tried to create a subclass like this:
import UIKit
class UINewButton: UIButton {
override func continueTrackingWithTouch(touch: UITouch, withEvent event: UIEvent?) -> Bool {
print("here")
let touchOutside = !CGRectContainsPoint(self.bounds, touch.locationInView(self))
if touchOutside {
let previousTochInside = CGRectContainsPoint(self.bounds, touch.previousLocationInView(self))
if previousTochInside {
print("Sending UIControlEventTouchDragExit")
self.sendActionsForControlEvents(.TouchDragExit)
self.highlighted = false
self.selected = false
}else{
print("Sending UIControlEventTouchDragOutside")
self.sendActionsForControlEvents(.TouchDragOutside)
}
}else{
let previousTouchOutside = !CGRectContainsPoint(self.bounds, touch.previousLocationInView(self))
if previousTouchOutside{
print("Sending UIControlEventTouchDragEnter")
self.sendActionsForControlEvents(.TouchDragEnter)
}else{
print("Sending UIControlEventTouchDragInside")
self.sendActionsForControlEvents(.TouchUpInside)
}
}
return super.continueTrackingWithTouch(touch, withEvent: event)
}
}
and create a button like this in a UIViewController
#IBOutlet var confirmButton: UINewButton!
I assumed when a UIButton being touched and dragged. It would call the function in this sequence:
beginTrackingWithTouch(when touched) -> continueTrackingWithTouch(when dragged) -> endTrackingWithTouch(when left)
But here is the weird part. Even though I override the function continueTrackingWithTouch, it still not been called. Cause the console window didn't show "here" where I put there in it. And the result remain the default distance 70. how come is that?
I tried to call the three functions mentioned above and return true if it needs one.
What did I missed?
After reading this article: UIControlEventTouchDragExit triggers when 100 pixels away from UIButton
Still not helping :( (plus it written in objective-C...)
Isn't the distance of 70px a property of the function so I can just changed?(How can I see the original function by the way? There is no detail in Apple Developer Documentation...)
Should I use button.addtarget in the UIViewController? But it seems like another way to do it.
Here is another question:
If I want to cancel the highlight state when dragged outside the button, is this right?
self.highlighted = false
self.selected = false
I don't know which one is the right one so I used it all.
please help! Just a newbie in swift but I have been stuck in this problem for 3 days. QQ
In Swift 3 the function signature has changed. It's now:
func continueTracking(_ touch: UITouch, with event: UIEvent?) -> Bool
API Reference
I'm working on Android TV Remote Controller - iOS version
I need to detect cursor change event in UITextField and send this event to Android TV.
I can't find any Delegate or notification will send UITextfield cursor change event.
Is there any way to get this event?
Many thanks.
As far as I know, you can KVO or subclass.
Since #NSLeader gave the answer for KVO, I'll explain the latter.
Here is a subclass example:
class MyUITextFieldThatEmitsCursorChangeEvents: UITextField
{
//Override this, but don't prevent change to its default behavior by
//calling the super getter and setter.
override var selectedTextRange: UITextRange? {
get {return super.selectedTextRange}
set {
self.emitNewlySetCursor(event: newValue) //<- Intercept the value
super.selectedTextRange = newValue //Maintain normal behavior
}
}
//I'm going to use a closure to pass the cursor position out,
//but you can use a protocol, NotificationCenter, or whatever floats your
//boat.
weak var cursorPosnDidChangeEvent: ((Int) -> ())?
//I'm going to abstract the logic here to keep the previous code slim.
private func emitNewlySetCursor(event range: UITextRange?)
{
//Now you have access to the start and end in range.
//If .start and .end are different, then it means text is highlighted.
//If you only care about the position where text is about to be
//entered, then only worry about .start.
//This is an example to calculate the cursor position.
if let rawRangeComponent = range?.start
{
let cursorPosition = offset(from: beginningOfDocument,
to: rawRangeComponent)
//Emit the value to whoever cares about it
self.cursorPosnDidChangeEvent?(cursorPosition)
}
}
}
Then, for example, if we're in a UIViewController:
override func viewDidLoad()
{
super.viewDidLoad()
let tf = MyUITextFieldThatEmitsCursorChangeEvents(frame: .zero)
self.view.addSubview(tf)
tf.cursorPosnDidChangeEvent = { newCursorPosn in
print(newCursorPosn) //( ͡° ͜ʖ ͡°)
}
}
Observe selectedTextRange property.
Example on RxSwift:
textField.rx.observeWeakly(UITextRange.self, "selectedTextRange")
.observeOn(MainScheduler.asyncInstance)
.skip(1)
.bind { (newTextRange) in
print(newTextRange)
}
.disposed(by: disposeBag)
I'm trying to create a subclass of SKLabelNode for a button in SpriteKit. I tried to create a button using SKLabelNode as a parent, so I can use everything I know about labels while creating my buttons (font, text size, text color, position, etc).
I've looked into Swift Spritekit Adding Button Programaticly and I'm using the basis of what that is saying, but rather I'm making a subclass instead of a variable, and I'm creating the button using a label's code. The subclass has the added function that will allow it to be tapped and trigger an action.
class StartScene: SKScene {
class myButton: SKLabelNode {
override func touchesEnded(touches: Set<NSObject>, withEvent event: UIEvent) {
for touch: AnyObject in touches {
let location = touch.locationInNode(self)
if myButton.containsPoint(location) {
// Action code here
}
}
}
}
override func didMoveToView(view: SKView) {
let startGameBtn = myButton(fontNamed: "Copperplate-Light")
startGameBtn.text = "Start Game"
startGameBtn.fontColor = SKColor.blackColor()
startGameBtn.fontSize = 42
startGameBtn.position = CGPointMake(frame.size.width/2, frame.size.height * 0.5)
addChild(startGameBtn)
}
}
My error is in: if myButton.containsPoint(location)
The error reads: Cannot invoke 'containsPoint' with an argument list of type '(CGPoint)'
I know it has to do something with my subclass, but I have no idea what it is specifically.
I also tried putting parentheses around myButton.containsPoint(location) like so:
if (myButton.containsPoint(location))
But then the error reads: 'CGPoint' is not convertible to 'SKNode'
Thanks
I have an alternative solution that would work the same way.
Change the following code
if myButton.containsPoint(location) {
To this and it should compile and have the same functionality
if self.position == location {