414b980117fce2cf2680a5d65df9c32c8cd98bcb
26 Nisan 2023

SwiftUI List ile scrollto sorunu

ile umut

SwiftUI, Jetpack Compose gibi kolay ve okunabilir tasarım ve kodlama yoluna gitme yönünde ilerlemek ve daha fazla özelleştirme yapılabilmesinin önünü açmak için yazılmış bir frameworktür.

Ben bu yapının kullanımını ilk olarak 2016’lı yıllarda Android için Anko kütüphanesinde görüp kullanmaya başlamıştım. Anko tabi sadece bu yönüyle değil arkaplan işlemlerini kolaylaştırma gibi bir çok geniş yelpazede işleri kolaylaştıran bir kütüphaneydi ki zaten sonradan bir çok özelliğini kotlin’e eklediler hatta Jetpack Compose’da oradan evrildi diyebiliriz. Şimdilerde arşivlenmiş bir şekilde github’da kodlarına bakmanız mümkün.

Sözün özüne gelecek olursak bu yapılarda diğer her yapıda olduğu gibi eksiklikler mevcut ancak SwiftUI henüz listeleme işini tam anlamıyla çözememiş olacak ki List kullanıldığında otomatik scroll etmek istediğimizde 3. kez scroll edilince patlıyor. Bu sorun 2020 yılından beri forumlarda ve stackoverflow’da var ancak sorun bir türlü çözülememiş. Bir çok yöntemi denedim, çözen de var ancak flick olma gibi sorunlar mevcut. Bulduğum en temiz çözümü de buradan paylaşmak istedim. Öncelikle ne yaptığımızda bu sorun oluyor onu göstereyim.

var body: some View {
        VStack {
            ScrollViewReader { scrollView in

                List(messages, id: \.id) { message in
                    MessageRow(message: message)
                }.onAppear(perform: {
                    self.scrollViewProxy = scrollView
                    self.messages.append(Message(text: "Yukleniyor", isMe: false))
                

                    DispatchQueue.main.asyncAfter(deadline: .now() + 16) {
                        withAnimation(Animation.easeIn(duration: 100.0).delay(10)) {

                            self.messages.append(Message(text: "Sor sorabildigini yuce yapay zekama", isMe: false))
                            scrollView.scrollTo(self.messages.count-1, anchor: .bottom)
                        }
                    }
                    self.isScrolledToEnd = true

                })
            }

        }

Burada onAppear’da listeye en son eklenen yani en alttaki mesaj’a gitmesi için ScrollViewReader kullanılması öneriliyor ki içindeki proxy(scroolView) değişkenini kullanıp scrollTo diyerek son item’a animasyonla gidelim. Ancak app crash oluyor, DispatchQueue.main.async kullanılması oneriliyor ancak değişen bir sonuç olmuyor. Bu durumda List kullanmamak gerekiyor. Bu yapı değişikliğini aşağıdaki şekilde kullanırsanız sorununuz çözülüyor.

var body: some View {
        VStack {
            ScrollView {
                ScrollViewReader { scrollView in
                    LazyVStack {
                        ForEach(viewModel.messages, id: \.id) { message in
                            MessageRow(message: message)
                                .id(message.id)
                        }
                    }
                    .padding(8)
                    .onChange(of: scrollItem) { _ in
                        if let lastMessage = viewModel.messages.last {
                            withAnimation {
                                scrollView.scrollTo(lastMessage.id, anchor: .bottom)
                            }
                        }
                    }
                }
            }
            Button("Add Message") {
                
                addItem()
            }
        }
    }

Burada LazyVStack ve içerisinde ForEach kullanmanın önemi var, viewModel.messages’a veri eklerken scrollItem seklinde bir Integer tanımlayarak arttırma işlemi yaptım burada veri her değiştiğinde onChange çalışıyor ve scroll oluyor. İsterseniz onChange’e direk viewModel.messages verebilirsiniz. Ben başka işlemler de yaptığım için değişken vermek istedim.