Changing height of NSSearchField in SwiftUI - swift

I am working with SwiftUI 1.0.
I have created a search bar for SwiftUI as the following:
import SwiftUI
struct Searchbar: NSViewRepresentable {
class Coordinator: NSObject, NSSearchFieldDelegate {
var parent: Searchbar
init(_ parent: Searchbar) {
self.parent = parent
}
func controlTextDidChange(_ notification: Notification) {
guard let searchField = notification.object as? NSSearchField else {
log.error("Unexpected control in update notification", source: .ui)
return
}
self.parent.search = searchField.stringValue
}
}
#Binding var search: String
func makeNSView(context: Context) -> NSSearchField {
let searchfield = NSSearchField(frame: .zero)
return searchfield
}
func changeSearchFieldItem(searchfield: NSSearchField, sender: AnyObject) -> NSSearchField {
//Based on the Menu item selection in the search field the placeholder string is set
(searchfield.cell as? NSSearchFieldCell)?.placeholderString = sender.title
return searchfield
}
func updateNSView(_ searchField: NSSearchField, context: Context) {
searchField.stringValue = search
searchField.delegate = context.coordinator
}
func makeCoordinator() -> Coordinator {
return Coordinator(self)
}
}
This is working fine so far when using it in my View:
Searchbar(search: $searchText)
I am wondering if the height of the NSSearchField can be changed to have a view similar to what is seen in the Maps.app:

Update: You can also set the controlSize to .large if you’re on Big Sur.
https://developer.apple.com/documentation/appkit/nscontrol/controlsize/large
You can add a height constraint:
func makeNSView(context: Context) -> NSSearchField {
let searchfield = NSSearchField(frame: .zero)
searchfield.translatesAutoresizingMaskIntoConstraints = false
searchfield.heightAnchor.constraint(greaterThanOrEqualToConstant: 40).isActive = true
return searchfield
}
…which works in macOS 11.1 Big Sur. Unfortunately the Focus ring does not adapt its height. You could hide it like this:
searchTextField.focusRingType = .none
… but that does not seem desirable in most situations.

Related

Why does a binding in UIViewRepresentables Coordinator have a constant read value

I have been writing a UIViewRepresentable and noticing some curios effects in regards to a binding I'm passing into the view.
When I read the bindings value in the coordinator through the saved UIViewRepresentable the value is always the value that it was initialized with. Trying to update the same binding however triggers an update in the surrounding UI.
This is code produces this behavior:
struct NativeTextView: UIViewRepresentable {
#Binding var text: String
func makeUIView(context: Context) -> UITextField {
let view = UITextField()
view.borderStyle = .roundedRect
view.addTarget(
context.coordinator,
action: #selector(Coordinator.updateText(sender:)),
for: .editingChanged
)
return view
}
func updateUIView(_ uiView: UITextField, context: Context) {
context.coordinator.updateUI(uiView)
}
func makeCoordinator() -> Coordinator {
Coordinator(_text)
}
class Coordinator: NSObject {
#Binding var text: String
init(_ text: Binding<String>){
_text = text
}
#objc func updateText(sender: UITextField){
text=sender.text!
}
func updateUI(_ uiView: UITextField) {
uiView.text = text
}
}
}
If I hover give my updateUI method a NativeTextView parameter, and use the .text field of it through the parameter, I read the correct value and the UI works correctly:
struct NativeTextView: UIViewRepresentable {
#Binding var text: String
func makeUIView(context: Context) -> UITextField {
let view = UITextField()
view.borderStyle = .roundedRect
view.addTarget(
context.coordinator,
action: #selector(Coordinator.updateText(sender:)),
for: .editingChanged
)
return view
}
func updateUIView(_ uiView: UITextField, context: Context) {
context.coordinator.updateUI(uiView, view: self)
}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
class Coordinator: NSObject {
var myView: NativeTextView
init(_ view: NativeTextView){
self.myView=view
}
#objc func updateText(sender: UITextField){
myView.text=sender.text!
}
func updateUI(_ uiView: UITextField, view: NativeTextView) {
uiView.text = view.text
}
}
}
It seems that the binding retains the ability to write to the outside #State variable but does not manage to access the current states value correctly. I'm guessing that this has something to do with the recreation of the NativeTextView view when SwiftUI notices an update of the #State, but I have not been able to find any documentation that would explain this behavior.
Does anyone know why this happens?
PS: for completeness this is my ContentViews body:
ZStack {
Color.red
VStack {
Text(test)
.padding()
.onTapGesture() {
test = "Bla"
}
NativeTextView(text: $test)
}
}

SwiftUI Show and Dismiss Textfield Keyboard

I try to show the text field keyboard as soon as the view appears and dismiss the keyboard when tap on the keyboard "return" key, the first part of the problem is solved by the code example bellow, But that make the keyboard "return" key doesn't work, Did any one can help to achieve my to show and dismiss the textfiled keyboard as I need.
import SwiftUI
struct MyTextField: UIViewRepresentable {
typealias UIViewType = UITextField
#Binding var becomeFirstResponder: Bool
func makeUIView(context: Context) -> UITextField {
let textField = UITextField()
return textField
}
func updateUIView(_ textField: UITextField, context: Context) {
if self.becomeFirstResponder {
DispatchQueue.main.async {
textField.becomeFirstResponder()
self.becomeFirstResponder = false
}
}
}
}
struct TextFieldFirstResponder: View {
#State private var becomeFirstResponder = false
var body: some View {
MyTextField(becomeFirstResponder: self.$becomeFirstResponder)
.onAppear {
self.becomeFirstResponder = true
}
}
}
Use textFieldShouldReturn delegate method. For this make Coordinator for UIViewRepresentable.
struct MyTextField: UIViewRepresentable {
typealias UIViewType = UITextField
#Binding var becomeFirstResponder: Bool
func makeUIView(context: Context) -> UITextField {
let textField = UITextField()
textField.delegate = context.coordinator
return textField
}
func updateUIView(_ textField: UITextField, context: Context) {
if self.becomeFirstResponder {
DispatchQueue.main.async {
textField.becomeFirstResponder()
self.becomeFirstResponder = false
}
}
}
func makeCoordinator() -> Coordinator {
Coordinator(parent: self)
}
class Coordinator: NSObject, UITextFieldDelegate {
var parent: MyTextField
init(parent: MyTextField) {
self.parent = parent
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
textField.resignFirstResponder()
}
}
}

Capturing all selection change events for an NSTextView, including those caused by mouse drags

I have a NSViewRepresentable that contains an NSScrollView with a NSTextView inside.
struct MultilineTextField: NSViewRepresentable {
typealias NSViewType = NSScrollView
private let scrollView = NSScrollView()
private let textView = NSTextView()
#Binding var text: String
#Binding var loc: NSRange
func makeNSView(context: Context) -> NSScrollView {
textView.string = text
textView.delegate = context.coordinator
scrollView.documentView = textView
return scrollView
}
func updateNSView(_ scrollView: NSScrollView, context: Context) {}
func makeCoordinator() -> Coordinator {
return Coordinator(self)
}
class Coordinator: NSObject, NSTextViewDelegate {
let textField: MultilineTextField
init(_ textField: MultilineTextField) {
self.textField = textField
}
func textDidChange(_ notification: Notification) {
textField.text = textField.textView.string
}
func textViewDidChangeSelection(_ notification: Notification) {
DispatchQueue.main.async {
self.textField.loc = self.textField.textView.selectedRange()
}
}
}
}
I'm trying to extract the selectedRange of the NSTextView to be displayed in a different view, eg:
Text(String(loc.location))
It works when the selectedRange is changed with arrow keys and on mouse down and mouse up, but it seems like textViewDidChangeSelection doesn't get called when in the middle of dragging the mouse over the text. As a result, the displayed loc.location value doesn't change while dragging until the mouse is lifted up.
As a workaround, I've tried subclassing NSTextView to override the mouseMoved method, but it seems like that doesn't get called either for some reason.
Just to verify that selectedRange actually gets updated on mouse drag, I tried continually updating loc (this is in the Coordinator class):
init(_ textField: MultilineTextField) {
self.textField = textField
super.init()
updateSelection()
}
func updateSelection() {
DispatchQueue.main.async {
self.textField.loc = self.textField.textView.selectedRange()
self.updateSelection()
}
}
This code does work, but also uses 100% CPU for no reason.
Is there a way to be notified when selectedRange changes in the case of mouse dragging?

View content doesn't appear in UIScrollView

I have this...
func makeUIView(context: Context) -> UIScrollView {
let control = UIScrollView()
control.addSubview(UIHostingController(rootView: preview).view)
return control
}
If I put my preview view outside of my custom scroll view then I can see it. But when I add it as a subview of a UIScrollView then I can't see anything.
Have I added it correctly?
Here is the complete code for the scroll view, which I got from here.
import SwiftUI
import Foundation
struct LegacyScrollView : UIViewRepresentable {
var preview: AnyView
init(preview: AnyView) {
self.preview = preview
}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
func makeUIView(context: Context) -> UIScrollView {
let control = UIScrollView()
control.addSubview(UIHostingController(rootView: preview).view)
return control
}
func updateUIView(_ uiView: UIScrollView, context: Context) {
}
class Coordinator: NSObject {
var control: LegacyScrollView
init(_ control: LegacyScrollView) {
self.control = control
}
#objc func handleRefreshControl(sender: UIRefreshControl) {
sender.endRefreshing()
}
}
}

swiftUI how to have search button on keyboard

In SwiftUI I have a simple search TextField where the user type something to be searched and a Button search.
I just want to add the option to have the button Search in the lower right corner of the Keyboard (I saw it in some application)
how can I do that?
iOS 15
You can change the return key for each textField with a simple modifier called: .submitLabel that takes the return key type and you should pass .search. Take a look at the following example:
Also, as you can see, you can have a callback to handle the return key press action just like the old textFieldShouldReturn function that is accessible by '.onSubmit' modifier.
If I understand correctly you want to change the UIReturnKeyType.
In that case you have to use UIKit since there isn't yet any option to change the type of return key in SwiftUI.
To do this, you have to make a custom TextField using UIIKit and then modify it the way you like.
Also keep in mind that the UIReturnKeyType enum is under discussion and may replace with a different implementation.
// MARK: Custom TextField
struct TextFieldTyped: UIViewRepresentable {
let keyboardType: UIKeyboardType
let returnVal: UIReturnKeyType
let tag: Int
#Binding var text: String
#Binding var isfocusAble: [Bool]
func makeUIView(context: Context) -> UITextField {
let textField = UITextField(frame: .zero)
textField.keyboardType = self.keyboardType
textField.returnKeyType = self.returnVal
textField.tag = self.tag
textField.delegate = context.coordinator
textField.autocorrectionType = .no
return textField
}
func updateUIView(_ uiView: UITextField, context: Context) {
if isfocusAble[tag] {
uiView.becomeFirstResponder()
} else {
uiView.resignFirstResponder()
}
}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
class Coordinator: NSObject, UITextFieldDelegate {
var parent: TextFieldTyped
init(_ textField: TextFieldTyped) {
self.parent = textField
}
func updatefocus(textfield: UITextField) {
textfield.becomeFirstResponder()
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
if parent.tag == 0 {
parent.isfocusAble = [false, true]
parent.text = textField.text ?? ""
} else if parent.tag == 1 {
parent.isfocusAble = [false, false]
parent.text = textField.text ?? ""
}
return true
}
}
}
And you can use it like this:
(Change the returnVal to .search in your case.)
struct CustomeKT: View {
#State var myTextForTX = ""
#State var focused: [Bool] = [false, true]
var body: some View {
TextFieldTyped(keyboardType: .default, returnVal: .search, tag: 0, text: self.$myTextForTX, isfocusAble: self.$focused)
}
}
Simple Use SearchTextField.swift
import SwiftUI
import UIKit
class UIKitTextField: UITextField, UITextFieldDelegate {
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)!
delegate = self
}
required override init(frame: CGRect) {
super.init(frame: frame)
delegate = self
self.setContentHuggingPriority(.defaultHigh, for: .vertical)
}
var action:(() -> Void)? = nil
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
self.action?()
if let nextField = textField.superview?.viewWithTag(textField.tag + 1) as? UITextField {
nextField.becomeFirstResponder()
} else {
textField.resignFirstResponder()
return true;
}
return false
}
}
struct SearchTextField : UIViewRepresentable {
#Binding var text: String
var action:() -> Void
func makeCoordinator() -> SearchTextField.Coordinator {
return Coordinator(value: self)
}
class Coordinator: NSObject,UITextFieldDelegate {
var parent:SearchTextField
init(value: SearchTextField) {
self.parent = value
}
#objc func textFieldEditingChanged(_ sender: UIKitTextField) {
self.parent.text = sender.text ?? ""
}
}
func makeUIView(context: Context) -> UIKitTextField {
let textfield = UIKitTextField(frame: .zero)
textfield.addTarget(context.coordinator, action: #selector(Coordinator.textFieldEditingChanged(_:)), for: .editingChanged)
textfield.text = self.text
textfield.placeholder = "search"
textfield.borderStyle = .none
textfield.returnKeyType = .search
textfield.action = self.action
return textfield
}
func updateUIView(_ uiView: UIKitTextField,
context: Context) {
uiView.text = self.text
}
}
Use :
SearchTextField(text: self.$searchKey) {
self.search(key: self.searchKey)
// or...
}