Reputation: 1701
I'm having a chat view with messages. When message composer gets a focus and keyboard is appeared the height of ScrollView
decreases. Now I want all messages to move up a little so users can see the same bottom message she saw before. Is there anyway to achieve this with a pure SwiftUI?
ScrollViewReader { scrollReader in
ScrollView {
LazyVStack(spacing: 24) {
ForEach(messages, id: \.id) {
MessageContainer(message: $0)
.id($0.id)
}
}
.padding(.horizontal, 16)
}
}
Upvotes: 5
Views: 5890
Reputation: 3
If you add edgesIgnoringSafeArea(.bottom)
before going to the webview page, the view will rise to the top when the keyboard opens.
Example code:
router.pushTo(view: MainNavigationView.builder.makeView(WebViewContainer(urlString: AppSettings.shared.customerSupportForm, type: .web, isAtBottom: .constant(false), isLoading: $viewModel.isLoading).edgesIgnoringSafeArea(.bottom), withNavigationTitle: userEnvironment.txtCustomerContactForm.localize,isNavBarAlphaAnimationActive: false))
Upvotes: 0
Reputation: 61
It happens because setting offset does not affect on views size.
You can add Spacer view with height equal to keyboard height and all views moved with changing self size.
I'm using KeyboardGuardian for find keyboard offset and use it like that:
VStack {
// ... LazyVStack, ChatControls, etc...
Spacer()
.frame(height: -kGuardian.slide)
.savePosition(in: $kGuardian.rects[0])
}
.ignoresSafeArea(.keyboard, edges: .bottom)
.onAppear() {
kGuardian.addObserver()
}
.onDisappear() {
kGuardian.removeObserver()
}
Upvotes: 0
Reputation: 12125
Then just try it with .offset
:
struct ContentView: View {
let messages = Message.dummyData
@State private var showNewMessage = false
@State private var newMessage = ""
@FocusState private var focus: Bool
var body: some View {
VStack {
ScrollView {
LazyVStack(alignment: .leading, spacing: 24) {
ForEach(messages, id: \.id) { message in
MessageContainer(message: message)
.offset(y: showNewMessage ? -300 : 0)
}
}
.padding(.horizontal, 16)
}
if showNewMessage == false {
Button("New Message") {
withAnimation {
showNewMessage = true
focus = true
}
}
} else {
Button("Send") {
showNewMessage = false
}
TextEditor(text: $newMessage)
.frame(height: 80)
.padding()
.background(
RoundedRectangle(cornerRadius: 20)
.stroke(Color.gray, lineWidth: 1)
)
.padding(.horizontal, 16)
.focused($focus)
}
}
}
}
Upvotes: 0
Reputation: 12125
Here is an example that uses ScrollViewReader
to scroll to the tapped message for answering it:
struct ContentView: View {
let messages = Message.dummyData
@State private var tappedMessage: Message?
@State private var newMessage = ""
@FocusState private var focus: Bool
var body: some View {
ScrollViewReader { scrollReader in
ScrollView {
LazyVStack(alignment: .leading, spacing: 24) {
ForEach(messages, id: \.id) { message in
MessageContainer(message: message)
.id(message.id)
.onTapGesture {
tappedMessage = message
focus = true
}
}
}
.padding(.horizontal, 16)
}
if let tappedMessage {
VStack {
TextEditor(text: $newMessage)
.frame(height: 80)
.padding()
.background(
RoundedRectangle(cornerRadius: 20)
.stroke(Color.gray, lineWidth: 1)
)
.padding(.horizontal, 16)
.focused($focus)
Button("Send") { self.tappedMessage = nil }
}
.onAppear {
DispatchQueue.main.asyncAfter(deadline: .now()+0.5) {
withAnimation {
scrollReader.scrollTo(tappedMessage.id)
}
}
}
}
}
}
}
Upvotes: 4