Auto ScrollView using SwiftUI and Combine framework

Auto ScrollView
struct Message : Identifiable {
//MARK: Attributes
var id: Int
var message: String
var message_timestamp: Int
//MARK: Init
init(id: Int, message: String, message_timestamp: Int){
self.id = id
self.message = message
self.message_timestamp = message_timestamp
}
}
class MessagesStore: ObservableObject {  //MARK: Attributes
var didChange = PassthroughSubject<MessagesStore, Never>()
@Published var messages: [Message] = [] {
didSet { self.didChange.send(self) }
}
//MARK: Init
init(messages: [Message] = []) {
self.messages = messages
fetch()
}
//MARK: fetch
func fetch() {
let message = Message(id: 1, message: “First!”, message_timestamp: Int(Date().timeIntervalSince1970))
self.messages.append(message)
}
}
var scrollView : some View {
ScrollView(.vertical) {
ScrollViewReader { scrollView in
ForEach(self.messagesStore.messages) { msg in
VStack {
OutgoingPlainMessageView() {
Text(msg.message).padding(.all, 20)
.foregroundColor(Color.textColorPrimary)
.background(Color.colorPrimary)
}.listRowBackground(Color.backgroundColorList)
Spacer()
}
// 1. First important thing is to use .id
//as identity of the view
.id(msg.id)

.padding(.leading, 10).padding(.trailing, 10)
}
// 2. Second important thing is that we are going to implement //onChange closure for scrollTarget change,
//and scroll to last message id

.onChange(of: scrollTarget) { target in
withAnimation {
scrollView.scrollTo(target, anchor: .bottom)
}
}
// 3. Third important thing is that we are going to implement //onChange closure for keyboardHeight change, and scroll to same //scrollTarget to bottom.
.onChange(of: keyboardHeight){ target in
if(nil != scrollTarget){
withAnimation {
scrollView.scrollTo(scrollTarget, anchor: .bottom)
}
}
}
//4. Last thing is to add onReceive clojure, that will add an action to perform when this ScrollView detects data emitted by the given publisher, in our case messages array.
// This is the place where our scrollTarget is updating.
.onReceive(self.messagesStore.$messages) { messages in
scrollView.scrollTo(messages.last!.id, anchor: .bottom)
self.scrollTarget = messages.last!.id
}
}
}
}
//MARK: View body
var body: some View {
ZStack {
Color.backgroundColorList.edgesIgnoringSafeArea(.all)
VStack {
if (self.messagesStore.messages.first != nil) {
scrollView.onTapGesture(perform: {
UIApplication.shared.windows.first?
.rootViewController?.view.endEditing(true)
})
}
Divider()
HStack(spacing: 10) {
ResizableTF(observableText: self.observableText,
height: self.$height)
.frame(height: self.height < 120 ? self.height : 120)
.padding(.horizontal)
.background(RoundedRectangle(cornerRadius: 10)
.stroke(Color.gray, lineWidth: 0.5)
.background(Color.backgroundColorList))
Button(action: {
appendChatMessage()
}) {
Image(“ic_send”)
.resizable()
.frame(width: 40, height: 40, alignment: .center)
}
}
.padding(.leading, 10).padding(.trailing, 10)
.padding(.bottom, 20)
}
.onAppear(){

NotificationCenter.default
.addObserver(forName: UIResponder
.keyboardDidShowNotification, object: nil, queue: .main) {
(data) in let height = data.userInfo
[UIResponder.keyboardFrameEndUserInfoKey] as! NSValue
self.keyboardHeight = height.cgRectValue.height
}
NotificationCenter.default.addObserver(
forName: UIResponder.keyboardDidHideNotification,
object: nil, queue: .main){ (_) in
self.keyboardHeight = 0
}
}
.background(Color.backgroundColorList)
.foregroundColor(Color.textColorSecondary)
.navigationViewStyle(StackNavigationViewStyle())
.gesture(DragGesture().updating($dragOffset, body: { (value,
state, transaction) in
if(value.startLocation.x < 20 && value.translation.width > 100)
{
self.mode.wrappedValue.dismiss()
}
}))
}
}

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Kenan Begić

Hello there, I’ Kenan. Software Engineer, Fullstack developer, mobile hobbyist. Support my content at: buymeacoffee.com/kenanbegic