{"id":303500,"date":"2020-05-13T15:00:12","date_gmt":"2020-05-13T15:00:12","guid":{"rendered":"http:\/\/savepearlharbor.com\/?p=303500"},"modified":"-0001-11-30T00:00:00","modified_gmt":"-0001-11-29T21:00:00","slug":"","status":"publish","type":"post","link":"https:\/\/savepearlharbor.com\/?p=303500","title":{"rendered":"\u041d\u0435\u0442\u0440\u0438\u0432\u0438\u0430\u043b\u044c\u043d\u044b\u0435 \u0430\u043d\u0438\u043c\u0430\u0446\u0438\u0438 \u0432 SwiftUI"},"content":{"rendered":"\n<div class=\"post__text post__text-html post__text_v1\" id=\"post-content-body\" data-io-article-url=\"https:\/\/habr.com\/ru\/post\/501790\/\">\n<h3 id=\"ideya\">\u0418\u0434\u0435\u044f<\/h3>\n<p>  <\/p>\n<p>\u041d\u0430 WWDC 2019 \u0431\u044b\u043b\u0430 \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u0430 SwiftUI \u2014 \u0442\u0435\u0445\u043d\u043e\u043b\u043e\u0433\u0438\u044f \u043a\u043e\u0440\u0435\u043d\u043d\u044b\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c \u0432\u043b\u0438\u044f\u044e\u0449\u0430\u044f \u043d\u0430 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0435 UI \u0432 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f\u0445 \u0434\u043b\u044f \u044d\u043a\u043e\u0441\u0438\u0441\u0442\u0435\u043c\u044b Apple. \u041d\u0430\u043c \u0432 <a href=\"http:\/\/distillery.com\" rel=\"nofollow\">Distillery<\/a> \u0441\u0442\u0430\u043b\u043e \u0438\u043d\u0442\u0435\u0440\u0435\u0441\u043d\u043e \u0432 \u043d\u0435\u0439 \u0440\u0430\u0437\u043e\u0431\u0440\u0430\u0442\u044c\u0441\u044f \u0447\u0443\u0442\u044c \u0433\u043b\u0443\u0431\u0436\u0435, \u0447\u0435\u043c \u044d\u0442\u043e \u043f\u043e\u0434\u0430\u043d\u043e \u0432 \u043f\u0440\u0438\u043c\u0435\u0440\u0430\u0445 \u043e\u0442 Apple. \u0412 \u0438\u0434\u0435\u0430\u043b\u0435 \u043d\u0443\u0436\u043d\u043e \u0431\u044b\u043b\u043e \u0437\u0430\u043f\u0438\u043b\u0438\u0442\u044c \u043a\u0430\u043a\u043e\u0439-\u043d\u0438\u0431\u0443\u0434\u044c \u043f\u043e\u043b\u0435\u0437\u043d\u044b\u0439 \u0434\u043b\u044f iOS \u043a\u043e\u043c\u0430\u043d\u0434\u044b \u0438 \u0441\u043e\u043e\u0431\u0449\u0435\u0441\u0442\u0432\u0430 UI \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442. \u0421 \u0438\u0434\u0435\u044f\u043c\u0438 \u043f\u043e \u044d\u0442\u043e\u043c\u0443 \u043f\u043e\u0432\u043e\u0434\u0443 \u043e\u043a\u0430\u0437\u0430\u043b\u043e\u0441\u044c \u0442\u0443\u0433\u043e, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u0440\u0435\u0448\u0438\u043b\u0438 \u043f\u0438\u043b\u0438\u0442\u044c \u0447\u0442\u043e-\u0442\u043e \u043f\u0440\u043e\u0441\u0442\u043e \u0437\u0430\u0431\u0430\u0432\u043d\u043e\u0435. \u0412\u0434\u043e\u0445\u043d\u043e\u0432\u0438\u043b <a href=\"https:\/\/dribbble.com\/shots\/3824870-Loading-Animation-for-iPhone-X\" rel=\"nofollow\">\u0432\u043e\u0442 \u044d\u0442\u043e\u0442<\/a> \u043a\u043e\u043d\u0446\u0435\u043f\u0442:<\/p>\n<p>  <\/p>\n<p><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/post_images\/88b\/017\/2e3\/88b0172e300e0c0d700f0ce74a68aa4d.gif\" alt=\"image\"><\/p>\n<p>  <\/p>\n<p>\u041e\u0441\u043e\u0431\u0435\u043d\u043d\u043e \u0438\u043d\u0442\u0435\u0440\u0435\u0441\u043d\u044b\u043c \u043f\u043e\u043a\u0430\u0437\u0430\u043b\u043e\u0441\u044c \u043e\u0431\u0438\u043b\u0438\u0435 \u043d\u0435\u0442\u0440\u0438\u0432\u0438\u0430\u043b\u044c\u043d\u043e\u0439 \u0430\u043d\u0438\u043c\u0430\u0446\u0438\u0438. \u0422\u0430\u043a\u0438\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c, \u043f\u043e \u0445\u043e\u0434\u0443 \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u0445\u043e\u0442\u0435\u043b\u043e\u0441\u044c \u043f\u0440\u043e\u0432\u0435\u0440\u0438\u0442\u044c, \u043d\u0430\u0441\u043a\u043e\u043b\u044c\u043a\u043e SwiftUI \u0443\u0434\u043e\u0431\u0435\u043d \u0438 \u043f\u0440\u0438\u0441\u043f\u043e\u0441\u043e\u0431\u043b\u0435\u043d \u0434\u043b\u044f \u0447\u0435\u0433\u043e-\u0442\u043e \u0431\u043e\u043b\u0435\u0435 \u0441\u043b\u043e\u0436\u043d\u043e\u0433\u043e, \u0447\u0435\u043c \u043f\u043e\u0447\u0442\u0438 \u0441\u0442\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u0439 UI \u0438\u0437 \u043f\u0440\u0438\u043c\u0435\u0440\u043e\u0432 WWDC 2019.<\/p>\n<p><a name=\"habracut\"><\/a>  <\/p>\n<h3 id=\"rezultat\">\u0420\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442<\/h3>\n<p>  <\/p>\n<p>\u041f\u043e\u043a\u0430\u0436\u0443 \u0441\u0440\u0430\u0437\u0443, \u0447\u0442\u043e \u043f\u043e\u043b\u0443\u0447\u0438\u043b\u043e\u0441\u044c:<\/p>\n<p>  <\/p>\n<p><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/post_images\/7a7\/cbf\/7a0\/7a7cbf7a05561123ceff4be47fff5ed6.gif\"><\/p>\n<p>  <\/p>\n<p>\u0414\u043e\u0441\u0442\u0443\u043f\u043d\u043e \u043d\u0430 <a href=\"https:\/\/github.com\/DistilleryTech\/RainbowBar\" rel=\"nofollow\">\u0433\u0438\u0442\u0445\u0430\u0431\u0435<\/a> \u0438 \u0432 <a href=\"https:\/\/cocoapods.org\/pods\/RainbowBar\" rel=\"nofollow\">CocoaPods<\/a>.<\/p>\n<p>  <\/p>\n<h3 id=\"ispolzovanie\">\u0418\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435<\/h3>\n<p>  <\/p>\n<p>\u0412 \u043f\u0430\u043f\u043a\u0435 Example \u0435\u0441\u0442\u044c \u043f\u0440\u0438\u043c\u0435\u0440. \u0412\u043e\u0442 \u043a\u0430\u043a \u0432 \u0444\u0430\u0439\u043b\u0435 ExampleView.swift \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f RainbowBar:<\/p>\n<p>  <\/p>\n<pre><code class=\"plaintext\">RainbowBar(waveEmitPeriod: 0.3,            visibleWavesCount: 3,            waveColors: [.red, .green, .blue],            backgroundColor: .white,            animated: animatedSignal) {                 self.running = false }<\/code><\/pre>\n<p>  <\/p>\n<p>\u042d\u0442\u043e \u043f\u043e\u0447\u0442\u0438 \u043c\u0438\u043d\u0438\u043c\u0430\u043b\u044c\u043d\u044b\u0439 \u043d\u0430\u0431\u043e\u0440 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u043e\u0432, \u043a\u0440\u043e\u043c\u0435 \u0437\u0430\u043a\u0440\u044b\u0432\u0430\u044e\u0449\u0435\u0433\u043e \u0437\u0430\u043c\u044b\u043a\u0430\u043d\u0438\u044f, \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u044b\u0439 \u0434\u043b\u044f \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u044f RainbowBar.<\/p>\n<p>  <\/p>\n<p>\u041a\u0440\u0430\u0442\u043a\u043e\u0435 \u043e\u043f\u0438\u0441\u0430\u043d\u0438\u0435 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u043e\u0432:<\/p>\n<p>  <\/p>\n<p><strong><code>waveEmitPeriod<\/code><\/strong> \u2014 \u043f\u0440\u043e\u043c\u0435\u0436\u0443\u0442\u043e\u043a \u0432\u0440\u0435\u043c\u0435\u043d\u0438 \u0432 \u0441\u0435\u043a\u0443\u043d\u0434\u0430\u0445, \u0447\u0435\u0440\u0435\u0437 \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0431\u0443\u0434\u0435\u0442 \u043f\u043e\u044f\u0432\u043b\u044f\u0442\u044c\u0441\u044f \u043d\u043e\u0432\u0430\u044f \u0432\u043e\u043b\u043d\u0430 \u0438\u0437 \u0446\u0435\u043d\u0442\u0440\u0430 \u0431\u0430\u0440\u0430.<\/p>\n<p>  <\/p>\n<p><strong><code>visibleWavesCount<\/code><\/strong> \u2014 \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u0432\u043e\u043b\u043d, \u043e\u0434\u043d\u043e\u0432\u0440\u0435\u043c\u0435\u043d\u043d\u043e \u0432\u0438\u0434\u0438\u043c\u044b\u0445 \u043d\u0430 \u043a\u0430\u0436\u0434\u043e\u0439 \u0438\u0437 \u043f\u043e\u043b\u043e\u0432\u0438\u043d\u043e\u043a \u0431\u0430\u0440\u0430.<\/p>\n<p>  <\/p>\n<p><strong><code>waveColors<\/code><\/strong> \u2014 \u043c\u0430\u0441\u0441\u0438\u0432 \u0446\u0432\u0435\u0442\u043e\u0432, \u0438\u0437 \u043a\u043e\u0442\u043e\u0440\u043e\u0433\u043e \u0431\u0443\u0434\u0435\u0442 \u0431\u0440\u0430\u0442\u044c\u0441\u044f \u0446\u0432\u0435\u0442 \u043a\u0430\u0436\u0434\u043e\u0439 \u043d\u043e\u0432\u043e\u0439 \u0432\u043e\u043b\u043d\u044b \u043f\u043e\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u043d\u043e. \u041f\u043e\u0441\u043b\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u044f \u0432\u0441\u0435\u0445 \u0446\u0432\u0435\u0442\u043e\u0432 \u0438\u0437 \u044d\u0442\u043e\u0433\u043e \u043c\u0430\u0441\u0441\u0438\u0432\u0430 \u043e\u043d \u0431\u0443\u0434\u0435\u0442 \u043f\u0435\u0440\u0435\u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d \u0437\u0430\u043d\u043e\u0432\u043e. \u041a\u0430\u043a \u043a\u043e\u043b\u044c\u0446\u0435\u0432\u043e\u0439 \u0431\u0443\u0444\u0435\u0440.<\/p>\n<p>  <\/p>\n<p><strong><code>backgroundColor<\/code><\/strong> \u2014 \u0444\u0438\u043d\u0430\u043b\u044c\u043d\u044b\u0439 \u0446\u0432\u0435\u0442 \u0437\u0430\u043c\u044b\u043a\u0430\u044e\u0449\u0435\u0439 \u0433\u0440\u0430\u0434\u0438\u0435\u043d\u0442\u043d\u043e\u0439 \u0432\u043e\u043b\u043d\u044b. \u041a \u0441\u043e\u0436\u0430\u043b\u0435\u043d\u0438\u044e, \u043f\u043e\u043a\u0430 \u0431\u0435\u0437 \u043f\u0440\u043e\u0437\u0440\u0430\u0447\u043d\u043e\u0441\u0442\u0438) \u041f\u043e\u0434\u0440\u0430\u0437\u0443\u043c\u0435\u0432\u0430\u0435\u0442\u0441\u044f, \u0447\u0442\u043e \u0444\u043e\u043d \u043f\u043e\u0434 \u0431\u0430\u0440\u043e\u043c \u0431\u0443\u0434\u0435\u0442 \u0438\u043c\u0435\u0442\u044c \u043e\u0434\u043d\u043e\u0440\u043e\u0434\u043d\u044b\u0439 \u0446\u0432\u0435\u0442, \u0438 \u044d\u0442\u043e\u0442 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440 \u0431\u0443\u0434\u0435\u0442 \u0440\u0430\u0432\u043d\u044f\u0442\u044c\u0441\u044f \u0435\u043c\u0443.<\/p>\n<p>  <\/p>\n<p><strong><code>animated<\/code><\/strong> \u2014 Combine-\u0441\u0438\u0433\u043d\u0430\u043b \u0434\u043b\u044f \u0437\u0430\u043f\u0443\u0441\u043a\u0430 \u0438 \u043e\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0438 \u0430\u043d\u0438\u043c\u0430\u0446\u0438\u0438 \u0442\u0438\u043f\u0430 <code>PassthroughSubject&lt;Bool, Never&gt;()<\/code><\/p>\n<p>  <\/p>\n<p><strong><code>completion<\/code><\/strong> \u0438\u043b\u0438 \u0437\u0430\u043a\u0440\u044b\u0432\u0430\u044e\u0449\u0435\u0435 \u0437\u0430\u043c\u044b\u043a\u0430\u043d\u0438\u0435 \u0431\u0443\u0434\u0435\u0442 \u0432\u044b\u0437\u0432\u0430\u043d\u043e \u043f\u043e\u0441\u043b\u0435 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u0438\u044f \u0444\u0438\u043d\u0430\u043b\u044c\u043d\u043e\u0439 \u0430\u043d\u0438\u043c\u0430\u0446\u0438\u0438. \u0422\u043e \u0435\u0441\u0442\u044c \u043a\u043e\u0433\u0434\u0430 \u0433\u0440\u0430\u0434\u0438\u0435\u043d\u0442\u043d\u0430\u044f \u0432\u043e\u043b\u043d\u0430 \u0437\u0430\u043a\u043e\u043d\u0447\u0438\u0442 \u0441\u0432\u043e\u0439 \u0445\u043e\u0434 \u043e\u0442 \u0446\u0435\u043d\u0442\u0440\u0430 \u043a \u043a\u0440\u0430\u044e \u044d\u043a\u0440\u0430\u043d\u0430.<\/p>\n<p>  <\/p>\n<p>\u041d\u0435\u0431\u043e\u043b\u044c\u0448\u0430\u044f \u043c\u0430\u0433\u0438\u044f \u0441\u043e\u0441\u0442\u043e\u0438\u0442 \u0432 \u0442\u043e\u043c, \u0447\u0442\u043e \u0431\u0430\u0440 \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438 \u043d\u0430\u0441\u0442\u0440\u0430\u0438\u0432\u0430\u0435\u0442 \u0441\u0432\u043e\u044e \u0432\u044b\u0441\u043e\u0442\u0443, \u043a\u0440\u0438\u0432\u0438\u0437\u043d\u0443 \u0432\u043e\u043b\u043d \u0438 \u0440\u0430\u0441\u0441\u0442\u043e\u044f\u043d\u0438\u0435 \u043c\u0435\u0436\u0434\u0443 \u043f\u043e\u043b\u043e\u0432\u0438\u043d\u043a\u0430\u043c\u0438 \u0432 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0438 \u043e\u0442 \u0434\u0435\u0432\u0430\u0439\u0441\u0430, \u043d\u0430 \u043a\u043e\u0442\u043e\u0440\u043e\u043c \u0437\u0430\u043f\u0443\u0449\u0435\u043d, \u0432 \u0442\u043e\u043c \u0447\u0438\u0441\u043b\u0435 \u0438 \u043d\u0430 \u0441\u0438\u043c\u0443\u043b\u044f\u0442\u043e\u0440\u0430\u0445. \u041e\u0441\u043e\u0431\u0435\u043d\u043d\u043e \u0445\u043e\u0440\u043e\u0448\u043e \u044d\u0442\u043e \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442, \u0435\u0441\u043b\u0438 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0431\u0430\u0440 \u043f\u0440\u043e\u0441\u0442\u043e \u0432 \u0441\u0430\u043c\u043e\u043c \u0432\u0432\u0435\u0440\u0445\u0443 \u2014 \u043a\u0430\u043a \u0432 \u043f\u0440\u0438\u043c\u0435\u0440\u0435. \u041e\u0442\u0432\u0435\u0447\u0430\u0435\u0442 \u0437\u0430 \u044d\u0442\u043e \u0441\u0442\u043e\u0440\u043e\u043d\u044f\u044f \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0430 <a href=\"https:\/\/github.com\/devicekit\/DeviceKit\" rel=\"nofollow\">DeviceKit<\/a>. \u0412 \u0441\u043b\u0443\u0447\u0430\u0435 \u0432\u044b\u0445\u043e\u0434\u0430 \u043d\u043e\u0432\u044b\u0445 \u0434\u0435\u0432\u0430\u0439\u0441\u043e\u0432, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043d\u0435 \u0431\u0443\u0434\u0443\u0442 \u0434\u0435\u0442\u0435\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c\u0441\u044f, \u0432\u0441\u0435 \u0432\u044b\u0448\u0435\u043f\u0435\u0440\u0435\u0447\u0438\u0441\u043b\u0435\u043d\u043d\u044b\u0435 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b \u043c\u043e\u0436\u043d\u043e \u0432\u044b\u0441\u0442\u0430\u0432\u0438\u0442\u044c \u0432\u0440\u0443\u0447\u043d\u0443\u044e.<\/p>\n<p>  <\/p>\n<h3 id=\"realizaciya\">\u0420\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f<\/h3>\n<p>  <\/p>\n<p>\u0421\u0430\u043c \u0431\u0430\u0440 \u0441\u043e\u0441\u0442\u043e\u0438\u0442 \u0438\u0437 \u0434\u0432\u0443\u0445 \u0438\u0434\u0435\u043d\u0442\u0438\u0447\u043d\u044b\u0445 \u043f\u043e\u043b\u043e\u0432\u0438\u043d\u043e\u043a WavesView. \u041e\u0434\u043d\u0430 \u0438\u0437 \u043d\u0438\u0445 \u0440\u0430\u0437\u0432\u0435\u0440\u043d\u0443\u0442\u0430 \u0432\u043e\u043a\u0440\u0443\u0433 \u0432\u0435\u0440\u0442\u0438\u043a\u0430\u043b\u044c\u043d\u043e\u0439 \u043e\u0441\u0438 Y \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e <code>.rotation3DEffect(.degrees(180), axis: (x: 0, y: 1, z: 0), anchor: .center)<\/code>. \u041c\u0435\u0436\u0434\u0443 \u043d\u0438\u043c\u0438 \u043b\u0435\u0436\u0438\u0442 Spacer \u0441 \u0448\u0438\u0440\u0438\u043d\u043e\u0439 centerSpacing. \u0412\u0441\u0451 \u044d\u0442\u043e \u043d\u0430\u0445\u043e\u0434\u0438\u0442\u0441\u044f \u0432\u043d\u0443\u0442\u0440\u0438 HStack \u0441 \u0432\u044b\u0441\u043e\u0442\u043e\u0439 height:<\/p>\n<p>  <\/p>\n<pre><code class=\"plaintext\">public var body: some View {     HStack {         WavesView(waveEmitPeriod: waveEmitPeriod,                   visibleWavesCount: visibleWavesCount,                   waveColors: waveColors,                   backgroundColor: backgroundColor,                   topCornerRadius: waveTopCornerRadius,                   bottomCornerRadius: waveBottomCornerRadius,                   animatedSignal: animated,                   completion: completion)             .rotation3DEffect(.degrees(180), axis: (x: 0, y: 1, z: 0), anchor: .center)         Spacer().frame(width: centerSpacing)         WavesView(waveEmitPeriod: waveEmitPeriod,                   visibleWavesCount: visibleWavesCount,                   waveColors: waveColors,                   backgroundColor: backgroundColor,                   topCornerRadius: waveTopCornerRadius,                   bottomCornerRadius: waveBottomCornerRadius,                   animatedSignal: animated,                   completion: nil)     }.frame(height: height) }<\/code><\/pre>\n<p>  <\/p>\n<p>WavesView \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f ZStack`\u043e\u043c WaveView<\/p>\n<p>  <\/p>\n<pre><code class=\"plaintext\">ZStack {     ForEach(waveNodes) { node in         WaveView(animationDuration: self.animationDuration,                  animationFinished: self.waveFinished,                  node: node,                  topCornerRadius: self.topCornerRadius,                  bottomCornerRadius: self.bottomCornerRadius)     } }<\/code><\/pre>\n<p>  <\/p>\n<p>\u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0441\u0442\u0440\u043e\u0438\u0442\u0441\u044f \u0438\u0437 \u043c\u0430\u0441\u0441\u0438\u0432\u0430 \u043c\u043e\u0434\u0435\u043b\u0435\u0439 \u0432\u043e\u043b\u043d <code>@State private var waveNodes = [WaveNode]()<\/code>. \u0418\u0437\u043c\u0435\u043d\u044f\u0435\u0442\u0441\u044f \u043e\u043d \u043f\u0440\u0438 \u0441\u0442\u0430\u0440\u0442\u0435\/\u0441\u0442\u043e\u043f\u0435 \u0430\u043d\u0438\u043c\u0430\u0446\u0438\u0438 \u0432 \u0441\u0435\u0442\u0442\u0435\u0440\u0435 animatedInnerState:<\/p>\n<p>  <\/p>\n<pre><code class=\"plaintext\">@State private var animatedInnerState: Bool = false {     didSet {         if animatedInnerState {             var res = [NotchWaveNode]()             for index in 0..&lt;visibleWavesCount {                 guard let color = self.colorEmitter.nextColor(from: self.waveColors) else { continue }                 let newNode = NotchWaveNode(color: color,                                             delay: waveEmitPeriod * Double(index))                 res.append(newNode)             }             waveNodes = res         } else {             waveNodes.removeAll {                 !$0.started             }             if let lastVisibleNode = waveNodes.last as? NotchWaveNode {                 let gradientNode = GradientWaveNode(frontColor: lastVisibleNode.color,                                                     backColor: backgroundColor,                                                     animationDuration: animationDuration,                                                     delay: 0,                                                     animationFinished: self.waveFinished)                 waveNodes.append(gradientNode)             }         }     } }<\/code><\/pre>\n<p>  <\/p>\n<p>\u0438 \u043f\u0440\u0438 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u0438\u0438 \u0432\u043e\u043b\u043d\u044b:<\/p>\n<p>  <\/p>\n<pre><code class=\"plaintext\">onReceive(waveFinished) { node in     if node is GradientWaveNode, let completion = self.completion {         DispatchQueue.main.async {             completion()         }         return     }      \/\/ remove invisible (lower, first) node?     if self.waveNodes.count &gt; 0 {         var removeFirstNode = false         if self.waveNodes.count &gt; 1 {             removeFirstNode = self.waveNodes[1].finished         }         if removeFirstNode {             self.waveNodes.removeFirst()         }     }      \/\/add new color (node)     if self.animatedInnerState, let color = self.colorEmitter.nextColor(from: self.waveColors) {         let newNode = NotchWaveNode(color: color, delay: 0)         self.waveNodes.append(newNode)     } }<\/code><\/pre>\n<p>  <\/p>\n<p>\u041e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0435 \u043c\u043e\u0434\u0435\u043b\u0438 (\u043d\u043e\u0434\u044b) \u043d\u0443\u0436\u043d\u044b \u0434\u043b\u044f \u043d\u0430\u0434\u0451\u0436\u043d\u043e\u0433\u043e \u0445\u0440\u0430\u043d\u0435\u043d\u0438\u044f \u0438 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u044f. \u0416\u0438\u0437\u043d\u0435\u043d\u043d\u044b\u0439 \u0446\u0438\u043a\u043b \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c\u044b\u0445 \u0432 SwiftUI \u043d\u0435\u0442\u0440\u0438\u0432\u0438\u0430\u043b\u0435\u043d \u0438 \u043d\u0435\u0443\u0434\u043e\u0431\u0435\u043d \u2014 \u043d\u0435\u0442 \u044f\u0432\u043d\u043e\u0433\u043e \u0434\u0435\u0441\u0442\u0440\u0443\u043a\u0442\u043e\u0440\u0430. \u041a \u0442\u043e\u043c\u0443 \u0436\u0435 \u0438\u0437\u043c\u0435\u043d\u044f\u0442\u044c \u043f\u043e\u043b\u044f \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u044b \u0432 body \u043d\u0435\u043b\u044c\u0437\u044f, \u043a\u0440\u043e\u043c\u0435 \u043f\u043e\u043b\u0435\u0439, \u043f\u043e\u043c\u0435\u0447\u0435\u043d\u043d\u044b\u0445 <code>@State<\/code>.<\/p>\n<p>  <\/p>\n<p>\u041f\u0435\u0440\u0432\u044b\u0435 visibleWavesCount \u0432\u043e\u043b\u043d \u0438\u043c\u0435\u044e\u0442 \u0437\u0430\u0434\u0435\u0440\u0436\u043a\u0438 \u0430\u043d\u0438\u043c\u0430\u0446\u0438\u0438 (node.delay), \u043e\u0442\u043b\u0438\u0447\u043d\u044b\u0435 \u043e\u0442 \u043d\u0443\u043b\u044f. \u041e\u0441\u0442\u0430\u043b\u044c\u043d\u044b\u0435 \u0436\u0435, \u0447\u0442\u043e \u0431\u0443\u0434\u0443\u0442 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u044b \u0443\u0436\u0435 \u043f\u043e\u0441\u043b\u0435 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u0438\u044f \u043f\u0435\u0440\u0432\u043e\u0439 \u0432\u043e\u043b\u043d\u044b, \u0431\u0443\u0434\u0443\u0442 \u0438\u043c\u0435\u0442\u044c \u043d\u0443\u043b\u0435\u0432\u0443\u044e \u0437\u0430\u0434\u0435\u0440\u0436\u043a\u0443. \u041e\u0441\u043e\u0431\u043e \u0441\u0442\u043e\u0438\u0442 \u043e\u0442\u043c\u0435\u0442\u0438\u0442\u044c drawingGroup, \u043f\u0440\u0438\u043c\u0435\u043d\u044f\u0435\u043c\u044b\u0439 \u043a ZStack \u2014 \u044d\u0442\u043e \u0443\u0441\u043a\u043e\u0440\u044f\u0435\u0442 \u0440\u0435\u043d\u0434\u0435\u0440\u0438\u043d\u0433 \u043c\u043d\u043e\u0436\u0435\u0441\u0442\u0432\u0430 \u0432\u044c\u044e\u0448\u0435\u043a.<\/p>\n<p>  <\/p>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u0440\u0430\u0441\u0441\u043c\u043e\u0442\u0440\u0438\u043c WaveView, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0433\u0435\u043d\u0435\u0440\u0438\u043b\u0438\u0441\u044c \u0434\u043b\u044f \u043a\u0430\u0436\u0434\u043e\u0439 \u043d\u043e\u0434\u044b:<\/p>\n<p>  <\/p>\n<pre><code class=\"plaintext\">func makeWave(from node: WaveNode) -&gt; some View {     let phase: CGFloat = self.animated ? 1.0 : 0.0     if let notchNode = node as? NotchWaveNode {         return AnyView(NotchWave(phase: phase,                                  animationFinished: self.animationFinished,                                  node: notchNode,                                  topCornerRadius: topCornerRadius,                                  bottomCornerRadius: bottomCornerRadius).foregroundColor(notchNode.color))     } else if let gradientNode = node as? GradientWaveNode {         return AnyView(GradientWave(phase: phase,                                     frontColor: gradientNode.frontColor,                                     backColor: gradientNode.backColor,                                     node: gradientNode,                                     minWidth: topCornerRadius + bottomCornerRadius))     } else {         return AnyView(EmptyView())     } }  var body: some View {     return makeWave(from: node).animation(Animation.easeIn(duration: animationDuration).delay(node.delay)).onAppear {         self.animated.toggle()     } }<\/code><\/pre>\n<p>  <\/p>\n<p>\u041f\u043e \u0441\u0443\u0442\u0438 \u044d\u0442\u043e \u043f\u0440\u043e\u0441\u0442\u043e &quot;\u0430\u043d\u0438\u043c\u0430\u0446\u0438\u043e\u043d\u043d\u0430\u044f \u043f\u0440\u043e\u043a\u043b\u0430\u0434\u043a\u0430&quot; \u0434\u043b\u044f \u043a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u044b\u0445 (NotchWave, GradientWave) \u0432\u044c\u044e\u0448\u0435\u043a, \u043d\u043e \u044d\u0442\u043e \u043d\u0435\u0442\u043e\u0447\u043d\u043e, \u043d\u043e\u0434. \u0421\u0430\u043c\u043e\u0435 \u0432\u0430\u0436\u043d\u043e\u0435 \u0442\u0443\u0442 \u2014 \u044d\u0442\u043e \u0441\u0442\u0430\u0440\u0442 \u0430\u043d\u0438\u043c\u0430\u0446\u0438\u0438 \u043f\u0440\u0438 \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0438 \u0432\u044c\u044e\u0448\u043a\u0438. easeIn \u0434\u0435\u043b\u0430\u0435\u0442 \u0434\u0432\u0438\u0436\u0435\u043d\u0438\u0435 \u0432\u043e\u043b\u043d \u0443\u0441\u043a\u043e\u0440\u044f\u0449\u0438\u043c\u0441\u044f \u0431\u043b\u0438\u0436\u0435 \u043a \u043a\u0440\u0430\u044f\u043c \u044d\u043a\u0440\u0430\u043d\u0430.<\/p>\n<p>  <\/p>\n<p>\u041e\u043a\u0430\u0437\u044b\u0432\u0430\u0435\u0442\u0441\u044f, \u0447\u0442\u043e NotchWave \u2014 \u044d\u0442\u043e \u043d\u0435 \u0432\u044c\u044e\u0448\u043a\u0430 (\u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0442\u043e\u0440 \u043f\u0440\u043e\u0442\u043e\u043a\u043e\u043b\u0430 View), \u0430 Shape \u2014 \u0442\u043e\u0447\u043d\u0435\u0435 \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0442\u043e\u0440 \u044d\u0442\u043e\u0433\u043e \u043f\u0440\u043e\u0442\u043e\u043a\u043e\u043b\u0430.<\/p>\n<p>  <\/p>\n<pre><code class=\"plaintext\">struct NotchWave: Shape {     var phase: CGFloat     var animationFinished: AnimationSignal     var node: NotchWaveNode     var topCornerRadius, bottomCornerRadius: CGFloat      var animatableData: CGFloat {         get { return phase }         set { phase = newValue }     }      func path(in rect: CGRect) -&gt; Path {         if !self.node.started &amp;&amp; self.phase &gt; 0.0 {             self.node.started = true         }          DispatchQueue.main.async {             if self.phase &gt;= 1.0 {                 self.node.finished = true                 self.animationFinished.send(self.node)             }         }          var p = Path()          p.move(to: CGPoint.zero)          let currentWidth = 2 * (topCornerRadius + bottomCornerRadius) + rect.size.width * phase         p.addLine(to: CGPoint(x: currentWidth, y: 0))          let topArcCenter = CGPoint(x: currentWidth, y: topCornerRadius)         p.addArc(center: topArcCenter, radius: topCornerRadius, startAngle: .degrees(270), endAngle: .degrees(180), clockwise: true)          let height = rect.size.height         p.addLine(to: CGPoint(x: currentWidth - topCornerRadius, y: height - bottomCornerRadius))          let bottomArcCenter = CGPoint(x: currentWidth - topCornerRadius - bottomCornerRadius, y: height - bottomCornerRadius)         p.addArc(center: bottomArcCenter, radius: bottomCornerRadius, startAngle: .degrees(0), endAngle: .degrees(90), clockwise: false)          p.addLine(to: CGPoint(x: 0, y: height))          p.closeSubpath()          return p     } }<\/code><\/pre>\n<p>  <\/p>\n<p>\u0412 animatableData \u0443\u043a\u0430\u0437\u0430\u043d \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440 phase, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0431\u0443\u0434\u0435\u0442 \u0430\u043d\u0438\u043c\u0438\u0440\u043e\u0432\u0430\u0442\u044c\u0441\u044f \u0438\u0437\u0432\u043d\u0435. \u0415\u0441\u043b\u0438 \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0438\u0442\u044c \u0435\u0433\u043e \u043a\u0430\u043a <code>@State<\/code>, \u0447\u0442\u043e \u043b\u043e\u0433\u0438\u0447\u043d\u043e, \u0442\u043e \u0430\u043d\u0438\u043c\u0430\u0446\u0438\u044f \u043d\u0435 \u0431\u0443\u0434\u0435\u0442 \u0440\u0430\u0431\u043e\u0442\u0430\u0442\u044c.<\/p>\n<p>  <\/p>\n<p>\u041e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u0438\u044f \u043e\u0442\u0440\u0438\u0441\u043e\u0432\u043a\u0438 \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u043d \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e GCD \u0438 \u0440\u0430\u0441\u0441\u0447\u0438\u0442\u0430\u043d \u043d\u0430 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0435 \u0432 \u0433\u043b\u0430\u0432\u043d\u043e\u043c \u043f\u043e\u0442\u043e\u043a\u0435, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u0434\u043b\u044f \u043e\u0442\u0440\u0438\u0441\u043e\u0432\u043a\u0438 UI, \u0441\u0440\u0430\u0437\u0443 (\u0438\u043b\u0438 \u043f\u043e\u0447\u0442\u0438) \u043f\u043e\u0441\u043b\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f \u0444\u0443\u043d\u043a\u0446\u0438\u0438 path. \u041a \u0441\u043e\u0436\u0430\u043b\u0435\u043d\u0438\u044e, \u043f\u043e\u043a\u0430 \u0432 SwiftUI \u043d\u0435\u0442 \u0440\u043e\u0434\u043d\u043e\u0433\u043e \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u0430 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u0438\u044f \u0430\u043d\u0438\u043c\u0430\u0446\u0438\u0438, \u043f\u043e\u0434\u043e\u0431\u043d\u043e\u0433\u043e completion \u0432 animateWithDuration \u0438\u0437 UIKit: <\/p>\n<p>  <\/p>\n<pre><code class=\"plaintext\">DispatchQueue.main.async {     if self.phase &gt;= 1.0 {         self.node.finished = true         self.animationFinished.send(self.node)     } }<\/code><\/pre>\n<p>  <\/p>\n<p>\u041f\u043e\u043c\u0438\u043c\u043e \u0446\u0432\u0435\u0442\u043e\u0432\u043e\u0439 \u0432\u043e\u043b\u043d\u044b \u0435\u0441\u0442\u044c \u0442\u0430\u043a\u0436\u0435 \u0437\u0430\u0432\u0435\u0440\u0448\u0430\u044e\u0449\u0430\u044f \u0433\u0440\u0430\u0434\u0438\u0435\u043d\u0442\u043d\u0430\u044f \u0432\u043e\u043b\u043d\u0430. \u041e\u043d\u0430 \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u043d\u0430 \u0438\u043d\u0430\u0447\u0435 \u2014 \u0440\u0435\u0430\u043b\u0438\u0437\u0443\u0435\u0442 \u043f\u0440\u043e\u0442\u043e\u043a\u043e\u043b View:<\/p>\n<p>  <\/p>\n<pre><code class=\"plaintext\">struct GradientWave: View {     var phase: CGFloat     var frontColor, backColor: Color     var node: GradientWaveNode     var minWidth: CGFloat      var body: some View {         if self.phase == 0 {             node.startAnimationTimer()         }          return GeometryReader { geometry in             HStack(spacing: 0) {                 Rectangle().foregroundColor(self.backColor).frame(width: (geometry.size.width + self.minWidth) * self.phase)                  Rectangle().fill(LinearGradient(gradient: Gradient(colors: [self.backColor, self.frontColor]), startPoint: .leading, endPoint: .trailing)).frame(width: self.minWidth)                  Spacer()             }         }     } }<\/code><\/pre>\n<p>  <\/p>\n<p>\u0413\u0440\u0430\u0434\u0438\u0435\u043d\u0442\u043d\u0430\u044f \u0432\u043e\u043b\u043d\u0430 \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u0442 \u0441\u043e\u0431\u043e\u0439 \u0433\u043e\u0440\u0438\u0437\u043e\u043d\u0442\u0430\u043b\u044c\u043d\u044b\u0439 \u0441\u0442\u0435\u043a: \u043f\u0440\u044f\u043c\u043e\u0443\u0433\u043e\u043b\u044c\u043d\u0438\u043a\u0430 \u0446\u0432\u0435\u0442\u0430 backgroundColor, \u043b\u0438\u043d\u0435\u0439\u043d\u043e\u0433\u043e \u0433\u0440\u0430\u0434\u0438\u0435\u043d\u0442\u0430 \u043e\u0442 backgroundColor \u0434\u043e \u0446\u0432\u0435\u0442\u0430 \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0435\u0439 \u0432\u0438\u0434\u0438\u043c\u043e\u0439 \u0432\u043e\u043b\u043d\u044b NotchWave. \u0412 \u043e\u0442\u043b\u0438\u0447\u0438\u0435 \u043e\u0442 <code>path(in rect: CGRect) -&gt; Path<\/code> \u0432 NotchWave \u0442\u0443\u0442 \u043c\u043e\u0436\u043d\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c GeometryReader \u0438 \u0431\u0440\u0430\u0442\u044c \u0448\u0438\u0440\u0438\u043d\u0443 \u0438\u0437 \u043d\u0435\u0433\u043e.<\/p>\n<p>  <\/p>\n<p>\u041e\u0431\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u0442\u044c \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u0438\u0435 \u0430\u043d\u0438\u043c\u0430\u0446\u0438\u0438 \u043a\u0430\u043a NotchWave \u043d\u0435 \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u0441\u044f, \u0442\u0430\u043a \u043a\u0430\u043a <code>var body: some View<\/code> \u0432\u044b\u0437\u044b\u0432\u0430\u0435\u0442\u0441\u044f \u0441\u0440\u0430\u0437\u0443 \u0434\u0432\u0430 \u0440\u0430\u0437\u0430 \u0441 \u043a\u0440\u0430\u0439\u043d\u0438\u043c\u0438 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f\u043c\u0438 \u0430\u043d\u0438\u043c\u0438\u0440\u0443\u0435\u043c\u043e\u0433\u043e phase. \u041f\u043e\u044d\u0442\u043e\u043c\u0443 \u0442\u0443\u0442 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f <code>startAnimationTimer()<\/code> \u0438\u0437 <code>GradientWaveNode<\/code>:<\/p>\n<p>  <\/p>\n<pre><code class=\"plaintext\">class GradientWaveNode: WaveNode {     let frontColor, backColor: Color     let animationDuration: Double     let animationFinished: AnimationSignal      private var timer: Timer?      func startAnimationTimer() {         self.timer = Timer.scheduledTimer(withTimeInterval: animationDuration, repeats: false) { _ in             self.animationFinished.send(self)         }     }      init(frontColor: Color, backColor: Color, animationDuration: Double, delay: Double, animationFinished: AnimationSignal) {         self.frontColor = frontColor         self.backColor = backColor         self.animationDuration = animationDuration         self.animationFinished = animationFinished          super.init(delay: delay)     }      deinit {         timer?.invalidate()     } }<\/code><\/pre>\n<p>  <\/p>\n<p>\u0442\u043e \u0435\u0441\u0442\u044c \u0441\u0438\u0433\u043d\u0430\u043b \u043e \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u0438\u0438 \u0430\u043d\u0438\u043c\u0430\u0446\u0438\u0438 \u043f\u043e\u0441\u044b\u043b\u0430\u0435\u0442 \u043f\u0440\u043e\u0441\u0442\u043e \u0442\u0430\u0439\u043c\u0435\u0440 \u043f\u043e \u043f\u0440\u043e\u0448\u0435\u0441\u0442\u0432\u0438\u044e \u0432\u0440\u0435\u043c\u0435\u043d\u0438, \u0440\u0430\u0432\u043d\u043e\u043c\u0443 \u0434\u043b\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u0438 \u0430\u043d\u0438\u043c\u0430\u0446\u0438\u0438. \u041d\u0435 \u0441\u0430\u043c\u044b\u0439 \u0442\u043e\u0447\u043d\u044b\u0439 \u0438 \u043f\u0440\u043e\u0441\u0442\u043e\u0439 \u0441\u043f\u043e\u0441\u043e\u0431. \u041d\u043e, \u043a\u0430\u043a \u0433\u043e\u0432\u043e\u0440\u0438\u043b\u043e\u0441\u044c \u0432\u044b\u0448\u0435, \u043f\u043e\u043a\u0430 \u0432 SwiftUI \u043d\u0435\u0442 \u0440\u043e\u0434\u043d\u043e\u0433\u043e \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u0430 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u0438\u044f \u0430\u043d\u0438\u043c\u0430\u0446\u0438\u0438(<\/p>\n<p>  <\/p>\n<p>\u041d\u043e\u0434\u0430 \u0446\u0432\u0435\u0442\u043e\u0432\u043e\u0439 \u0432\u043e\u043b\u043d\u044b \u043f\u0440\u043e\u0441\u0442\u0430 \u0438 \u0438\u043d\u0442\u0435\u0440\u0435\u0441\u0430 \u043d\u0435 \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u0442:<\/p>\n<p>  <\/p>\n<pre><code class=\"plaintext\">class NotchWaveNode: WaveNode {     let color: Color      init(color: Color, delay: Double) {         self.color = color         super.init(delay: delay)     } }<\/code><\/pre>\n<p>  <\/p>\n<p>\u0411\u0430\u0437\u043e\u0432\u044b\u0439 \u043a\u043b\u0430\u0441\u0441 \u043d\u043e\u0434:<\/p>\n<p>  <\/p>\n<pre><code class=\"plaintext\">class WaveNode: Identifiable {     let id = UUID()     let delay: Double      var started: Bool = false     var finished: Bool = false      init(delay: Double) {         self.delay = delay     } }<\/code><\/pre>\n<p>  <\/p>\n<p>\u041e\u043d \u0438\u043d\u0442\u0435\u0440\u0435\u0441\u0435\u043d \u0440\u0430\u0437\u0432\u0435 \u0447\u0442\u043e \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0435\u0439 \u043f\u0440\u043e\u0442\u043e\u043a\u043e\u043b\u0430 Identifiable, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c \u0434\u043b\u044f \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u044f \u043c\u0430\u0441\u0441\u0438\u0432\u0430 \u043d\u043e\u0434 \u0432 ForEach. \u0412 \u0441\u043b\u0443\u0447\u0430\u0435 \u0432\u0441\u0442\u0440\u043e\u0435\u043d\u043d\u044b\u0445 \u0442\u0438\u043f\u043e\u0432 \u0434\u043b\u044f ForEach \u043f\u0440\u043e\u0449\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440 <code>id: \\.self<\/code>. \u0412 \u043d\u0430\u0448\u0435\u043c \u0436\u0435 \u0441\u043b\u0443\u0447\u0430\u0435, \u0441\u043e \u0441\u0432\u043e\u0438\u043c \u043a\u043b\u0430\u0441\u0441\u043e\u043c, \u044d\u0442\u043e \u043f\u043e\u0442\u0440\u0435\u0431\u043e\u0432\u0430\u043b\u043e \u0431\u044b \u043e\u0442 \u043d\u0435\u0433\u043e \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0438\u044f \u043f\u0440\u043e\u0442\u043e\u043a\u043e\u043b\u0443 Hashable. \u0427\u0442\u043e \u0441\u0440\u0430\u0432\u043d\u0438\u043c\u043e \u043f\u043e \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u0443 \u043a\u043e\u0434\u0430 \u0441 \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u043d\u043d\u044b\u043c \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0438\u0435\u043c Identifiable \u0438 \u043c\u0435\u043d\u0435\u0435 \u044d\u043b\u0435\u0433\u0430\u043d\u0442\u043d\u043e.<\/p>\n<p>  <\/p>\n<p>\u041f\u0435\u0440\u0435\u0431\u043e\u0440 \u0446\u0432\u0435\u0442\u043e\u0432 \u0434\u043b\u044f \u043d\u043e\u0432\u044b\u0445 \u0432\u043e\u043b\u043d \u0432\u044b\u043d\u0435\u0441\u0435\u043d \u0432 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0439 \u043a\u043b\u0430\u0441\u0441 ColorEmitter, \u0444\u0430\u043a\u0442\u0438\u0447\u0435\u0441\u043a\u0438 \u0434\u0435\u043b\u0430\u044e\u0449\u0438\u0439 \u0438\u0437 \u043c\u0430\u0441\u0441\u0438\u0432\u0430 \u0446\u0432\u0435\u0442\u043e\u0432 \u043a\u043e\u043b\u044c\u0446\u0435\u0432\u043e\u0439 \u0431\u0443\u0444\u0435\u0440. \u0421\u0434\u0435\u043b\u0430\u043d\u043e \u044d\u0442\u043e, \u043f\u043e\u0442\u043e\u043c\u0443 \u0447\u0442\u043e \u0432 SwiftUI \u043d\u0435\u043b\u044c\u0437\u044f \u0431\u0435\u0437 <a href=\"https:\/\/habr.com\/ru\/users\/state\/\" class=\"user_link\">State<\/a> \u043c\u0435\u043d\u044f\u0442\u044c \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435 \u0438\u0437 body:<\/p>\n<p>  <\/p>\n<pre><code class=\"plaintext\">class ColorEmitter {     var colors, refColors: [Color]?      func nextColor(from newColors: [Color]) -&gt; Color? {         if !(refColors?.elementsEqual(newColors) ?? false) {             colors = newColors             refColors = newColors         }          let res = colors?.removeFirst()         if let res = res {             colors?.append(res)         }         return res     } }<\/code><\/pre>\n<p>  <\/p>\n<h3 id=\"zaklyuchenie\">\u0417\u0430\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435<\/h3>\n<p>  <\/p>\n<p>\u0412 \u0446\u0435\u043b\u043e\u043c, \u0441\u0435\u0439\u0447\u0430\u0441 SwiftUI \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u0438\u0442 \u0432\u043f\u0435\u0447\u0430\u0442\u043b\u0435\u043d\u0438\u0435 \u043d\u0435\u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u043d\u043e\u0441\u0442\u0438. \u041a\u0430\u043a \u0438 Combine \u043f\u043e \u0441\u0440\u0430\u0432\u043d\u0435\u043d\u0438\u044e \u0441 \u0434\u0440\u0443\u0433\u0438\u043c\u0438 rx-\u0444\u0440\u0435\u0439\u043c\u0432\u043e\u0440\u043a\u0430\u043c\u0438. \u041e\u043d \u0445\u043e\u0440\u043e\u0448 \u0434\u043b\u044f \u0432\u0441\u0435\u0433\u043e, \u0447\u0442\u043e \u0440\u0430\u043d\u044c\u0448\u0435 \u0434\u0435\u043b\u0430\u043b\u043e\u0441\u044c \u0432 Interface builder: \u0440\u0430\u0437\u043c\u0435\u0442\u043a\u0438 \u0441\u0442\u0430\u043d\u0434\u0430\u0440\u0442\u043d\u043e\u0433\u043e UI \u0438 \u043d\u0435 \u0441\u0438\u043b\u044c\u043d\u043e \u0434\u0438\u043d\u0430\u043c\u0438\u0447\u0435\u0441\u043a\u043e\u0433\u043e \u0435\u0433\u043e \u043d\u0430\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f. \u041f\u043b\u044e\u0441 \u043d\u0435\u043c\u043d\u043e\u0433\u043e \u043c\u0430\u0433\u0438\u0438 \u0432 \u0432\u0438\u0434\u0435 \u043e\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0438\u044f \u043a\u043e\u043d\u0444\u043b\u0438\u043a\u0442\u043e\u0432 \u0440\u0430\u0437\u043c\u0435\u0442\u043a\u0438 \u0438 \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u043e\u0433\u043e dark mode. \u0414\u043b\u044f \u0431\u043e\u043b\u0435\u0435 \u0441\u043b\u043e\u0436\u043d\u043e\u0433\u043e \u0440\u0435\u043d\u0434\u0435\u0440\u0438\u043d\u0433\u0430 SwiftUI \u043d\u0435 \u0438\u043c\u0435\u0435\u0442 \u0434\u0430\u0436\u0435 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u0430 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u0438\u044f \u0430\u043d\u0438\u043c\u0430\u0446\u0438\u0438. \u041b\u0438\u0447\u043d\u043e \u044f \u0441 \u043d\u0435\u0442\u0435\u0440\u043f\u0435\u043d\u0438\u0435\u043c \u0436\u0434\u0443 \u043d\u043e\u0432\u0443\u044e \u0432\u0435\u0440\u0441\u0438\u044e. \u0418\u0437\u0443\u0447\u0438\u0442\u044c \u0438 \u043f\u043e\u043f\u0440\u043e\u0431\u043e\u0432\u0430\u0442\u044c \u0435\u0433\u043e (\u043a\u0430\u043a \u0438 Combine) \u0441\u043e\u0432\u0435\u0442\u0443\u044e \u0443\u0436\u0435 \u0441\u0435\u0439\u0447\u0430\u0441.<\/p>\n<p>  <\/p>\n<p><em>\u0418\u0441\u0445\u043e\u0434\u043d\u0438\u043a\u0438 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430 \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u044b <a href=\"https:\/\/github.com\/DistilleryTech\/RainbowBar\" rel=\"nofollow\">\u0437\u0434\u0435\u0441\u044c<\/a>.<br \/>  \u041f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c \u0432 \u0441\u0432\u043e\u0439 \u043f\u0440\u043e\u0435\u043a\u0442 \u043c\u043e\u0436\u043d\u043e \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e <a href=\"https:\/\/cocoapods.org\/pods\/RainbowBar\" rel=\"nofollow\">CocoaPods<\/a>.<br \/>  <\/em><\/p>\n<p>  <\/p>\n<p><em>\u0421\u0442\u0430\u0442\u044c\u044f \u043d\u0430\u043f\u0438\u0441\u0430\u043d\u0430 \u043c\u043e\u0438\u043c \u043a\u043e\u043b\u043b\u0435\u0433\u043e\u0439 <a href=\"https:\/\/www.linkedin.com\/in\/alexkubarev\/\" rel=\"nofollow\">\u0410\u043b\u0435\u043a\u0441\u0435\u0435\u043c \u041a\u0443\u0431\u0430\u0440\u0435\u0432\u044b\u043c<\/a> \u0438 \u043e\u043f\u0443\u0431\u043b\u0438\u043a\u043e\u0432\u0430\u043d\u0430 \u0434\u043b\u044f \u0441\u043e\u043e\u0431\u0449\u0435\u0441\u0442\u0432\u0430 \u043f\u043e \u0435\u0433\u043e \u043f\u0440\u043e\u0441\u044c\u0431\u0435.<\/em><\/p>\n<\/div>\n<p> \u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b \u0441\u0442\u0430\u0442\u044c\u0438 <a href=\"https:\/\/habr.com\/ru\/post\/501790\/\"> https:\/\/habr.com\/ru\/post\/501790\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"\n<div class=\"post__text post__text-html post__text_v1\" id=\"post-content-body\" data-io-article-url=\"https:\/\/habr.com\/ru\/post\/501790\/\">\n<h3 id=\"ideya\">\u0418\u0434\u0435\u044f<\/h3>\n<p>  <\/p>\n<p>\u041d\u0430 WWDC 2019 \u0431\u044b\u043b\u0430 \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u0430 SwiftUI \u2014 \u0442\u0435\u0445\u043d\u043e\u043b\u043e\u0433\u0438\u044f \u043a\u043e\u0440\u0435\u043d\u043d\u044b\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c \u0432\u043b\u0438\u044f\u044e\u0449\u0430\u044f \u043d\u0430 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0435 UI \u0432 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f\u0445 \u0434\u043b\u044f \u044d\u043a\u043e\u0441\u0438\u0441\u0442\u0435\u043c\u044b Apple. \u041d\u0430\u043c \u0432 <a href=\"http:\/\/distillery.com\" rel=\"nofollow\">Distillery<\/a> \u0441\u0442\u0430\u043b\u043e \u0438\u043d\u0442\u0435\u0440\u0435\u0441\u043d\u043e \u0432 \u043d\u0435\u0439 \u0440\u0430\u0437\u043e\u0431\u0440\u0430\u0442\u044c\u0441\u044f \u0447\u0443\u0442\u044c \u0433\u043b\u0443\u0431\u0436\u0435, \u0447\u0435\u043c \u044d\u0442\u043e \u043f\u043e\u0434\u0430\u043d\u043e \u0432 \u043f\u0440\u0438\u043c\u0435\u0440\u0430\u0445 \u043e\u0442 Apple. \u0412 \u0438\u0434\u0435\u0430\u043b\u0435 \u043d\u0443\u0436\u043d\u043e \u0431\u044b\u043b\u043e \u0437\u0430\u043f\u0438\u043b\u0438\u0442\u044c \u043a\u0430\u043a\u043e\u0439-\u043d\u0438\u0431\u0443\u0434\u044c \u043f\u043e\u043b\u0435\u0437\u043d\u044b\u0439 \u0434\u043b\u044f iOS \u043a\u043e\u043c\u0430\u043d\u0434\u044b \u0438 \u0441\u043e\u043e\u0431\u0449\u0435\u0441\u0442\u0432\u0430 UI \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442. \u0421 \u0438\u0434\u0435\u044f\u043c\u0438 \u043f\u043e \u044d\u0442\u043e\u043c\u0443 \u043f\u043e\u0432\u043e\u0434\u0443 \u043e\u043a\u0430\u0437\u0430\u043b\u043e\u0441\u044c \u0442\u0443\u0433\u043e, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u0440\u0435\u0448\u0438\u043b\u0438 \u043f\u0438\u043b\u0438\u0442\u044c \u0447\u0442\u043e-\u0442\u043e \u043f\u0440\u043e\u0441\u0442\u043e \u0437\u0430\u0431\u0430\u0432\u043d\u043e\u0435. \u0412\u0434\u043e\u0445\u043d\u043e\u0432\u0438\u043b <a href=\"https:\/\/dribbble.com\/shots\/3824870-Loading-Animation-for-iPhone-X\" rel=\"nofollow\">\u0432\u043e\u0442 \u044d\u0442\u043e\u0442<\/a> \u043a\u043e\u043d\u0446\u0435\u043f\u0442:<\/p>\n<p>  <\/p>\n<p><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/post_images\/88b\/017\/2e3\/88b0172e300e0c0d700f0ce74a68aa4d.gif\" alt=\"image\"><\/p>\n<p>  <\/p>\n<p>\u041e\u0441\u043e\u0431\u0435\u043d\u043d\u043e \u0438\u043d\u0442\u0435\u0440\u0435\u0441\u043d\u044b\u043c \u043f\u043e\u043a\u0430\u0437\u0430\u043b\u043e\u0441\u044c \u043e\u0431\u0438\u043b\u0438\u0435 \u043d\u0435\u0442\u0440\u0438\u0432\u0438\u0430\u043b\u044c\u043d\u043e\u0439 \u0430\u043d\u0438\u043c\u0430\u0446\u0438\u0438. \u0422\u0430\u043a\u0438\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c, \u043f\u043e \u0445\u043e\u0434\u0443 \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u0445\u043e\u0442\u0435\u043b\u043e\u0441\u044c \u043f\u0440\u043e\u0432\u0435\u0440\u0438\u0442\u044c, \u043d\u0430\u0441\u043a\u043e\u043b\u044c\u043a\u043e SwiftUI \u0443\u0434\u043e\u0431\u0435\u043d \u0438 \u043f\u0440\u0438\u0441\u043f\u043e\u0441\u043e\u0431\u043b\u0435\u043d \u0434\u043b\u044f \u0447\u0435\u0433\u043e-\u0442\u043e \u0431\u043e\u043b\u0435\u0435 \u0441\u043b\u043e\u0436\u043d\u043e\u0433\u043e, \u0447\u0435\u043c \u043f\u043e\u0447\u0442\u0438 \u0441\u0442\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u0439 UI \u0438\u0437 \u043f\u0440\u0438\u043c\u0435\u0440\u043e\u0432 WWDC 2019.<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[],"tags":[],"class_list":["post-303500","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/303500","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=303500"}],"version-history":[{"count":0,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/303500\/revisions"}],"wp:attachment":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=303500"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=303500"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=303500"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}