SwiftUI ile Dinamik Listeleme

Ata Anıl Turgay
blutv
Published in
5 min readJun 10, 2021

SwiftUI ilk tanıtıldığı günden itibaren iOS developerların dikkatini üzerine çekmeye devam ediyor. Önceki yazıda SwiftUI ile oldukça az kod satırı yazarak dinamik bir şekilde uygulamalar geliştirebileceğimizi temel kavramları inceleyerek detaylıca anlatmıştık. O yazıya buradan ulaşabilirsiniz.

Bu yazıda elimizde kanal listelerinin olduğu bir json olacak ve bu json’ı projede lokalde tutup modelleyeceğiz ve ekrana bu kanal listesini basacağız. Aynı BluTV uygulamasında canlı yayın kanalları listesinin olduğu ekran gibi değil mi? Buna ek olarak bir de search alanı ekleyip kanalları arayarak bulabileceğiz. O zaman hiç vakit kaybetmeden bir an önce başlayalım!

[{
"id": 12001,
"channelName": "Kanal-1001",
"channelLogo": "rectangle",
"channelNo": 1001,
"programName": "Kanal-1001 programı",
"programInterval": "20:00-21:10"
},
{
"id": 12002,
"channelName": "Kanal-1002",
"channelLogo": "shield",
"channelNo": 1002,
"programName": "Kanal-1002 programı"
"programInterval": "22:00-23:00"
},
{
"id": 12003,
"channelName": "Kanal-1003",
"channelLogo": "app",
"channelNo": 1003,
"programName": "Kanal-1003 programı",
"programInterval": "22:15-23:15"
}
... // 9 adet daha kanal modeli olduğunu düşünelim
]

Yukarıdaki örnekte bir json dosyamız mevcut. İçerisinde 12 tane kanal objesi bu şekilde tutuluyor, servisten kanal listesini çektiğimizi düşünelim çünkü bu örnekte datayı lokalde tutacağımızı belirttik. Örnekte ilk 3 tanesi gözükmekte.

Lokalde herhangi bir dosyaya erişmek için Bundle class’ını kullanırız. Ancak bu örneğimizde lokaldeki bir json dosyasına erişip içerisindeki datayı modellememiz gerekiyor. Bunun için bir Bundle extension yazıyoruz.

extension Bundle {
func decode<T: Codable>(_ file: String) -> T {
guard let url = self.url(forResource: file, withExtension: nil)
else {
fatalError("Failed to locate \(file) in bundle.")
}
guard let data = try? Data(contentsOf: url) else {
fatalError("Failed to load \(file) from bundle.")
}
guard let loaded = try? JSONDecoder().decode(T.self, from: data) else {
fatalError("Failed to decode \(file) from bundle.")
}
return loaded
}
}

Yazdığımız decode methodu generic bir methoddur. İlk kontrol lokalde verdiğimiz isimle bir dosya olup olmadığıdır. İkinci kontrol bu dosya içerisinde çekilecek bir data olup olmadığıdır. Son kontrol ise datayı istediğimiz modele decode edebilecek mi diye yazdığımız bir kontroldür. Tüm bu kontroller başarıyla geçildikten sonra bize generic bir T type döndürür. Bu bizim belirlediğimiz ve istediğimiz modelin tipidir. Aşağıdaki gibi bir model yaratalım.

struct Channel: Codable, Identifiable {
let id: Int
let channelName: String
let channelLogo: String
let channelNo: Int
let programName: String
let programInterval: String
}

SwiftUI kütüphanesinin ForEach veya List kullanımlarında dinamik view’lar oluşturulurken bu view hiyerarşisini doğru bir şekilde anlayarak yönetebilmesi için Identifiable protocolünü kullanıyoruz. Gelelim bir önceki yazımızda oluşturduğumuz LiveTVView içerisine.

Yazmış olduğumuz Bundle extension içerisindeki decode methodu ile LiveTV.json dosyasındaki datayı alıp yine yukarıda oluşturduğumuz Channel modeline göre decode ederek ‘channels’ ismini verdiğimiz array’i oluşturduk. Body’i incelemeden önce 32. satırda başlayan LiveTVView_Preview struct’ını incelersek burada sadece LiveTVView() kodunu görmüş oluruz. Sağ taraftaki preview işte sadece bu tek satır kod ile çalışmakta. Böylece LiveTVView’ı initialize ederek preview alanını doldurduğunu görmüş olduk. Preview alanı sadece kendisinden sorumlu olan kod bloğunun çıktısını temsil ettiği için yukarıdaki görselde daha önce oluşturmuş olduğumuz TabView yapısı gözükmemekte. Projemizi run ettiğimiz zaman TabView’lı olan halini koruduğumuzu görebilirsiniz. Gelin şimdi body içerisinde neler yazdık inceleyelim!

Elimizde bir NavigationView var ve title’ı ‘Canlı Yayın’. Bu NavigationView içerisine de bir ScrollView ekliyoruz. ScrollView içerisinde ForEach ile channels array’i içinde dönerek ChannelView instanceları yaratıyoruz. ChannelView bizden bir channel model bekliyor. ForEach ile dönerek array içerisindeki channelları sırasıyla bu ChannelView’a veriyoruz ve karşılığında custom channel viewlarını elde ederek ScrollView içerisine basıyoruz. Peki nedir bu ChannelView gelin bakalım!

ChannelView struct’ı içerisinde beklediğimiz bir channel model var. Bu modele göre ekranı dolduracağız.

İlk olarak preview öncekilere göre farklı modda, bunun sebepleri 46. ve 47. satırlarda sırasıyla ‘preferredColorSchema(.dark)’ ile arka planı dark mode yapmamız ve ‘previewLayout(.sizeThatFits)’ diyerek kullanacağımız alan kadar boyut vermesini sağlamış olmamızdır.

Body içerisinde bir HStack(Horizontal StackView) yaratıyoruz ve alignment .center veriyoruz. Bu HStack içerisindeki objelerin mesafesini 16 birim belirledik. Daha sonra HStack içerisine sırayla Image, Spacer, VStack, Spacer, Image ekliyoruz. İlk Image kanal logosu, son image play logosu için. Spacer adından da anlaşılacağı üzere boşluk bırakmak için kullanılıyor. Image kullanımında genelde unutulan kısım .resizable()’dır. Eğer bu kullanım unutulursa image istenilen boyutlarda ve çözünürlükte olmayacaktır. HStack içerisinde bir de VStack(Vertical StackView) kullandık. İçerisinde gördüğünüz üzere üç tane text mevcut. Bu textler sırayla kanal adını, kanaldaki programın zaman aralığını ve kanalda yayınlanan programın adını gösteriyorlar.

Artık elimizde herhangi bir view içerisinde kullanabileceğimiz bir ChannelView mevcut. SwiftUI kütüphanesinin en ilgi çekici yanı işte bu custom reusable view’ları çok kolay ve sade bir şekilde oluşturmaya olanak sağlaması ve bu oluşturulan view’ların kolay bir şekilde tekrar tekrar kullanılabilmesidir.

Yazının başında belirttiğimiz gibi bu ekranda bir de search alanı ve bu search alanına girilen text’e göre güncellenmesi gereken bir liste yarattığımızı gözlemlememiz gerekiyor.

ScrollView içerisine ForEach döngüsünden önce bir adet TextField ekledik. 14. satırda gördüğümüz üzere searchText değişkeni tanımladık, önceki yazımızda buradaki state ifadesinin ne anlama geldiğini ve $ ile bu değişkeni nasıl dinlediğimizi ifade etmiştik. ForEach döngüsünde channels arrayini eğer searchText boş string ise filtreleme yapma, eğer searchText boş string değilse searchText’in bu array içinde olup olmadığına bak ve filtreleme yap komutunu vermiş olduk. $searchText ile bu değişkenin her değişiklik durumunda dinlenmesi sağlanarak filtreleme işlemi yapılır ve ekran yukarıdaki preview alanındaki gibi güncellenir.

Yine çok sade ve kısa bir kod bloğu ile neler yaptık öyle değil mi? SwiftUI bizi bu ve benzeri örneklerle şaşırtmaya devam edecek gibi görünüyor.

Bu yazıda SwiftUI kütüphanesi ile canlı yayın kanalları servisinden kanalları çekmişiz gibi lokalde tuttuğumuz dataları modelleyerek kanal listeleme ekranı yaptık ve bu ekranı search bar ile süsleyerek dinamik hale getirdik. Böylece girilen her yeni arama metni ile kendisini güncelleyen dinamik bir kanal listeleme ekranımız oldu.

Herkese keyifli araştırmalar ve okumalar…

--

--