Expand NSViewRepresentable (NSTextView) height based on window height - swiftui

I created an NSViewRepresentable structure that manages the state of an NSTextView. The view, by default, only has one line and I'd like to extend that to the edge of the window.
The view is defined as such:
struct Editor: View, NSViewRepresentable {
#Binding var text: String
func makeNSView(context: Context) -> NSTextView {
let view = NSTextView()
view.font = NSFont.monospacedSystemFont(ofSize: 12, weight: .regular)
return view
}
func updateNSView(_ view: NSTextView, context: Context) {
let attributedText = NSMutableAttributedString(string: text)
view.textStorage?.setAttributedString(attributedText)
}
}
It currently looks like this, it adds lines as you go. Is there a way for the darker text area to be expanded to the bottom of the window automatically?

You can use NSTextView.scrollableTextView() to do this:
struct ContentView : View {
#State var text = ""
var body: some View {
VStack {
Editor(text: $text)
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
}
struct Editor: View, NSViewRepresentable {
#Binding var text: String
func makeNSView(context: Context) -> NSView {
let scrollView = NSTextView.scrollableTextView()
guard let textView = scrollView.documentView as? NSTextView else {
return scrollView
}
textView.font = NSFont.monospacedSystemFont(ofSize: 12, weight: .regular)
context.coordinator.textView = textView
return scrollView
}
func updateNSView(_ view: NSView, context: Context) {
let attributedText = NSMutableAttributedString(string: text)
context.coordinator.textView?.textStorage?.setAttributedString(attributedText)
}
func makeCoordinator() -> Coordinator {
return Coordinator()
}
class Coordinator {
var textView : NSTextView?
}
}

Related

CustomText in Swift UI

I need to make custom text in swift UI, I know I can make extension and do something like this :
Text("ABCD")
.HeadingXLargeStyle()
extension Text{
func HeadingXLargeStyle() -> some View{
self.font( .custom("nunito_regular", size: 80))
}
}
BUT I want to do something like this:
HeadingXLargeStyle("ABCD")
//I tried this
final class HeadingXLarge: UIViewRepresentable{
func updateUIView(_ uiView: UITextView, context: Context) {
uiView.text = newtext
}
var newtext: String
init(_ text: String){
newtext = text
}
typealias UIViewType = UITextView
func makeUIView(context: Context) -> UITextView {
let textView = UITextView()
textView.font = UIFont(name: "inter_bold.ttf", size: 40)
return textView
}
}
but not working.
For a custom text view, you can simply create one View that has a string property to display text like this.
struct CustomText: View {
let text: String
var body: some View {
Text(text)
.font(.custom("nunito_regular", size: 80))
}
}
If you want to access UITextView from SwiftUI you need to create a struct that confirms to protocol UIViewRepresentable, not a class.
struct TextView: UIViewRepresentable {
#Binding var text: String
func makeUIView(context: Context) -> UITextView {
UITextView()
}
func updateUIView(_ uiView: UITextView, context: Context) {
uiView.text = text
}
}
Now access the above TextView like this.
struct ContentView: View {
#State var text = ""
var body: some View {
TextView(text: $text)
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
}
}

swiftui ios15 keyboard avoidance issue on custom textfield

Repost question from this Adjust View up with Keyboard show in SwiftUI 3.0 iOS15.
SwiftUI keyboard avoidance won't show the whole textfield including the overlay.
I already tried a lot different ways from googling.
Does anyone have any solution for this?
struct ContentView: View {
#State var text: String = ""
var body: some View {
ScrollView {
VStack {
Spacer(minLength: 600)
TextField("Placeholder", text: $text)
.textFieldStyle(CustomTextFieldStyle())
}
}
}
}
struct CustomTextFieldStyle: TextFieldStyle {
func _body(configuration: TextField<Self._Label>) -> some View {
configuration
.padding(10)
.overlay(
RoundedRectangle(cornerRadius: 20)
.stroke(Color.red, lineWidth: 5)
)
}
}
You can write the custom UITextFeild, in which the intrinsicContentSize will be overridden.
Example:
final class _UITextField: UITextField {
override var intrinsicContentSize: CGSize {
CGSize(width: UIView.noIntrinsicMetric, height: 56)
}
}
Then, you can write your own implementation of TextField, using UIViewRepresentable protocol and UITextFieldDelegate:
struct _TextField: UIViewRepresentable {
private let title: String?
#Binding var text: String
let textField = _UITextField()
init(
_ title: String?,
text: Binding<String>
) {
self.title = title
self._text = text
}
func makeCoordinator() -> _TextFieldCoordinator {
_TextFieldCoordinator(self)
}
func makeUIView(context: Context) -> _UITextField {
textField.placeholder = title
textField.delegate = context.coordinator
return textField
}
func updateUIView(_ uiView: _UITextField, context: Context) {}
}
final class _TextFieldCoordinator: NSObject, UITextFieldDelegate {
private let control: _TextField
init(_ control: _TextField) {
self.control = control
super.init()
control.textField.addTarget(self, action: #selector(textFieldEditingChanged), for: .editingChanged)
}
#objc private func textFieldEditingChanged(_ textField: UITextField) {
control.text = textField.text ?? ""
}
}

How do I change the height of this UIViewRepresentable?

I found this code on the web :
struct CustomTextField: UIViewRepresentable {
#Binding var text: String
#State var placeholder: String
func makeCoordinator() -> Coordinator {
Coordinator(text: $text)
}
func makeUIView(context: Context) -> UITextField {
let textField = UITextField()
textField.borderStyle = .roundedRect
textField.placeholder = placeholder
textField.autocapitalizationType = .none
textField.autocorrectionType = .no
textField.spellCheckingType = .no
textField.keyboardType = .URL
textField.frame = CGRect(x: 0, y: 0, width: 20.00, height: 10)
textField.delegate = context.coordinator
return textField
}
func updateUIView(_ view: UITextField, context: Context) {
view.text = text
}
}
extension CustomTextField {
class Coordinator: NSObject, UITextFieldDelegate {
#Binding var text: String
init(text: Binding<String>) {
_text = text
}
func textFieldDidChangeSelection(_ textField: UITextField) {
DispatchQueue.main.async {
self.text = textField.text ?? ""
}
}
}
}
The code works absolutely fine. The problem with this is that I am not able to find a suitable way to increase the height of this. As you can see, I tried to use a CGRect as the frame, to no effect. How can I change the size (particularly height in my specific scenario) of this custom UIViewRepresentable?
Just the same you would do with any other SwiftUI view:
CustomTextField(text: $text, placeholder: "")
// constant
.frame(height: 100)
// fill available height
.frame(maxHeight: .infinity)
If you wanna make it respect intrinsicContentSize, check out this answer

SwiftUI Search Bar in line with navigation bar

Does anyone have working Swiftui code that will produce a search bar in the navigation bar that is inline with the back button? As if it is a toolbar item.
Currently I have code that will produce a search bar below the navigation back button but would like it in line like the picture attached shows (where the "hi" is):
I am using code that I found in an example:
var body: some View {
let shopList = genShopList(receiptList: allReceipts)
VStack{
}
.navigationBarSearch(self.$searchInput)
}
public extension View {
public func navigationBarSearch(_ searchText: Binding<String>) -> some View {
return overlay(SearchBar(text: searchText)
.frame(width: 0, height: 0))
}
}
fileprivate struct SearchBar: UIViewControllerRepresentable {
#Binding
var text: String
init(text: Binding<String>) {
self._text = text
}
func makeUIViewController(context: Context) -> SearchBarWrapperController {
return SearchBarWrapperController()
}
func updateUIViewController(_ controller: SearchBarWrapperController, context: Context) {
controller.searchController = context.coordinator.searchController
}
func makeCoordinator() -> Coordinator {
return Coordinator(text: $text)
}
class Coordinator: NSObject, UISearchResultsUpdating {
#Binding
var text: String
let searchController: UISearchController
private var subscription: AnyCancellable?
init(text: Binding<String>) {
self._text = text
self.searchController = UISearchController(searchResultsController: nil)
super.init()
searchController.searchResultsUpdater = self
searchController.hidesNavigationBarDuringPresentation = true
searchController.obscuresBackgroundDuringPresentation = false
self.searchController.searchBar.text = self.text
self.subscription = self.text.publisher.sink { _ in
self.searchController.searchBar.text = self.text
}
}
deinit {
self.subscription?.cancel()
}
func updateSearchResults(for searchController: UISearchController) {
guard let text = searchController.searchBar.text else { return }
self.text = text
}
}
class SearchBarWrapperController: UIViewController {
var searchController: UISearchController? {
didSet {
self.parent?.navigationItem.searchController = searchController
}
}
override func viewWillAppear(_ animated: Bool) {
self.parent?.navigationItem.searchController = searchController
}
override func viewDidAppear(_ animated: Bool) {
self.parent?.navigationItem.searchController = searchController
}
}
}
If anyone has a solution to this problem that would be greatly appreciated! I know that in IoS 15 they are bringing out .searchable but looking for something that will work for earlier versions too.
You can put any control in the position you want by using the .toolbar modifier (iOS 14+) and an item with .principal placement, e.g.:
var body: some View {
VStack {
// rest of view
}
.toolbar {
ToolbarItem(placement: .principal) {
MySearchField(text: $searchText)
}
}
}
A couple of things to note:
The principal position overrides an inline navigation title, either when it's set with .navigationBarTitleDisplayMode(.inline) or when you have a large title and scroll up the page.
It's possible that your custom view expands horizontally so much that the back button loses any text component. Here, I used a TextField to illustrate the point:
You might be able to mitigate for that by assigning a maximum width with .frame(maxWidth:), but at the very least it's something to be aware of.

SwiftUI Searchbar pushed off screen by keyboard

I have implemented a searchbar that filters a list. However when the keyboard appears it pushes the searchbar right off the screen. I have tried using .ignoresSafeArea(.keyboard) however it will not work (I have tried placing it in many different spots). I would like to make it so the view/list does not move at all when the keyboard appears.
I am displaying this view below after pressing a button
//MARK: - ActivitySelectorView
ActivitySelectorView(showActivitySelector: $showActivitySelector, activityToSave: activityToSave, allActivities: activities, categoryNames: categoryNames)
.environmentObject(activityToSave)
.frame(width: screen.width, height: screen.height)
.offset(x: showActivitySelector ? 0 : screen.width)
.offset(y: screen.minY)
.offset(x: viewState.width)
.animation(.easeInOut)
And inside ActivitySelectorView I have a title bar and the filtered list which includes a searchbar and list
var body: some View {
ZStack {
//backgroundColor
Color("\(activityToSave.category)Color")
VStack {
TitleBar(showingAlert: $showingAlert, showActivitySelector: $showActivitySelector, categoryName: categoryNames[(Int(String(activityToSave.category.last!)) ?? 1) - 1])
//MARK: - LIST
FilteredList(filter: activityToSave.category, passedActivityBinding: $activityToSave.activityName, showActivitySelector: $showActivitySelector)
.colorMultiply(Color("\(activityToSave.category)Color"))
}
}
}
Here we have FilteredList:
var body: some View {
List {
SearchBar(text: $searchText)
ForEach(fetchRequest.wrappedValue.filter({ searchText.isEmpty ? true : $0.name.contains(searchText) }), id: \.self) { activity in
Text(activity.name.capitalized)
.onTapGesture {
self.showActivitySelector = false
self.selectedActivity = activity.name.capitalized
}
}.onDelete(perform: deleteActivity)
}
.resignKeyboardOnDragGesture()
}
And last the code for the searchBar
struct SearchBar: UIViewRepresentable {
#Binding var text: String
class Coordinator: NSObject, UISearchBarDelegate {
#Binding var text: String
init(text: Binding<String>) {
_text = text
}
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
text = searchText
}
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
UIApplication.shared.endEditing()
}
}
func makeCoordinator() -> Coordinator {
return Coordinator(text: $text)
}
func makeUIView(context: Context) -> UISearchBar {
let searchBar = UISearchBar(frame: .zero)
searchBar.delegate = context.coordinator
searchBar.returnKeyType = .done
searchBar.enablesReturnKeyAutomatically = false
return searchBar
}
func updateUIView(_ uiView: UISearchBar, context: Context) {
uiView.text = text
}
}