Skip to content

Commit

Permalink
feat: Add onEditingChanged, onTextChanged, and searchField APIs to Se…
Browse files Browse the repository at this point in the history
…archField component.
  • Loading branch information
jaywcjlove committed Oct 10, 2024
1 parent 1078caf commit dbb3548
Show file tree
Hide file tree
Showing 3 changed files with 201 additions and 57 deletions.
116 changes: 111 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ You can add MarkdownUI to an Xcode project by adding it as a package dependency.

1. From the File menu, select Add Packages…
2. Enter https://github.com/jaywcjlove/swiftui-searchfield the Search or Enter Package URL search field
3. Link `Markdown` to your application target
3. Link `SearchField` to your application target

Or add the following to `Package.swift`:

Expand All @@ -29,20 +29,126 @@ Or [add the package in Xcode](https://developer.apple.com/documentation/xcode/ad
## Usage

```swift
import SearchField
public init(searchText: Binding<String>,
placeholder: String? = nil,
onEditingChanged: ((Bool) -> Void)? = nil,
onTextChanged: ((String) -> Void)? = nil,
searchField: ((NSSearchField) -> Void)? = nil)
```

Parameters

- `searchText`: A binding to the string for the search text.
- `placeholder`: An optional placeholder string that is displayed when the input field is empty.
- `onEditingChanged`: A closure that is called when the editing state changes, returning a boolean value that indicates whether editing has started or ended.
- `onTextChanged`: A closure that is called when the text changes, returning the current search text.
- `searchField`: A closure that allows access to the NSSearchField instance for custom operations.


```swift
import SwiftUI

struct ContentView: View {
@State private var searchText = ""
var body: some View {
SearchField(searchText, textFieldChanged: { value in
print("value\(value)")
searchText = value
SearchField(searchText: $searchText, placeholder: "Search...", searchField: { searchField in
print(type(of: searchField)) // -> NSSearchField
})
}
}
```

In this example, SearchField is initialized and bound to the @State variable searchText, and the text entered by the user will be printed to the console.

### Example: Handling Editing State

```swift
import SwiftUI
import SearchField

struct ContentView: View {
@State private var searchText = ""
@State private var isEditing = false

var body: some View {
SearchField(
searchText: $searchText,
placeholder: "Search...",
onEditingChanged: { editing in
isEditing = editing
print("Editing started: \(editing)")
},
onTextChanged: { text in
print("Search text changed to: \(text)")
}
) { searchField in
print(type(of: searchField)) // -> NSSearchField
}
}
}
```

In this example, the onEditingChanged closure is used to handle changes in the editing state.

### Example: Accessing the NSSearchField Instance

```swift
import SwiftUI
import SearchField

struct ContentView: View {
@State private var searchText = ""
@State private var searchField: NSSearchField?

var body: some View {
SearchField(
searchText: $searchText,
placeholder: "Search...",
searchField: { field in
// 自定义 NSSearchField 的属性
field.isBordered = true
field.isBezeled = true
}
)
}
}
```

### Upgrade v1 to v2

```diff
import SearchField

struct ContentView: View {
@State private var searchText = ""
var body: some View {
- SearchField(searchText, textFieldChanged: { value in
+ SearchField(searchText: $searchText, placeholder: "Search...") { text in
- print("value\(value)")
- searchText = value
+ print("Search text changed to: \(text)")
+ })
- }
Text(searchText)
}
}
```

```swift
import SwiftUI
import SearchField

struct ContentView: View {
@State private var searchText = ""

var body: some View {
SearchField(searchText: $searchText, placeholder: "Search...", searchField: { text in
print("Search text changed to: \(text)")
})
}
}
```

## License

Licensed under the MIT License.
37 changes: 34 additions & 3 deletions SearchFieldExample/SearchFieldExample/ContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,48 @@ import SearchField

struct ContentView: View {
@State private var searchText = ""
@State private var isEditing = false

var body: some View {
VStack {
Image(systemName: "globe")
.imageScale(.large)
.foregroundStyle(.tint)
Text("Hello, Search Field!")
SearchField(searchText, textFieldChanged: { value in
print("value\(value)")
searchText = value

SearchField(searchText: $searchText, placeholder: "Search...", searchField: { searchField in
print(type(of: searchField)) // -> NSSearchField
})

SearchField(
searchText: $searchText,
placeholder: "Search...",
searchField: { field in
// 自定义 NSSearchField 的属性
field.isBordered = true
field.isBezeled = true
}
)

SearchField(
searchText: $searchText,
placeholder: "Search...",
onEditingChanged: { editing in
isEditing = editing
print("Editing started: \(editing)")
},
onTextChanged: { text in
print("Search text changed to: \(text)")

}
) { searchField in
print(type(of: searchField)) // -> NSSearchField
}

SearchField(searchText: $searchText, placeholder: "Search...", searchField: { text in
print("Search text changed to: \(text)")
})

Text(searchText)
}
.padding()
Expand Down
105 changes: 56 additions & 49 deletions Sources/SearchField/SearchField.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,89 +13,96 @@ import SwiftUI
/// struct ContentView: View {
/// @State private var searchText = ""
/// var body: some View {
/// SearchField(searchText, textFieldChanged: { value in
/// print("value\(value)")
/// searchText = value
/// SearchField(searchText: $searchText, placeholder: "Search...", searchField: { searchField in
/// print(type(of: searchField)) // -> NSSearchField
/// })
/// }
/// }
/// ```
///
///

@available(macOS 10.15, *)
public struct SearchField: View {
// A binding to a string value
@Binding private var text: String?
// A closure that takes a string as input and returns void
var textFieldChanged: ((String) -> Void)
@Binding private var text: String
var placeholder: String?
var onEditingChanged: ((Bool) -> Void)?
var onTextChanged: ((String) -> Void)?
var searchField: ((NSSearchField) -> Void)?

/// Initializes a SearchField with a default value and a closure to be called when the text field changes
public init(_ defaultValue: String?, textFieldChanged: @escaping ((String) -> Void)) {
_text = .constant(defaultValue ?? "")
self.textFieldChanged = textFieldChanged
public init(
searchText: Binding<String>,
placeholder: String? = nil,
onEditingChanged: ((Bool) -> Void)? = nil,
onTextChanged: ((String) -> Void)? = nil,
searchField: ((NSSearchField) -> Void)? = nil
) {
self._text = searchText
self.placeholder = placeholder
self.onEditingChanged = onEditingChanged
self.onTextChanged = onTextChanged
self.searchField = searchField
}
/// Initializes a SearchField with a binding to a string value and a closure to be called when the text field changes
public init(_ defaultValue: Binding<String?>, textFieldChanged: @escaping ((String) -> Void)) {
_text = defaultValue
self.textFieldChanged = textFieldChanged
}
// Defines the body of the SearchField view

public var body: some View {
let binding = Binding<String>(
get: { self.text ?? "" }, // Retrieves the value of the binding
set: {
// Sets the value of the binding to the new value
self.text = $0;
// Calls the closure with the new value
self.textFieldChanged($0)
}
SearchFieldView(
search: $text,
placeholder: placeholder,
onEditingChanged: onEditingChanged,
onTextChanged: onTextChanged,
searchField: searchField
)
// Returns a SearchFieldView with the created binding
return SearchFieldView(search: binding)
}
}

@available(macOS 10.15, *)
private struct SearchFieldView: NSViewRepresentable {
// A binding to a string value
@Binding var search: String
var placeholder: String?
var onEditingChanged: ((Bool) -> Void)?
var onTextChanged: ((String) -> Void)?
var searchField: ((NSSearchField) -> Void)?

class Coordinator: NSObject, NSSearchFieldDelegate {
var parent: SearchFieldView
init(_ parent: SearchFieldView) {
self.parent = parent
}
// Called when the text in the search field changes
func controlTextDidChange(_ notification: Notification) {
// let searchField = notification.object as! NSSearchField
// self.parent.search = searchField.stringValue

guard let searchField = notification.object as? NSSearchField else {
print("Unexpected control in update notification")
return
}
// Sets the binding value to the new search field value
self.parent.search = searchField.stringValue
guard let searchField = notification.object as? NSSearchField else { return }
parent.search = searchField.stringValue
parent.onTextChanged?(searchField.stringValue)
}

func controlTextDidBeginEditing(_ notification: Notification) {
parent.onEditingChanged?(true)
}

func controlTextDidEndEditing(_ notification: Notification) {
parent.onEditingChanged?(false)
}
}
// Creates a new NSView instance

func makeNSView(context: NSViewRepresentableContext<SearchFieldView>) -> NSSearchField {
// Creates a new NSSearchField
let tf = NSSearchField(frame: .zero)
// tf.focusRingType = .none
tf.drawFocusRingMask()
return tf
let searchField = NSSearchField(frame: .zero)
// Sets the coordinator as the search field's delegate
searchField.delegate = context.coordinator
searchField.placeholderString = placeholder

// Pass the NSSearchField instance to the external closure.
self.searchField?(searchField)

return searchField
}

func updateNSView(_ searchField: NSSearchField, context: NSViewRepresentableContext<SearchFieldView>) {
// Updates the search field's value with the binding value
searchField.stringValue = search
// Sets the coordinator as the search field's delegate
searchField.delegate = context.coordinator
searchField.placeholderString = placeholder
}

// 创建一个协调器实例以与 NSView 协调

// Creates a coordinator instance to coordinate with the NSView
func makeCoordinator() -> Coordinator {
return Coordinator(self)
}

}

0 comments on commit dbb3548

Please sign in to comment.