|
I thought this would be straightforward, but ... nope. I want to implement a control that synchronizes a Slider and a TextField. That is, if you move the Slider, the TextField updates, and if you enter a value in the text field, the slider updates. The value is a Float. You see this in Garage Band and Logic for the faders and such. I implemented this in standard Swift with AppKit, and now I want to do it in SwiftUI. Here's the code. For testing, the View here is instantiated in ContentView.swift in the basic template code. It's synchronizing in only one direction: enter a value into the TextField box and the Slider updates. But it doesn't work in the other direction, that is, moving the Slider thumb doesn't update the TextField. The debug print statements tell me that textValue property updates in the Slider's onEditingChanged: callback but the text field contents do not update. What am I missing? Thanks in advance.
|
|
Hi! Just use
|
|
Thanks for the tip! It's close ... but not quite there. And in a very subtle way. Here's what I see. When the TextField has focus, only pressing <Return> after typing in a value triggers the field's .onSubmit() method. I guess I need to read up on SubmitTriggers, as I think that the user pressing the <Tab> key should trigger that method. OK, that's fine for starters. And pressing <Return> updates the Slider thumb position as desired. The TextField retains focus. If you grab the Slider thumb with your mouse while the TextField has focus, the textValue property is updated in the .onChange() method. (Verified by adding a print() to that method.) But this does not update the TextField. Weird! If I <tab> from the TextField to the Slider thumb, to give the Slider focus, then dragging the thumb now updates the textValue property and what is displayed in the TextField. Also it doesn't matter if the .onChange() method is attached to the Slider or to the enclosing VStack: the behavior is the same. Getting close, I think! |
SPONSORED Join a FREE crash course for mid/senior iOS devs who want to achieve an expert level of technical and practical skills – it’s the fast track to being a complete senior developer! Hurry up because it'll be available only until February 9th. Sponsor Hacking with Swift and reach the world's largest Swift community! |
|
Maybe it makes more sense to use just one slider value for both textField and slider... like that...
|
|
That was close, but still ... it requires pressing <Return> to accept the value in the TextField and pass it along to the Slider. Pressing <Tab> -- which is how the faders work in Logic -- doesn't change the focus. But you gave me clues! As I'm new at SwiftUI, I didn't know about either the I added that This was helpful in my searching. The answer is to change the Thus we get the following. The only flaw here is that clicking the mouse on the Slider area or thumb doesn't give the focus to the slider, it remains with the TextField. That's next. But this works. Oh, yeah, there's also a bit of a race, in that when you enter a value in the TextField and press <Return> or <Tab>, the new I hope this helps someone else!
|
|
For closure: I figured it out. In Logic and Garage Band, hitting <Return> in the TextField accepts the value and updates the fader position. Hitting <Tab> does the same thing. However, the actions in SwiftUI are different. When the TextField has focus, the user expects that if <Return> is pressed, the value in the TextField box is accepted. This action is handled by If the user hits <Tab> while the TextField has focus, focus moves to the next control in the View, in this case the Slider. The value in the TextEdit box is not accepted, so the Slider position and value do not change. However, this behavior is not what I want. I want the value in the TextField to be the new Slider position. This requires using the In the other direction, when we move the Slider thumb or we click anywhere in the Slider we want the value in the TextField to update to the Slider value. There is an oddity with the Slider. If the TextEdit has focus and you click your mouse on the Slider (anywhere in it, including the thumb), the Slider does not get focus. And if it does not have focus, for whatever reason even though when the Slider position changes you copy the sliderValue to the textValue which should update the TextField display, that doesn't happen. The TextField display does not change. The trick, then, is to use a property to monitor/control the Slider's FocusState, and when a change is detected in the Slider position, we force the Slider to have focus. Then updating the TextField's value from the Slider I'm sure this code can be more Swifty, but it works as is and is a good start for bigger things.
|
|
@aspdigital: Thanks for sharing your useful solution! Just for fun, here's an alternative implementation of clamp() without if statements (from Paul's article on Swift 5.5 new features): https://www.hackingwithswift.com/swift/5.5/property-wrapper-function-parameters |
|
@bobstern: I knew there was a clamp function somewhere, but couldn't find it. Thanks for the link! |
|
I removed the "SOLVED" tag because this control has a very serious flaw. It's impossible for something outside to get control's value. I've tried various combinations of @Binding and a class for the data with @Published for the value and various, and it always gets caught with an error in the initializer. I added |
|
Actual closure: I sorted out how to get the control's values outside of the view using Xcode project is here. |
|
I suggest another way of linking a TextField and a Slider together without having an infinite loop between the two. For this, I use the onKeyPress down and up events, combined with the onChange event. In fact, I've noticed that when you type in the textField, the order of the events is as follows: 1) onKeyPress down 2) onChange 3) onKeyPress up Knowing this, when you type in the TextField, you can change the position of the slider while disabling slider feedback. In the same way, when you move the slider, you can update the Textfield without receiving feedback from the TextField. Here's an example:
Disabling and enabling the change is performed by the keyIsPressed variable, which is modified by the onKeyPress event. |
SPONSORED Join a FREE crash course for mid/senior iOS devs who want to achieve an expert level of technical and practical skills – it’s the fast track to being a complete senior developer! Hurry up because it'll be available only until February 9th.
Sponsor Hacking with Swift and reach the world's largest Swift community!
This topic has been closed due to inactivity, so you can't reply. Please create a new topic if you need to.
All interactions here are governed by our code of conduct.
Link copied to your pasteboard.