SwiftUI untuk tugas kompetitif Telegram Charts terakhir (Maret 2019): semuanya sederhana



Saya akan mulai dengan pernyataan bahwa aplikasi yang dibahas dalam artikel ini memerlukan Xcode 11 dan MacOS Catalina jika Anda ingin menggunakan Live Previews , dan Mojave jika Anda menggunakan simulator. Kode aplikasi ada di Github .

Tahun ini di WWDC 2019 , Apple mengumumkan SwiftUI , cara deklaratif baru untuk membangun antarmuka pengguna (UI) di semua perangkat Apple . Ini hampir merupakan keberangkatan yang sepenuhnya dari UIKit biasa, dan saya - seperti banyak pengembang lainnya - benar-benar ingin melihat alat baru ini beraksi.

Artikel ini menyajikan pengalaman penyelesaian dengan SwiftUI masalah yang kode dalam UIKit jauh lebih kompleks dan tidak dapat UIKit menurut pendapat saya dengan cara yang mudah dibaca.

Tugas ini terkait dengan kontes Telegram terakhir untuk Android , iOS dan pengembang JS , yang diadakan dari 10 Maret hingga 24 Maret 2019. Dalam kompetisi ini, tugas sederhana diusulkan untuk menampilkan secara grafis intensitas penggunaan sumber daya tertentu di Internet tergantung pada waktu berdasarkan data JSON . Sebagai pengembang iOS , Anda harus menggunakan Swift untuk mengirimkan kode yang ditulis dari awal ke kompetisi tanpa menggunakan pustaka khusus asing untuk merencanakan.

Tugas ini membutuhkan keterampilan untuk bekerja dengan kemampuan grafis dan animasi iOS: Core Graphics , Core Animation , Metal , OpenGL ES . Beberapa alat ini adalah alat pemrograman tingkat rendah yang tidak berorientasi objek. Pada dasarnya, di iOS tidak ada templat yang dapat diterima untuk menyelesaikan tugas-tugas grafis sepintas yang terlihat sepintas lalu. Oleh karena itu, setiap kontestan menciptakan animatornya sendiri ( Render ) berdasarkan Metal , CALayers , OpenGL , CADisplayLink . Ini menghasilkan banyak kode dari mana tidak mungkin untuk meminjam dan mengembangkan apa pun, karena ini adalah karya murni "hak cipta" yang hanya bisa dikembangkan oleh penulis. Namun, seharusnya tidak demikian.

Dan pada awal Juni di WWDC 2019 , SwifUI muncul - framework baru yang dikembangkan oleh Apple , ditulis dalam bahasa Swift dan dirancang untuk secara deskriptif menggambarkan antarmuka pengguna ( UI ) dalam kode. Anda menentukan subviews mana subviews diperlihatkan di View Anda, data apa yang menyebabkan subviews ini berubah, pengubah apa yang perlu Anda terapkan padanya, untuk menjadikannya posisi di tempat yang tepat, untuk memiliki ukuran dan gaya yang tepat. Elemen yang sama pentingnya SwiftUI adalah kontrol aliran data yang dapat dimodifikasi pengguna, yang pada gilirannya memperbarui UI .

Pada artikel ini saya ingin menunjukkan bagaimana tugas kontes Telegram di SwiftUI diselesaikan dengan cepat dan mudah. Selain itu, ini adalah proses yang sangat menarik.

Tugas


Aplikasi kompetitif harus secara bersamaan menampilkan 5 "set Grafik" di layar menggunakan data yang disediakan oleh Telegram . Untuk satu "set Grafik", UI sebagai berikut:



Di bagian atas ada "Zona grafik" dengan skala umum di sepanjang sumbu Y normal dengan tanda dan garis kotak horizontal. Di bawahnya adalah garis merayap dengan cap waktu sepanjang sumbu X sebagai tanggal.

Bahkan lebih rendah adalah apa yang disebut "peta mini" (seperti dalam Xcode 11 ), yaitu, "jendela" transparan yang mendefinisikan bagian dari periode waktu "Grafik" kami, yang disajikan secara lebih rinci di "zona Charts" atas. "Peta mini" ini tidak hanya dapat dipindahkan sepanjang sumbu X , tetapi juga lebarnya dapat diubah, yang mempengaruhi skala waktu di "area Grafik".

Dengan bantuan checkboxs dilukis dengan warna "Grafik" dan diberikan namanya, Anda dapat menolak untuk menunjukkan "Grafik" yang sesuai dengan warna ini di "Zona grafik".

Ada banyak "set Grafik", dalam contoh pengujian kami ada 5, misalnya, dan semuanya harus ditempatkan pada satu layar.

Dalam UI dirancang menggunakan SwiftUI tidak perlu tombol untuk beralih antara mode Dark dan Light , ini sudah dibangun ke dalam SwiftUI . Selain itu, SwiftUI lebih banyak opsi untuk menggabungkan "set Grafik" (yaitu, set layar yang disajikan di atas) daripada sekadar menggulir ke bawah tabel, dan kita akan melihat beberapa opsi yang sangat menarik ini.

Tapi pertama-tama, mari kita fokus pada menampilkan satu "set SwiftUI " yang akan SwiftUI buat ChartView :



SwiftUI memungkinkan Anda untuk membuat dan menguji UI kompleks dalam potongan-potongan kecil, dan kemudian sangat mudah untuk merakit potongan-potongan ini menjadi sebuah teka-teki. Kami akan melakukannya. ChartView kami ChartView sangat baik menjadi potongan-potongan kecil ini:

  • GraphsForChart - ini adalah grafik itu sendiri, dibangun untuk satu "set Grafik" tertentu. "Grafik" ditampilkan untuk rentang waktu yang dikendalikan oleh pengguna menggunakan RangeView "peta mini", yang akan disajikan di bawah ini.
  • YTickerView adalah sumbu Y dengan ketinggian dan kisi horizontal yang sesuai.
  • IndicatorView adalah indikator yang digerakkan pengguna secara horizontal yang memungkinkan Anda untuk melihat nilai-nilai "Grafik" dan waktu untuk posisi indikator yang sesuai pada sumbu waktu pada sumbu X
  • TickerView - "garis merayap" menunjukkan cap waktu pada sumbu X sebagai tanggal,
  • RangeView - "jendela" sementara, disesuaikan oleh pengguna menggunakan gerakan, untuk mengatur interval waktu untuk "Grafik",
  • CheckMarksView - berisi "tombol" yang diwarnai dengan warna "Charts" dan memungkinkan Anda untuk mengontrol keberadaan " ChartView " di ChartView .

ChartView pengguna dapat berinteraksi dengan ChartView dalam tiga cara:

1. mengontrol "peta mini" menggunakan gerakan DragGesture - itu dapat menggeser "jendela" sementara ke kanan dan kiri dan mengurangi / menambah ukurannya:



2. pindahkan indikator ke arah horisontal, menunjukkan nilai "Grafik" pada titik waktu tertentu:



3. sembunyikan / tampilkan "Charts" tertentu menggunakan tombol-tombol yang diwarnai dengan warna "Charts" dan terletak di bagian bawah ChartView :



Kami dapat menggabungkan berbagai "Set Bagan" (kami memiliki 5 di antaranya dalam data uji) dengan berbagai cara, misalnya, dengan menempatkan semuanya secara bersamaan di satu layar menggunakan daftar List (seperti tabel yang dapat digulir ke atas dan ke bawah):



atau menggunakan ScrollView dan tumpukan horisontal HStack dengan efek 3D:



... atau dalam bentuk ZStack "kartu" yang saling bertumpukan, urutannya dapat diubah: "kartu" atas dengan "satu set Grafik" dapat ditarik ke bawah cukup jauh untuk melihat kartu berikutnya, dan jika Anda terus menyeretnya ke bawah, maka itu " pergi "ke tempat terakhir di ZStack , dan" kartu "" ini selanjutnya ":



Dalam UI rumit ini - "tabel gulir", tumpukan horizontal dengan efek 3D , ZStack "kartu" saling bertumpukan satu sama lain - semua cara interaksi pengguna bekerja sepenuhnya: bergerak sepanjang garis waktu dan mengubah "skala" dari mini - map , indikator dan tombol sembunyikan tombol "Grafik".

Selanjutnya kami akan mempertimbangkan secara rinci desain UI ini menggunakan SwiftUI - dari elemen sederhana hingga komposisi mereka yang lebih kompleks. Tapi pertama-tama, mari kita pahami struktur data yang kita miliki.

Jadi, solusi untuk masalah kami dibagi menjadi beberapa tahap:

  • Unduh data dari file JSON dan sajikan dalam format "internal" yang nyaman
  • Buat UI untuk satu "set Grafik"
  • Gabungkan berbagai "set grafik"

Unduh data


Kami siap membantu, Telegram menyediakan data JSON yang berisi beberapa "set Grafik." Setiap " chart set" individu dari chart berisi beberapa "Bagan" (atau "Garis") dari chart.columns . Setiap "Grafik" ("Garis") memiliki tanda pada posisi 0 - "x" , "y0" , "y1" , "y2" , "y3" , diikuti oleh nilai waktu pada sumbu X ("x") , atau nilai "Graphics" ("Lines") ( "y0" , "y1" , "y2" , "y3" ) pada sumbu Y :



Kehadiran semua "Garis" di "set grafik" adalah opsional. Nilai untuk "kolom" x adalah cap waktu UNIX dalam milidetik.

Selain itu, masing-masing " chart set" chart dilengkapi dengan warna chart.colors dalam format 6 digit heksadesimal (misalnya, "#AAAAAA") dan chart.names .

Untuk membangun Model Data yang terletak di file JSON , saya menggunakan layanan tipe cepat yang sangat baik. Di situs ini, Anda menyisipkan sepotong teks dari file JSON dan menentukan bahasa pemrograman ( Swift ), nama struktur ( Chart ), yang akan dibentuk setelah "parsing" data JSON ini dan hanya itu.

Kode dihasilkan di bagian tengah layar, yang kami salin ke aplikasi kami dalam file terpisah bernama Chart.swift . Di sinilah kita akan menempatkan Model Data format JSON. Dengan menggunakan Loader data dari file JSON ke Model yang dipinjam dari demo SwiftUI Generic , saya mendapatkan array columns: [ChartElement] , yang merupakan kumpulan "Set columns: [ChartElement] " dalam format Telegram .

ChartElement data ChartElement , berisi array elemen heterogen, tidak terlalu cocok untuk pekerjaan interaktif intensif dengan grafik, selain itu, cap waktu disajikan dalam format UNIX dalam milidetik (misalnya, 1542412800000, 1542499200000, 1542585600000, 1542672000000 ), dan warna dalam 6 format heksadesimal digit (misalnya, "#AAAAAA" ).

Oleh karena itu, di dalam aplikasi kita akan menggunakan data yang sama, tetapi dalam format "internal" yang berbeda dan cukup sederhana [LinesSet] . [LinesSet] adalah kumpulan dari " LinesSet Set" LinesSet , yang masing-masing berisi cap waktu xTime dalam format "Feb 12, 2019" (sumbu X ) dan beberapa lines "Bagan" (sumbu Y ):



Data untuk setiap Garis Grafik (Garis) disajikan

  • array points: [Int] integer points: [Int] ,
  • bernama "Graphics" title: String ,
  • ketik "Graphics" type: String? ,
  • color : UIColor dalam format UIColor Swift - UIColor ,
  • jumlah poin countY: Int .

Selain itu, "Grafik" apa pun dapat disembunyikan atau ditampilkan tergantung pada nilai isHidden: Bool . upperBound dan upperBound menyesuaikan rentang waktu mengambil nilai dari 0 hingga 1 dan tidak hanya menampilkan ukuran jendela waktu "mini map" ( upperBound - lowerBound ), tetapi juga lokasinya pada sumbu waktu X :



Struktur data JSON [ChartElement] dan struktur data LinesSet dan Line "internal" ada di file Chart.swift . Kode untuk memuat data JSON dan mengubahnya menjadi struktur internal terletak di file Data.swift . Rincian tentang transformasi ini dapat ditemukan di sini .

Sebagai hasilnya, kami menerima data tentang "set grafik" dalam format internal sebagai array dari chartsData .



Ini adalah Data kami, tetapi untuk bekerja di SwiftUI , Anda perlu memastikan bahwa setiap perubahan yang dibuat oleh pengguna dalam array chartsData (mengubah "jendela" sementara, menyembunyikan / menampilkan "Charts") mengarah pada pembaruan otomatis Views kami.

Kami akan membuat @EnvironmentObject . Ini akan memungkinkan kami untuk menggunakan Data di mana pun itu diperlukan, dan di samping itu, secara otomatis memperbarui Views kami jika data berubah. Ini adalah sesuatu seperti Singleton atau data global.

@EnvironmentObject mengharuskan kita untuk membuat beberapa final class UserData , yang terletak di file UserData.swift , menyimpan data chartsData dan mengimplementasikan protokol ObservableObject :



Kehadiran "pembungkus" yang telah @Published pada @Published akan memungkinkan Anda memposting "berita" bahwa sifat-sifat charts dari kelas UserData telah berubah, sehingga Views apa pun "berlangganan berita ini" di SwiftUI akan dapat secara otomatis memilih data baru dan memperbarui.

Ingatlah bahwa di properti charts nilai isHidden untuk " isHidden " apa pun dapat berubah (mereka memungkinkan Anda untuk menyembunyikan atau menampilkan "Charts" ini), serta batas waktu batas bawah dan batas atas yang lebih rendah untuk setiap "set Grafik" masing-masing individu.

Kami ingin menggunakan properti charts dari kelas UserData seluruh aplikasi kami dan kami tidak harus menyinkronkannya dengan UI secara manual berkat @EnvironmentObject .

Untuk melakukan ini, ketika memulai aplikasi, kita harus membuat turunan dari kelas UserData () sehingga selanjutnya kita dapat mengaksesnya di mana saja di aplikasi kita. Kami akan melakukan ini dalam file SceneDelegate.swift di dalam scene (_ : , willConnectTo: , options: ) metode. Di sinilah ContentView kami dibuat dan diluncurkan, dan di sinilah kami harus meneruskan ContentView @EnvironmentObject apa pun yang kami @EnvironmentObject sehingga SwiftUI dapat membuatnya tersedia untuk View lain:



Sekarang, dalam View apa pun View untuk mengakses data @Published dari kelas UserData , kita perlu membuat variabel var menggunakan pembungkus @EnvironmentObject . Misalnya, ketika mengatur rentang waktu di RangeView kami membuat variabel var userData dengan TYPE UserData :



Jadi, segera setelah kami menerapkan beberapa @EnvironmentObject ke dalam "lingkungan" aplikasi, kami dapat segera mulai menggunakannya baik pada level tertinggi atau level 10 di bawah - tidak masalah. Tetapi yang lebih penting, setiap kali View mengubah "lingkungan", semua Views yang memiliki @EnvironmentObject ini akan secara otomatis @EnvironmentObject , sehingga memastikan sinkronisasi dengan data.

Mari kita beralih ke merancang antarmuka pengguna ( UI ).

User Interface (UI) untuk satu "set Grafik"


SwiftUI menawarkan teknologi komposit untuk membuat SwiftUI dari banyak Views kecil, dan kami telah melihat bahwa aplikasi kami sangat cocok dengan teknologi ini, karena terbagi menjadi beberapa bagian: ChartView " ChartView Charts", " GraphsForChart Charts", Y -axis YTickerView - YTickerView , nilai indikator "Charts" yang digerakkan pengguna, TickerView " TickerView " dengan TickerView waktu pada sumbu X , RangeView "time window" yang RangeView , tanda pada persembunyian / menampilkan "Charts" CheckMarksView . Kami tidak hanya dapat membuat semua Views ini secara independen satu sama lain, tetapi juga segera menguji dalam Xcode 11 menggunakan Previews (tampilan "hidup" awal) pada data uji. Anda akan terkejut betapa sederhana kode ini untuk membuatnya dari Views lain yang lebih mendasar.

GraphView - "Graph" ("Line")


Pandangan pertama, yang akan kita mulai, sebenarnya adalah "Grafik" itu sendiri (atau "Garis"). Kami akan menyebutnya GraphView :



Membuat GraphView , seperti biasa, dimulai dengan membuat file baru di Xcode 11 menggunakan menu File → New → File :



Kemudian kami memilih JENIS file yang diinginkan - ini adalah file SwiftUI :



... berikan nama "GraphView" ke View kami dan tunjukkan lokasinya:



Klik tombol "Create" dan dapatkan View standar dengan Text ( "Hello World!") Di tengah layar:



Tugas kita adalah mengganti Text ("Hello World!") Dengan "Grafik", tetapi pertama-tama, mari kita lihat data awal apa yang kita miliki untuk membuat "Grafik":

  • kami memiliki nilai line.points "Graphics" line: Line ,
  • rentang waktu rangeTime , yang merupakan rentang indeks. Range prangko waktu xTime pada sumbu X,
  • rentang nilai rangeY: Range "Grafik" untuk rangeY: Range Y,
  • ketebalan garis garis "Grafik" garis lineWidth .

Tambahkan properti ini ke struktur GraphView :



Jika kita ingin menggunakan untuk Previews "Grafik" kami (preview), yang hanya mungkin untuk MacOS Catalyna , maka kita harus memulai GraphView dengan rentang indeks rangeTime dan data line "Grafik" itu sendiri:



Kami sudah memiliki data uji chartsData yang kami dapatkan dari file JSON chart.json , dan kami menggunakannya untuk Previews .

Dalam kasus kami, ini akan menjadi yang pertama " chartsData[0] set" chartsData[0] dan "Bagan" pertama dalam bagan ini chartsData[0].lines[0] , yang kami akan berikan GraphView sebagai parameter line .

Kami akan menggunakan rentang penuh indeks 0..<(chartsData[0].xTime.count - 1) sebagai interval waktu intervalTime.
lineWidth dan lineWidth dapat diatur secara eksternal, atau tidak diatur, karena mereka sudah memiliki nilai awal: rangeY adalah nil , dan lineWidth adalah 1 .

Kami sengaja membuat JENIS dari rangeY properti Optional JENIS, karena jika rangeY tidak diatur secara eksternal dan rangeY = nil , maka kami menghitung nilai minY minimum dan maksimum maksimum dari "Grafik" langsung dari data line.points :



Kode ini dikompilasi, tetapi kami masih memiliki View standar di layar dengan teks Text ("Hello World!") Di tengah layar:



Karena di dalam body kita harus mengganti Text ("Hello World!") Dengan Path , yang akan membangun "Grafik:




Kami akan melingkari stroke (...) Path kami Path garis yang ketebalannya adalah garis lineWidth , dan warna garis garis akan sesuai dengan warna default (yaitu, hitam):



Kita dapat mengganti warna hitam untuk garis goresan dengan warna yang ditentukan dalam line.color "Garis" khusus "Warna":



Agar "Grafik" kami ditempatkan dalam persegi empat ukuran apa pun, kami menggunakan wadah GeometryReader . Dalam dokumentasi Apple GeometryReader adalah tampilan “penampung” yang mendefinisikan kontennya sebagai fungsi dari ukurannya sendiri dan mengoordinasikan ruang. Pada dasarnya, GeometryReader adalah pandangan lain! Karena hampir SEMUA yang ada di SwiftUI adalah View ! GeometryReader akan memungkinkan ANDA, tidak seperti Views lainnya Views untuk mengakses beberapa informasi berguna tambahan yang dapat Anda gunakan saat mendesain View kustom Anda.

Kami menggunakan kontainer GeometryReader dan Path untuk membuat GraphView dapat disesuaikan dengan ukuran apa pun. Dan jika kita perhatikan dengan teliti kode kita, kita akan melihat pada penutupan untuk GeometryReader variabel yang disebut geometry :



Variabel ini memiliki GeometryProxy TYPE, yang pada gilirannya adalah struktur struct dengan banyak "kejutan":

 public var size: CGSize { get } public var safeAreaInsets: EdgeInsets { get } public func frame(in coordinateSpace: CoordinateSpace) -> CGRect public subscript<T>(anchor: Anchor<T>) -> T where T : Equatable { get } 

Dari definisi GeometryProxy , kita melihat bahwa ada dua variabel yang dihitung var size var safeAreaInsets dan var safeAreaInsets , satu frame( in:) fungsi frame( in:) dan subscript getter . Kami hanya membutuhkan variabel size untuk menentukan lebar geometry.size.width size Lebar dan tinggi geometry.size.height size Tinggi dari area gambar "Grafik".

Selain itu, kami mengaktifkan "Grafik" kami untuk beranimasi menggunakan pengubah animation (.linear(duration: 0.6)) .



GraphView_Previews memungkinkan kita untuk menguji "Grafik" dari "set" apa saja dengan sangat mudah. Di bawah ini adalah "Chart" dari "chart set" dengan indeks 4: chartsData[4] dan indeks 0 "Graphics" di set ini: chartsData[4].lines[0] :



Kami mengatur height "Grafik" ke 400 menggunakan frame (height: 400) , lebarnya tetap sama dengan lebar layar. Jika kami tidak menggunakan frame (height: 400) , maka "Grafik" akan menempati seluruh layar.Kami tidak menentukan rentang nilai rangeYdan GraphViewmenggunakan nilai nildefault, dalam hal ini "Bagan" mengambil nilai minimum dan maksimum dalam interval waktu rangeTime:



Meskipun kami menggunakan Pathpengubah untuk model kami animation (.linear(duration: 0.6)), tidak ada animasi yang akan terjadi, misalnya, ketika mengubah rentang rangeYnilai " Grafik. " "Bagan" hanya akan "melompat" dari satu nilai rentang rangeYke yang lain tanpa animasi.

Alasannya sederhana: kami mengajarkan SwiftUIcara menggambar "Grafik" untuk rentang tertentu rangeY, tetapi kami tidak mengajarkan SwiftUIcara mereproduksi "Grafik" beberapa kali dengan nilai menengah kisaran rangeYantara awal dan akhir, dan untuk itu dalamSwiftUImemenuhi protokol Animatable.

Untungnya, jika milik Anda Viewadalah "figur", yaitu View, yang mengimplementasikan protokol Shape, maka protokol sudah diterapkan untuk itu Animatable. Ini berarti bahwa ada properti yang dihitung animatableDatadengan mana kita dapat mengontrol proses animasi, tetapi secara default diatur ke EmptyAnimatableData, yaitu, tidak ada animasi terjadi.

Untuk menyelesaikan masalah dengan animasi, pertama-tama kita perlu mengubah "Grafik" kita GraphViewmenjadi Shape. Ini sangat sederhana, kita hanya perlu mengimplementasikan fungsi func path (in rect:CGRect) -> Pathyang pada dasarnya sudah kita miliki dan tunjukkan dengan bantuan properti yang dihitung animatableDatadata apa yang ingin kita animasikan:



Perhatikan bahwa tema kontrol animasi adalah topik lanjutan dalamSwiftUIdan Anda dapat mempelajari lebih lanjut di artikel “Animasi SwiftUI Tingkat Lanjut - Bagian 1: Jalur” . Kita dapat menggunakan

"gambar" yang dihasilkan Graphdalam GraphViewNew"Grafik" yang jauh lebih sederhana dengan animasi:



Anda tahu bahwa kita tidak perlu GeometryReaderuntuk "Grafik" baru kami GraphViewNew, karena berkat protokol Shape"angka" kami Graphakan dapat beradaptasi dengan ukuran induknya View.

Secara alami, Previewskami mendapatkan hasil yang sama seperti dalam kasus dengan GraphView:



Dalam kombinasi berikut, kami akan menggunakan GraphViewNew"Grafik" yang sama untuk menampilkan nilai.

GraphsForChart - set "Grafik" ("Garis")


Tugas ini Viewadalah untuk menampilkan SEMUA "Grafik" ("Garis") dari "set Grafik" chartdalam rentang waktu tertentu rangeTimedengan sumbu umum Y, dan lebar "Garis" sama dengan lineWidth:



Seperti untuk GraphViewdan GraphViewNew, kami akan membuat GraphsForChartfile baru untuk GraphsForChart.swiftdan menentukan data awal untuk "Set Grafik":

  • "set Charts" sendiri chart: LineSet(nilai aktif Y),
  • range rangeTime: Range( X) dari indeks cap waktu "Charts",
  • ketebalan garis garis grafik lineWidth

Rentang nilai rangeY: Rangeuntuk "set grafik" ( Y) dihitung sebagai gabungan dari rentang individu yang tidak ditunggangi ( isHidden = false) "Bagan" yang termasuk dalam "set" ini:



Untuk ini, kami menggunakan fungsi rangeOfRanges: Kami menampilkan



semua NOT charts tersembunyi ( isHidden = false) dalam ZStackkonstruksi ForEach, memberikan setiap "Grafik" kemungkinan muncul di layar dan meninggalkan layar "menggunakan pengubah" pindah " transition(.move(edge: .top)):



Berkat pengubah ini, proses menyembunyikan dan mengembalikan" Grafik " ChartViewakan dilakukan di layar dengan animasi dan akan menjelaskan kepada pengguna mengapa skala telah berubah Y.

Gunakan drawingGroup()berarti gunakanMetaluntuk menggambar bentuk grafik. Pada data pengujian kami dan pada simulator, Anda tidak akan merasakan perbedaan dalam kecepatan menggambar dengan Metaldan Metal, tetapi jika Anda mereproduksi banyak grafik yang agak besar iPhone, maka Anda akan melihat perbedaan ini. Untuk pengantar yang lebih rinci, kapan menggunakannya drawingGroup(), Anda dapat melihat artikel "Animasi SwiftUI Tingkat Lanjut - Bagian 1: Jalur" atau menonton sesi video 237 WWDC 2019 ( Membangun Tampilan Kustom dengan SwiftUI ).

Seperti halnya GraphViewNewpengujian GraphsForChartmenggunakan pratinjau, Previewskita dapat atur "set Grafik" apa pun, misalnya, dengan indeks 0:



IndicatorView - Indikator "Grafik" yang dipindahkan secara horizontal.


Indikator ini memungkinkan Anda untuk mendapatkan nilai yang tepat dari "Grafik" dan waktu untuk titik yang sesuai pada waktu di X:



Indikator ini dibuat untuk "set Grafik" tertentu chartdan terdiri dari pergerakan sepanjang XLINE vertikal dengan MARK di atasnya dalam bentuk "lingkaran" di tempat nilai "Grafik". "POSTER" kecil terlampir di bagian atas garis vertikal ini, yang berisi nilai numerik dari "Grafik" dan waktu.



Indikator meluncur oleh pengguna menggunakan gerakan DragGesture:



Kami menggunakan apa yang disebut eksekusi gerakan "inkremental". Alih-alih jarak terus menerus dari titik awal value.translation.width, kami akan onChangedterus menerima jarak dari tempat kami terakhir kali kami melakukan gerakan di pawang :value.translation.width - self.prevTranslation. Ini akan memberi kita pergerakan indikator yang mulus.

Untuk menguji indikator IndicatorViewdengan bantuan Previews"set Grafik" yang diberikan, chartkita dapat menarik Viewkonstruksi "Grafik" yang sudah jadi GraphsForChart:



Kita dapat mengatur rentang waktu yang lain, tetapi terkoordinasi satu sama lain untuk rangeTimeindikator IndicatorViewdan "Grafik" GraphsForChart. Ini akan memungkinkan kami untuk memastikan bahwa "lingkaran" yang menunjukkan nilai-nilai "Grafik" berada di tempat yang tepat.

TickerView- Xdengan tanda.


«» , X Y . X TickerMarkView . TickerMarkView View VStack , Path Text :



« » chart : LineSet TickerView rangeTime estimatedMarksNumber , :



«» ScrollView HStack, yang akan bergeser seiring perubahan rentang waktu rangeTime.

Pada TickerViewkami membentuk langkah stepdengan mana prangko waktu muncul TimeMarkView, berdasarkan rentang waktu tertentu rangeTimedan lebar layar widthRange...



... lalu pilih perangko waktu secara bertahap stepdari array chart.xTimemenggunakan indeks indexes.

Sebenarnya X- garis horizontal - kita akan meletakkan overlay...



... pada tumpukan horizontal HStack, dengan cap waktu TimeMarkView, yang kita maju dengan offset:



Selain itu, kita dapat mengatur warna X- itu sendiri colorXAxis, dan tanda - colorXMark:



YTickerView- Ydengan tanda dan kisi.


Yang ini Viewmenggambar Ydengan tanda digital YMarkView. Tanda itu sendiri YMarkViewsangat sederhana Viewdengan tumpukan vertikal VStackdi mana mereka ditempatkan Path(garis horizontal) dan Textdengan nomor:



Satu set tanda menyala Yuntuk "set Grafik" tertentu chartterbentuk di YTickerView. Rentang nilai rangeYdihitung sebagai gabungan dari rentang nilai semua "Bagan" yang termasuk dalam "set Bagan" ini menggunakan fungsi rangeOfRanges. Perkiraan jumlah tanda pada sumbu Y diatur oleh parameter estimatedMarksNumber:



YTickerViewkami memantau perubahan dalam kisaran nilai "Grafik" rangeY. Sebenarnya sumbu Y - garis vertikal - kita memaksakan overlaypada tanda kita ...



Selain itu, kita dapat mengatur warna sumbu Y itu sendiri colorYAxis, dan tanda - colorYMark:



RangeView - mengatur rentang waktu menggunakan "mini-map".


Bagian yang paling mengharukan dari antarmuka pengguna kami adalah mengatur rentang waktu ( lowerBound, upperBound) untuk menampilkan "set Charts":



RangeView- ini mini - mapmenyoroti bagian waktu tertentu untuk tujuan pertimbangan yang lebih terinci dari "set Charts" pada yang lain Views.

Seperti pada yang sebelumnya View, data awal RangeViewadalah:



  • "bagan set" itu sendiri chart: LineSet(nilai Y),
  • tinggi height "mini-map" RangeView,
  • lebar widthRange "mini-map" RangeView,
  • indentasi indent "mini-map" RangeView.

Tidak seperti yang lain yang dibahas di atas Views, kita harus mengubah DragGesturerentang waktu ( lowerBound, upperBound) dengan isyarat dan segera melihat perubahannya, sehingga rentang waktu yang ditentukan pengguna ( lowerBound, upperBound) yang dengannya kita akan bekerja disimpan dalam variabel variabel @EnvironmentObject var userData: UserData:



Setiap perubahan pada variabel var userDataakan menyebabkan menggambar ulang semua Viewstergantung padanya.

Karakter utama dalam RangeViewadalah "jendela" transparan, posisi dan ukurannya dikontrol oleh pengguna dengan gerakan DragGesture:

1. jika kita menggunakan gerakan di dalam "jendela" transparan, POSISI "jendela" berubah X, dan ukurannya tidak berubah:



2. jika kita menggunakan gerakan di bagian gelap yang gelap, maka hanya BORDER KIRI dari "jendela" yang berubah lowerBound, yang memungkinkan kita untuk mengurangi atau menambah lebar "jendela" transparan:



3. jika kita menggunakan gerakan di bagian yang gelap gelap, hanya BATU YANG TEPAT dari "jendela" yang berubah upperBound, memungkinkan Anda untuk mengurangi atau menambah lebar "jendela" transparan:



RangeViewterdiri dari 3 elemen dasar yang sangat sederhana: dua persegi panjang Rectangle ()dan gambar Image, yang perbatasannya ditentukan oleh properti lowerBounddan upperBounddari @EnvironmentObject var userData: UserDatadan disesuaikan menggunakan gerakan DragGesture:



Kami "overlay" ( overlay) yang akrab dengan konstruksi ini ( ) kami GraphsForChartViewdengan "Charts" dari "set Charts" yang diberikan chart:



, «» «».

«» ( ), lowerBound upperBound userData onChanged DragGesture Rectangle () Image ...



, , Views ( «», X , Y c hartView ):



View @EnvironmentObject userData: UserData , Previews , .environmentObject (UserData()) :



CheckMarksView — «» «».


CheckMarksViewitu adalah tumpukan horisontal HStackdengan baris checkBoxesuntuk mengalihkan properti dari isHiddenmasing-masing "Grafik" di "set Grafik" chart:



CheckBoxdalam proyek kami dapat diimplementasikan baik menggunakan tombol biasa Buttondan dipanggil CheckButton, atau menggunakan tombol simulasi SimulatedButton.



Tombol Buttonharus ditiru karena ketika menempatkan beberapa tombol ini di Listsalah satu yang terletak lebih tinggi dalam hierarki, mereka "menolak" untuk bekerja dengan benar. Ini adalah bug lama yang terjebak di Xcode 11 sejak beta 1 ke versi saat ini . Versi aplikasi saat ini menggunakan tombol simulasi SimulatedButton.

Baik tombol simulasi SimulatedButtondan tombol nyataCheckButton View « » — CheckBoxView . HStack , Tex Image :



, CheckBoxView @Binding var line: Line . isHidden « » CheckBoView :




CheckBoView SimulatedButton CheckButton $ line :




isHidden line SimulatedButton onTapGesture …



… CheckButton — action Button :



, SimulatedButton CheckButton @Bindingsebuah variabel var line: Line. Oleh karena itu, penggunaannya harus diterapkan $pada CheckMarksViewvariabel beralih userData.charts[self.chartIndex].lines[self.lineIndex(line: line)].isHidden, yang disimpan dalam sebuah variabel global variabel @EnvironmentObject var userData:



Kami telah terus terpakai dalam proyek saat ini CheckButtonpada kasus ini, jika Anda tiba-tiba Appleakan memperbaiki kesalahan ini. Selain itu, Anda dapat mencoba menggunakan CheckButtondi CheckMarksViewsebaliknya SimulatedButtondan pastikan bahwa itu tidak bekerja dalam kasus komposisi set, "satu set grafik ChartViewmenggunakan Listdi ListChartsView.

Karena kita Viewberisi variabel @EnvironmentObject var userData: UserData, untuk pratinjau Previews, kita harus menetapkan nilai awalnya dengan .environmentObject(UserData()):



Kombinasi berbagai macam Views.


SwiftUI- ini terutama kombinasi dari berbagai yang kecil Viewsmenjadi yang besar, dan yang besar Viewsmenjadi yang sangat besar, dll, seperti dalam permainan Lego. Sebagai SwiftUIada berbagai cara kombinasi seperti Views:

  • tumpukan vertikal VStack,
  • tumpukan horisontal HStack,
  • "Kedalaman" dari stack ZStack,
  • kelompok Group,
  • ScrollView ,
  • daftar List,
  • membentuk Form,
  • wadah penunjuk TabView
  • dll.

Kami memulai kombinasi kami dengan yang paling sederhana GraphsViewForChart, yang memberikan "set grafik" "faceless" GraphsForChartAXIS Y dan indikator bergerak sepanjang sumbu X menggunakan tumpukan "dalam" ZStack:



Kami menambahkan sebuah wadah ke wadah Previewsbaru kami untuk menampilkannya dalam mode menggunakan modifier . Kami melanjutkan kombinasi dan melampirkan ke "set grafik" yang diperoleh di atas dengan AXIS Y dan indikator, AXIS X dalam bentuk "garis yang berjalan", serta kontrol: rentang waktu dari "peta mini" dan tampilan "Grafik" beralih . Hasilnya, kami mendapatkan yang dinyatakan di atas , yang menampilkan "set Grafik" dan memungkinkan Anda untuk mengontrol tampilannya pada sumbu waktu:GraphsViewForChartNavigationViewDark.collorScheme(.dark)

RangeViewCheckMarksView

ChartView



Dalam hal ini, kami melakukan kombinasi menggunakan tumpukan vertikal VStack:



Sekarang kami akan mempertimbangkan 3 opsi untuk menggabungkan set ChartView "Chart Sets" yang sudah diterima:

  1. "Tabel yang dapat digulir" List,
  2. tumpukan horizontal HStackdengan efek 3D,
  3. ZStack "kartu" ditumpangkan

"Tabel yang dapat digulir"ListChartsView disusun menggunakan daftar List:



Tumpukan horizontal dengan efek 3D disusun menggunakan ScrollViewtumpukan horizontal HStackdan daftar dalam bentuk ForEach:



Dalam tampilan ini, semua cara interaksi pengguna bekerja sepenuhnya: bergerak di sepanjang garis waktu dan mengubah "skala" mini- map, indikator dan tombol sembunyikan tombol "Grafik".

ZStack "kartu" ditumpangkan.


CardView «»- « » X Y, : «mini — map» / . CardView ChartView , «» , , , ZStack « » cardBackgroundColor . , «» :



«» VStack , ZStack ForEach :



«», «3D-c» CardViewScalable , indexChatdan mereka bergeser sedikit secara vertikal.

Urutan “kartu berskala 3D” dapat diubah menggunakan urutan ( sequenced) gerakan LongPressGesturedan DragGesture, yang hanya bertindak pada “kartu” paling atas dengan indexChat == 0:



Anda dapat mengklik ( LongPress) di “kartu” atas dengan “set Charts”, dan kemudian menariknya ( Drag) ke bawah cukup jauh untuk melihat kartu berikutnya, dan jika Anda terus menyeretnya ke bawah, itu "pergi" ke tempat terakhir ZStack, dan "kartu" berikutnya muncul ke depan:



Selain itu, untuk "kartu" atas yang dapat kita terapkan TapGesture, yang akan bertindak bersama dengan gerakan LongPressGesturedan DragGesture:



Tapisyarat akan menunjukkan modal yang "set grafis" ChartViewdengan e manajemen ementami RangeViewdanCheckMarksView :



Aplikasi TabViewuntuk menggabungkan pada satu layar ketiga varian dari "set grafik" komposisi ChartView.





Kami memiliki 3 penanda dengan gambar Imagedan teks Text, tumpukan vertikal VStacktidak diperlukan untuk presentasi bersama mereka.

Mereka sesuai dengan 3 metode kami untuk menggabungkan "set Grafik" ChartViews:

  1. "Tabel yang dapat digulir" ListChartViews,
  2. tumpukan horizontal dengan efek 3D HStackChartViews,
  3. ZStack melapiskan "kartu" OverlayCardsViews.

Semua elemen interaksi pengguna: bergerak di sepanjang garis waktu dan mengubah "skala" dengan bantuan mini - map, indikator, dan tombol untuk menyembunyikan "Grafik". sepenuhnya bekerja dalam semua 3 kasus.

Kode ada di Github .

SwiftUI ...


Anda harus berkenalan dengan tutorial video, buku, dan blog:

Mang To , Mari Bangun Aplikasi Itu , serta deskripsi beberapa aplikasi SwiftUI ,
- buku gratis "SwiftUI dengan contoh" dan video www.hackingwithswift.com/quick-start/swiftui
- buku berbayar tetapi separuhnya dapat diunduh secara gratis www.bigmountainstudio.com/swiftui-views-book
- kursus 100 hari dengan SwiftUI www.hackingwithswift.com/articles/201/start-the-100-days-of-swiftui , yang dimulai sekarang dan akan berakhir pada 31 Desember 2019,
- hal-hal mengesankan di SwiftUI dilakukan di swiftui-lab.com
- Majid blog ,
- di pointFree.cowww.pointfree.co "marathon" dari posting tentang penggunaan Reducers di SwiftUI (super menarik)
adalah aplikasi MovieSwiftUI yang hebat yang telah meminjam beberapa ide.

Source: https://habr.com/ru/post/id468897/


All Articles