How can I put "print" data in a TextView upon button click of a function call (in macOs SwiftUI)?

Newbie question here. Using playground, I came up with the "logic" I wanted to use in my program. But don't know how to transfer the logic into my macOs Swift program. I have basically placed my text-transforming lines (from playground) inside a function, and now would like to call the function by using a button that would send the information into a TextView below it.

Here's my Content view:




struct ContentView: View {

 @State private var myText: String = ""

   
  func splitWords() {
    var mystring = "Orange Apple Pear"

    let mystringArray = mystring.components(separatedBy: " ")

    for (_, niceword) in mystringArray.enumerated() {
       
      print("<a id=\"\(niceword)\" class=\"ttTerm\" href=...>\(niceword)</a>") }
  }

   
 var body: some View {
   VStack {
        
      
   Button("Transformed text should go into field below", action: {
     splitWords()
   }
   )
      
      
   }
   MultilineTextEditorView(text: $myText)
     .frame(width: 400.0, height: 225.0)
     .padding(30)
      
 }



One main question here is -- I know I used "print" in playground to see the results in the console, but how do I make that line useful in terms of "printing" the data inside a TextView?

(I actually played around with different ideas in place of "print", like using "result" or assigning a "output" variable, but I kept getting errors.)

I have left out all the coding above this that deals with creating the TextView, which involves using "NSViewControllerRepresentable" -- but let me know if I should include that as well.

Thanks.

That would be much easier to have the complete code, so that we can try it.

Why not change in:

func splitWords() -> String {
  var mystring = "Orange Apple Pear"
  let mystringArray = mystring.components(separatedBy: " ")
  var result = ""
  for niceword in mystringArray  {     // enumerated() not needed here
    result = result + "<a id=\"\(niceword)\" class=\"ttTerm\" href=...>\(niceword)</a>\n"     // \n to add a linefeed at the end
  }
    return result
}

and call it later as:

                    Text(splitWords())

Or use TextEditor:

struct ContentView : View { 
   @State var someText : String = splitWords()

    var body: some View {
           TextEditor(text: $someText)
     }
}

Thanks. Not sure what to do with the part of the answer that says "and call it later as: Text(splitWords())" -- don't know where to put that. (I put it in different places and ran the program but got different errors).

Regarding the top section of coding creating the TextView, it is below (I didn't want to clutter up the original question):

import SwiftUI


// MARK: - View
struct MultilineTextEditorView: NSViewControllerRepresentable {
  @Binding var text: String

  func makeNSViewController(context: Context) -> NSViewController {
    let vc = MultilineTextEditorController()
    vc.textView.delegate = context.coordinator
    return vc
  }

  func updateNSViewController(_ nsViewController: NSViewController, context: Context) {
    guard let vc = nsViewController as? MultilineTextEditorController else { return }

    if text != vc.textView.string {
      vc.textView.string = text
    }
  }
}

// MARK: - Coordinator
extension MultilineTextEditorView {

  func makeCoordinator() -> Coordinator {
    return Coordinator(self)
  }

  class Coordinator: NSObject, NSTextViewDelegate {
    var parent: MultilineTextEditorView
    var selectedRanges: [NSValue] = []

    init(_ parent: MultilineTextEditorView) {
      self.parent = parent
    }

    func textDidBeginEditing(_ notification: Notification) {
      guard let textView = notification.object as? NSTextView else { return }

      self.parent.text = textView.string
    }

    func textDidChange(_ notification: Notification) {
      guard let textView = notification.object as? NSTextView else { return }

      self.parent.text = textView.string
      self.selectedRanges = textView.selectedRanges
    }

    func textDidEndEditing(_ notification: Notification) {
      guard let textView = notification.object as? NSTextView else { return }

      self.parent.text = textView.string
    }
  }
}

// MARK: - Controller
fileprivate final class MultilineTextEditorController: NSViewController {
  var textView = NSTextView()

  override func loadView() {
    let scrollView = NSScrollView()

    // - ScrollView
    scrollView.documentView = textView
    scrollView.hasVerticalScroller = true
    scrollView.hasHorizontalScroller = false
    scrollView.autohidesScrollers = true
    scrollView.drawsBackground = false

    // Corner radius on scrollView (clips underlying textView)
    scrollView.wantsLayer = true
    scrollView.layer?.cornerRadius = 4.0

    // - TextView
    textView.autoresizingMask = [.width]
    textView.allowsUndo = true
    textView.font = .systemFont(ofSize: 16)

    // Background and reposition insets so they don't get clipped by scrollView cornerRadius
    textView.backgroundColor = NSColor.textBackgroundColor.withAlphaComponent(0.25)
    textView.textContainerInset = NSSize(width: 4, height: 4)

    self.view = scrollView
  }

  override func viewDidAppear() {
    self.view.window?.makeFirstResponder(self.view)
  }
}
code-block

[don't forget to add a "}" after my ContentView to complete the picture]

I just saw the part of your answer regarding using a TextEditor. Thanks for the suggestion, but I do have a reason for creating a TextView and Button -- it's because I plan to expand this little program to have another capability (which I am also trying to figure out -- but I will probably post another question on the forum for that).

In fact, put the TextEditor in the button action.

Here is a small example:

struct ContentView : View {
    @State var someText : String = splitWords()
    @State private var showText = false

    var body: some View {
                VStack {

        Button("Show text") {
            showText.toggle()
        }

        if showText {
            TextEditor(text: $someText)
        }
    }
}

Thanks for your elegant solution. It's certainly a lot cleaner, simpler than my hacky NSviewcontrolRepresentable code (that I found on the 'net). If you could please indulge me regarding one more thing -- the other "capability" I mentioned earlier -- I would like to create a TextView (or TextEditor view) above your solution, and be able to enter any group of words (it could be anywhere from 3 to 50 words (it might be a paragraph from a news article) that would serve as the "Input" field in place of my original "Orange Apple Pear" data.

Here is the "look and feel" of what I'm after based on the coding you provided:

    import SwiftUI



func splitWords() -> String {

  var mystring = "Orange Apple Pear"

  let mystringArray = mystring.components(separatedBy: " ")

  var result = ""

  for niceword in mystringArray  {     // enumerated() not needed here

    result = result + "<a id=\"\(niceword)\" class=\"ttTerm\" href=...>\(niceword)</a>\n"     // \n to add a linefeed at the end

  }

    return result

}



struct ContentView : View {



    @State var someText1 : String = ""

    @State var someText2 : String = splitWords()

    @State private var showText = true



    var body: some View {

        VStack {







        if showText {

            TextEditor(text: $someText1)

                .frame(width: 400.0, height: 225.0)

                .padding(30)

            Button("Show transformed text below") {

                showText.toggle()

            }

            TextEditor(text: $someText2)

                .frame(width: 400.0, height: 225.0)

                .padding(30)

            

        }

    }

}



}

So the idea is that I would be able input some words in the top field, click the button, and then have the result come out in the bottom field.

[By the way, I'm the poster of the original question. When I came back here to post, my id was locked out, don't know why, so I re-entered my name but added an extra "0" to my name]

With your code, we see nothing once button tapped.

Because you put everything in the if showText { }

I tried this, which seems to do what you are expecting. Even more, as text2 is changed "on the fly".

func splitWords(_ mystring: String) -> String {
//  var mystring = "Orange Apple Pear"
  let mystringArray = mystring.components(separatedBy: " ")
  var result = ""
  for niceword in mystringArray  {     // enumerated() not needed here
    result = result + "<a id=\"\(niceword)\" class=\"ttTerm\" href=...>\(niceword)</a>\n"     // \n to add a linefeed at the end
  }
    return result
}
struct ContentView : View {
    
    @State var someText1 : String = "Orange Apple Pear"
    @State var someText2 : String = ""
    @State private var showText = false
    
    var body: some View {
        
        VStack {
            TextEditor(text: $someText1)    // always visible
                .frame(width: 400.0, height: 225.0)
                .padding(30)
            
            if showText {
                // Button to hide
                Button("Hide transformed text below") {
                    showText.toggle()
                }
                // Let's show text2 and change when text1 is changed
                TextEditor(text: $someText2)
                    .frame(width: 400.0, height: 225.0)
                    .padding(30)
                    .onChange(of: someText1, perform: { _ in someText2 = splitWords(someText1) })
            } else {
                // Button to show ; we prepare text2
                Button("Show transformed text below") {
                    someText2 = splitWords(someText1)  // will be available when we show
                    showText.toggle()
                }
            }
            
        }
        
    }
}

Fantastic! This does just what I wanted. Thank you! This has also been a great experience for me to get acquainted with what can be done with SwiftUI. Thanks also for your commenting throughout -- super helpful for me to understand what's going on.

How can I put "print" data in a TextView upon button click of a function call (in macOs SwiftUI)?
 
 
Q