0

I would like to have a ScrollView that encloses a Table and a TextEditor, stacked vertically. With this arrangement, a user would always scroll down past all the table rows to find the text editor.

Below is modified Apple example code that I've been experimenting with to no avail. I get the desired stacking by manually adjusting tableContentHeight variable. How could I adjust it dynamically at runtime? Or is there some other way to achieve the desired setup?

import SwiftUI
struct Person: Identifiable {
    let givenName: String
    let familyName: String
    let emailAddress: String
    let id = UUID()
}
struct PeopleTable: View {
    @State private var people = [
        Person(givenName: "Juan", familyName: "Chavez", emailAddress: "[email protected]"),
        Person(givenName: "Mei", familyName: "Chen", emailAddress: "[email protected]"),
        Person(givenName: "Tom", familyName: "Clark", emailAddress: "[email protected]"),
        Person(givenName: "Gita", familyName: "Kumar", emailAddress: "[email protected]"),
        Person(givenName: "Juan", familyName: "Chavez", emailAddress: "[email protected]"),
        Person(givenName: "Mei", familyName: "Chen", emailAddress: "[email protected]"),
        Person(givenName: "Tom", familyName: "Clark", emailAddress: "[email protected]"),
        Person(givenName: "Gita", familyName: "Kumar", emailAddress: "[email protected]")
    ]

    // TODO: make this track table content height
    @State private var tableContentHeight: CGFloat = 235

    @State private var text: String = loremIpsum
    var body: some View {
        ScrollView {
            Table(people) {
                TableColumn("Given Name", value: \.givenName)
                TableColumn("Family Name", value: \.familyName)
                TableColumn("E-Mail Address", value: \.emailAddress)
            }
            .frame(height: tableContentHeight)
            TextEditor(text: $text).font(Font.system(size: 14))
        }
    }
}
#Preview {
    PeopleTable()
}

let loremIpsum = """
Pariatur neque enim occaecati ut inventore odio. Eius earum cumque ea placeat debitis nobis eos. Doloremque eum consectetur quos quia. Optio dignissimos quis iste. Nostrum animi vel doloribus et. Non delectus odio dolor in.

Eos ea aperiam est cupiditate eius quibusdam nulla. Sed sed aut labore blanditiis temporibus dolores. Doloribus vel expedita et possimus rerum. Doloribus ut vel nisi debitis molestias.

Pariatur quia eligendi et. Ipsa quae enim fugiat modi odit voluptatibus est. Officiis non impedit repellat omnis quod labore. Ratione ut iste ab ratione atque atque.

Quos nesciunt aut quos autem aut iusto repellendus. Error ex labore ad eaque dolorem. Laborum sed et ut incidunt mollitia omnis molestias. Reiciendis officiis alias quos expedita asperiores et culpa. Reiciendis nihil minima qui consequuntur. Maiores sint ut numquam facere ullam temporibus ea officiis.
"""
1
  • You'll need to use List or just a ScrollView for that. there isn't a built in way yet for this. Commented Apr 25 at 17:39

1 Answer 1

0

This is very similar to this question, and my answer there applies here too. While onScrollGeometryChange doesn't work on macOS Tables, you can still use SwiftUI-Introspect to work with the NSTableView directly.

struct PeopleTable: View {
    @State private var people = [
        Person(givenName: "Juan", familyName: "Chavez", emailAddress: "[email protected]"),
        Person(givenName: "Mei", familyName: "Chen", emailAddress: "[email protected]"),
        Person(givenName: "Tom", familyName: "Clark", emailAddress: "[email protected]"),
        Person(givenName: "Gita", familyName: "Kumar", emailAddress: "[email protected]"),
    ]
    
    @State private var tableContentHeight: CGFloat = 235
    
    @State private var text: String = loremIpsum
    var body: some View {
        ScrollView {
            Table(people) {
                TableColumn("Given Name", value: \.givenName)
                TableColumn("Family Name", value: \.familyName)
                TableColumn("E-Mail Address", value: \.emailAddress)
            }
            // specify the versions of macOS on which you want this to work
            .introspect(.table, on: .macOS(.v15)) { table in
                DispatchQueue.main.async {
                    // take into account the table header height
                    // in addition to the total height of the rows
                    tableContentHeight = table.intrinsicContentSize.height + (table.headerView?.bounds.height ?? 0)
                }
            }
            .scrollDisabled(true)
            .frame(height: tableContentHeight)
            TextEditor(text: $text).font(Font.system(size: 14))
        }
    }
}
Sign up to request clarification or add additional context in comments.

5 Comments

I applied .onScrollGeometryChange to the table as you suggested, but I don't see it invoked at all. The effect is that PeopleTable remains invisible with initial height of zero. If I add .onScrollGeometryChange to the explicit outer ScrollView, I observe its closure invoked. I read in the docs that "If multiple scroll views are found within the view hierarchy, only the first one will invoke the closure you provide and a runtime issue will be logged.” I don't see anything logged, though. I’m targeting macOS (forgot the tag; will fix that).
I should have been more clear. Whatever I initialize tableContentHeight to (e.g., 100), the table remains at the initialized height and onScrollGeometryChange is never invoked.
@user2962393 Okay, so this doesn't work for macOS. Are you open to using SwiftUI-Introspect, which is not forward-compatible?
Swift-Introspect does not look attractive to me at this point. I guess I’ll just tolerate this limitation for now.
@user2962393 SwiftUI Table supports very little on macOS. If you are going to be primarily working with tables, I'd suggest wrapping a NSTableView. Otherwise you would likely have many more limitations to tolerate. I've already shown an example here. All you need to do is override sizeThatFits differently - return proposed width and table.intrinsicContentSize.height + (table.headerView?.bounds.height ?? 0) as the height.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.