Нередко встречающаяся в разработке под iOS задача — раскрывающиеся/складывающиеся секции в UITableView. Сегодня мы реализуем эту задачу, используя SwiftUI. В качестве небольшого twist'a добавим анимированный треугольник в заголовке секции и сделаем ячейки также раскрывающимися.
Разработка проходила на XCode 11.2 под macOS Catalina 10.15.1
struct QuoteDataModel : Identifiable {
var id: String {
return latin
}
var latin : String
var russian : String
var expanded = false
}
struct SectionDataModel : Identifiable {
var id: Character {
return letter
}
var letter : Character
var quotes : [QuoteDataModel]
var expanded = false
}
var latinities : [SectionDataModel] = [
SectionDataModel(letter: "C", quotes: [
QuoteDataModel(latin: "Calvitium non est vitium, sed prudentiae indicium.", russian: "Лысина не порок, а свидетельство мудрости."),
QuoteDataModel(latin: "Conjecturalem artem esse medicinam.", russian: "Медицина есть искусство догадок."),
QuoteDataModel(latin: "Crede firmiter et pecca fortiter!", russian: "Верь крепче и греши смелее!")]),
SectionDataModel(letter: "H", quotes: [
QuoteDataModel(latin: "Homo sine religione sicut equus sine freno.", russian: "Человек без религии что лошадь без удил."),
QuoteDataModel(latin: "Habet et musca splenem.", russian: "Разозлиться может и муха.")]),
SectionDataModel(letter: "M", quotes: [
QuoteDataModel(latin: "Malum est mulier, sed necessarium malum.", russian: "Хоть женщина и зло, но зло необходимое."),
QuoteDataModel(latin: "Mulierem ornat silentium.", russian: "Женщину красит молчанье.")])]
import SwiftUI
struct HeaderView : View {
var section : SectionDataModel
var body: some View {
HStack() {
Spacer()
Text(String(section.letter))
.font(.largeTitle)
.foregroundColor(Color.black)
Spacer()
}
.background(Color.yellow)
}
}
struct HeaderView_Previews: PreviewProvider {
static var previews: some View {
HeaderView(section: latinities[0])
}
}
import SwiftUI
struct QuoteView: View {
var quote : QuoteDataModel
var body: some View {
VStack(alignment: .leading, spacing: 5) {
Text(quote.latin)
.font(.title)
if quote.expanded {
Group() {
Divider()
Text(quote.russian).font(.body)
}
}
}
}
}
struct QuoteView_Previews: PreviewProvider {
static var previews: some View {
QuoteView(quote: latinities[0].quotes[0])
}
}
struct ContentView: View {
var body: some View {
List {
ForEach(latinities) { section in
Section(header: HeaderView(section: section), footer: EmptyView()) {
if section.expanded {
ForEach(section.quotes) { quote in
QuoteView(quote: quote)
}
}
}
}
}
.listStyle(GroupedListStyle())
}
}
struct Triangle : Shape {
func path(in rect: CGRect) -> Path {
var path = Path()
path.move(to: CGPoint(x: 0, y: 0))
path.addLine(to: CGPoint(x: 0, y: rect.height - 1))
path.addLine(to: CGPoint(x: sqrt(3)*(rect.height)/2, y: rect.height/2))
path.closeSubpath()
return path
}
}
Triangle()
.fill(Color.black)
.overlay(
Triangle()
.stroke(Color.red, lineWidth: 5)
)
.frame(width : 50, height : 50)
.padding()
.rotationEffect(.degrees(section.expanded ? 90 : 0), anchor: .init(x: 0.5, y: 0.5)).animation(.default))
class UserData : ObservableObject {
@Published var latinities : [SectionDataModel] = [
SectionDataModel(letter: "C", quotes: [
QuoteDataModel(latin: "Calvitium non est vitium, sed prudentiae indicium.", russian: "Лысина не порок, а свидетельство мудрости."),
QuoteDataModel(latin: "Conjecturalem artem esse medicinam.", russian: "Медицина есть искусство догадок."),
QuoteDataModel(latin: "Crede firmiter et pecca fortiter!", russian: "Верь крепче и греши смелее!")]),
SectionDataModel(letter: "H", quotes: [
QuoteDataModel(latin: "Homo sine religione sicut equus sine freno.", russian: "Человек без религии что лошадь без удил."),
QuoteDataModel(latin: "Habet et musca splenem.", russian: "Разозлиться может и муха.")]),
SectionDataModel(letter: "M", quotes: [
QuoteDataModel(latin: "Malum est mulier, sed necessarium malum.", russian: "Хоть женщина и зло, но зло необходимое."),
QuoteDataModel(latin: "Mulierem ornat silentium.", russian: "Женщину красит молчанье.")])]
}
struct HeaderView_Previews: PreviewProvider {
static var previews: some View {
HeaderView(section: UserData().latinities[0])
}
}
struct QuoteView_Previews: PreviewProvider {
static var previews: some View {
QuoteView(quote: UserData().latinities[0].quotes[0])
}
}
@EnvironmentObject var userData : UserData
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView().environmentObject(UserData())
}
}
window.rootViewController = UIHostingController(rootView: contentView)
window.rootViewController = UIHostingController(rootView: contentView.environmentObject(UserData()))
func sectionIndex(section : SectionDataModel) -> Int {
userData.latinities.firstIndex(where: {$0.letter == section.letter})!
}
func quoteIndex(section : Int, quote : QuoteDataModel) -> Int {
return userData.latinities[section].quotes.firstIndex(where: {$0.latin == quote.latin})!
}
var body: some View {
List {
ForEach(userData.latinities) { section in
Section(header: HeaderView(section: section)
.onTapGesture {
self.userData.latinities[self.sectionIndex(section: section)].expanded.toggle()
}, footer: EmptyView()) {
if section.expanded {
ForEach(section.quotes) { quote in
QuoteView(quote: quote)
.onTapGesture {
let sectionIndex = self.sectionIndex(section: section)
let quoteIndex = self.quoteIndex(section: sectionIndex, quote: quote)
self.userData.latinities[sectionIndex].quotes[quoteIndex].expanded.toggle()
}
}
}
}
}
}
.listStyle(GroupedListStyle())
}
К сожалению, не доступен сервер mySQL