ScrollView affecting the Views inside Geometry Reader

68 views Asked by At

Main Content View:

struct ContentView: View {
    
    @State var editing: Bool = false
    @State var inputText: String = ""
    @State var vOffset: CGFloat = 0
    @State var hOffset: CGFloat = 0
    @State private var activeField: Int = 0
    
    var body: some View {
        GeometryReader { geometry in
            ScrollView {
                ZStack(alignment: .top) {
                    if activeField == 1 {
                        SuggestionTextFieldMenu(editing: $editing, text: $inputText)
                            .zIndex(1)
                            .offset(y: geometry.size.height * 0.57 - 70)
                    }
                    
                    TextField("Placeholder", text: $inputText, onEditingChanged: { edit in
                        self.editing = edit
                        self.activeField = 1
                    })
                    .padding(.horizontal, 10)
                    .frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: 50, alignment: .center)
                    .background(Color.init(red: 0.98, green: 0.98, blue: 0.98))
                    .cornerRadius(14)
                    .shadow(radius: 4)
                    .padding(.horizontal, 20)
                    .offset(y: geometry.size.height * 0.5 - 70)
                    
                    if activeField == 2 {
                        SuggestionTextFieldMenu(editing: $editing, text: $inputText)
                            .zIndex(1)
                            .offset(y: geometry.size.height * 0.5)
                    }
                    
                    TextField("Placeholder", text: $inputText, onEditingChanged: { edit in
                        self.editing = edit
                        self.activeField = 2
                    })
                    .padding(.horizontal, 10)
                    .frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: 50, alignment: .center)
                    .background(Color.init(red: 0.98, green: 0.98, blue: 0.98))
                    .cornerRadius(14)
                    .shadow(radius: 4)
                    .padding(.horizontal, 20)
                    .offset(y: geometry.size.height * 0.5)
                }
            }
        }
    }
}

import SwiftUI
public struct SuggestionTextFieldMenu: View {
    
    @State var names: [String] = ["Apple","Peach","Orange","Banana", "Melon", "Watermelon","Mandarin","Mulberries","Lemon","Lime","Loquat","Longan","Lychee","Grape","Pear","Kiwi","Mango"]
    @Binding var editing: Bool
    @Binding var inputText: String
    
    private var filteredTexts: Binding<[String]> { Binding (
        get: {
            return inputText.isEmpty ? names : names.filter { $0.contains(inputText) }
        },
        set: { _ in })
    }
    
    public init(editing: Binding<Bool>, text: Binding<String>) {
        self._editing = editing
        self._inputText = text
    }
    
    public init(editing: Binding<Bool>, text: Binding<String>, verticalOffset: CGFloat, horizontalOffset: CGFloat) {
        self._editing = editing
        self._inputText = text
    }
    
    public var body: some View {
        ScrollView {
            LazyVStack(spacing: 0) {
                ForEach(filteredTexts.wrappedValue, id: \.self) { textSearched in
                    
                    Text(textSearched)
                        .padding(.horizontal, 25)
                        .padding(.vertical, 25)
                        .frame(minWidth: 0,
                               maxWidth: .infinity,
                               minHeight: 0,
                               maxHeight: 50,
                               alignment: .leading)
                        .contentShape(Rectangle())
                        .onTapGesture(perform: {
                            inputText = textSearched
                            editing = false
                            self.endTextEditing()
                        })
                    Divider()
                        .padding(.horizontal, 10)
                }
            }
        }.background(Color.white)
        
        .cornerRadius(15)
        .foregroundColor(Color(.black))
        .ignoresSafeArea()
        .frame(maxWidth: .infinity,
               minHeight: 0,
               maxHeight: 50 * CGFloat( (filteredTexts.wrappedValue.count > 3 ? 3: filteredTexts.wrappedValue.count)))
        .shadow(radius: 4)
        .padding(.horizontal, 25)
        .isHidden(!editing, remove: !editing)
        
    }
}

import SwiftUI
public extension View {
    @ViewBuilder func isHidden(_ hidden: Bool, remove: Bool = false) -> some View {
        if hidden {
            if !remove {
                self.hidden()
            }
        } else {
            self
        }
    }
    
    func endTextEditing() {
        UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder),
                                        to: nil, from: nil, for: nil)
    }
}

@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
public struct StatefulPreviewWrapper<Value, Content: View>: View {
    @State var value: Value
    var content: (Binding<Value>) -> Content

    public var body: some View {
        content($value)
    }

    public init(_ value: Value, content: @escaping (Binding<Value>) -> Content) {
        self._value = State(wrappedValue: value)
        self.content = content
    }
}

I tired to use vstack but that push the elements downwards when dropdown appear. I cannot use Overlay because that not supported in IOS 14.0. I change the position of ScrollView does change any thing. right now i cant even tap on my textfields.I want to display dropdown menu right under the textField when i click on textField and dropDown Menu should above the View and doesn't change the position of other view inside the body.

Current View of app with this code

1

There are 1 answers

1
Benzy Neez On BEST ANSWER

As mentioned in a comment, you can use overlays in iOS 14, but the syntax is a little different. However, since a suggestion menu is taller than the TextField it is shown against, it is not a bad idea to use a ZStack.

The big change I would suggest is not to use offset for layout. Instead, use padding.

It seems that you were using a GeometryReader to define the position of the views based on a fraction of the total display height. This is probably not a very reliable way to get a consistent layout across different sizes of display. But if you really want to do it this way, you could apply padding to the ScrollView based on the excess height.

Here is an updated version of your ContentView body. The suggestion layers appear below the other layers, this way there is no need to apply .zIndex. Padding is used to position the views relative to the top of the ZStack. I have left out the GeometryReader since I didn't see that it was serving any useful purpose.

var body: some View {
    ScrollView {
        ZStack(alignment: .top) {
            TextField("Placeholder", text: $inputText, onEditingChanged: { edit in
                self.editing = edit
                self.activeField = 1
            })
            .padding(.horizontal, 10)
            .frame(maxWidth: .infinity)
            .background(Color.init(red: 0.98, green: 0.98, blue: 0.98))
            .cornerRadius(14)
            .shadow(radius: 4)

            TextField("Placeholder", text: $inputText, onEditingChanged: { edit in
                self.editing = edit
                self.activeField = 2
            })
            .padding(.horizontal, 10)
            .frame(maxWidth: .infinity)
            .background(Color.init(red: 0.98, green: 0.98, blue: 0.98))
            .cornerRadius(14)
            .shadow(radius: 4)
            .padding(.top, 100)

            if activeField == 1 {
                SuggestionTextFieldMenu(editing: $editing, text: $inputText)
                    .padding(.top, 35)
            } else if activeField == 2 {
                SuggestionTextFieldMenu(editing: $editing, text: $inputText)
                    .padding(.top, 135)
            }
        }
        .padding(20)
    }
    .frame(maxHeight: 360)
}

Screenshot