{"id":336860,"date":"2022-08-10T15:00:36","date_gmt":"2022-08-10T15:00:36","guid":{"rendered":"http:\/\/savepearlharbor.com\/?p=336860"},"modified":"-0001-11-30T00:00:00","modified_gmt":"-0001-11-29T21:00:00","slug":"","status":"publish","type":"post","link":"https:\/\/savepearlharbor.com\/?p=336860","title":{"rendered":"<span>\u0421\u043e\u0437\u0434\u0430\u0435\u043c \u043a\u043b\u0430\u0441\u0441\u043d\u044b\u0439 UI: iOS Shape Morphing<\/span>"},"content":{"rendered":"<div><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-2\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/c8e\/9bd\/1dd\/c8e9bd1dda19088826830ca628a65e63.png\" width=\"780\" height=\"439\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/c8e\/9bd\/1dd\/c8e9bd1dda19088826830ca628a65e63.png\"\/><figcaption><\/figcaption><\/figure>\n<p>\u0421\u0435\u0440\u0438\u044f, \u043f\u043e\u0441\u0432\u044f\u0449\u0435\u043d\u043d\u0430\u044f \u0432\u043e\u0441\u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044e \u043a\u043b\u0430\u0441\u0441\u043d\u044b\u0445 \u0434\u0435\u043c\u043e\u043a \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u0441\u043a\u043e\u0433\u043e \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0430, \u043d\u0430 \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u044f \u043d\u0430\u0442\u043a\u043d\u0443\u043b\u0441\u044f \u043d\u0430 \u043f\u0440\u043e\u0441\u0442\u043e\u0440\u0430\u0445 \u0438\u043d\u0442\u0435\u0440\u043d\u0435\u0442\u0430. \u0421\u0435\u0433\u043e\u0434\u043d\u044f \u043c\u044b \u0440\u0435\u0430\u043b\u0438\u0437\u0443\u0435\u043c \u043c\u043e\u0440\u0444\u0438\u043d\u0433 \u043c\u0435\u0436\u0434\u0443 \u0440\u0430\u0437\u043d\u044b\u043c\u0438 \u0444\u043e\u0440\u043c\u0430\u043c\u0438 \u0438\u043a\u043e\u043d\u043e\u043a \u0438 \u0440\u0430\u0437\u0431\u0435\u0440\u0435\u043c\u0441\u044f, \u0447\u0442\u043e \u0442\u0430\u043a\u043e\u0435 Metaballs.<\/p>\n<p>\u041d\u0430 \u0434\u043d\u044f\u0445 \u0432 \u0422\u0432\u0438\u0442\u0442\u0435\u0440\u0435 \u044f \u043d\u0430\u0442\u043a\u043d\u0443\u043b\u0441\u044f <a href=\"https:\/\/twitter.com\/DLX\/status\/1541101687267594252\">\u043d\u0430 \u0442\u0432\u0438\u0442, \u0434\u0435\u043c\u043e\u043d\u0441\u0442\u0440\u0438\u0440\u0443\u044e\u0449\u0438\u0439 \u043e\u0434\u043d\u0443 \u043a\u043b\u0430\u0441\u0441\u043d\u0443\u044e \u0442\u0435\u0445\u043d\u0438\u043a\u0443 \u0432 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u0441\u043a\u043e\u043c \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0435<\/a>, \u0438 \u0437\u0430\u0438\u043d\u0442\u0435\u0440\u0435\u0441\u043e\u0432\u0430\u043b\u0441\u044f, \u043a\u0430\u043a \u043e\u043d\u0430 \u0431\u044b\u043b\u0430 \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u043d\u0430.<\/p>\n<p>\u0423\u0432\u0438\u0434\u0435\u0432 \u044d\u0442\u043e\u0442 \u0442\u0432\u0438\u0442, \u044f \u0432\u0441\u043f\u043e\u043c\u043d\u0438\u043b \u043e\u0431 \u0438\u0434\u0435\u0435, \u0432\u0435\u0440\u0442\u0435\u0432\u0448\u0435\u0439\u0441\u044f \u0443 \u043c\u0435\u043d\u044f \u0432 \u0433\u043e\u043b\u043e\u0432\u0435 \u043d\u0430 \u043f\u0440\u043e\u0442\u044f\u0436\u0435\u043d\u0438\u0438 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u0438\u0445 \u043b\u0435\u0442. \u0418\u0434\u0435\u044f \u0437\u0430\u043a\u043b\u044e\u0447\u0430\u043b\u0430\u0441\u044c \u0432 \u0442\u043e\u043c, \u0447\u0442\u043e\u0431\u044b \u043d\u0430\u043f\u0438\u0441\u0430\u0442\u044c \u0441\u0435\u0440\u0438\u044e \u0441\u0442\u0430\u0442\u0435\u0439, \u043f\u043e\u0441\u0432\u044f\u0449\u0435\u043d\u043d\u044b\u0445 \u0432\u043e\u0441\u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044e \u0440\u0430\u0437\u043b\u0438\u0447\u043d\u044b\u0445 \u043a\u043b\u0430\u0441\u0441\u043d\u044b\u0445 \u0448\u0442\u0443\u043a \u0438\u0437 \u043a\u043e\u043d\u0446\u0435\u043f\u0442\u0443\u0430\u043b\u044c\u043d\u044b\u0445 \u0432\u0438\u0434\u0435\u043e, \u043f\u043e\u0441\u0442\u043e\u0432 \u043d\u0430 Dribbble \u0438\u043b\u0438 \u043d\u0435\u043e\u0431\u044b\u0447\u043d\u044b\u0445 \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u043e\u0432 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u0441\u043a\u043e\u0433\u043e \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0430 \u0438\u0437 \u0434\u0440\u0443\u0433\u0438\u0445 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0439.<\/p>\n<p>\u0418\u0442\u0430\u043a, \u0432 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u043f\u0435\u0440\u0432\u043e\u0439 \u0441\u0442\u0430\u0442\u044c\u0438 \u0438\u0437 (\u044f \u043d\u0430\u0434\u0435\u044e\u0441\u044c) \u0431\u043e\u043b\u044c\u0448\u043e\u0439 \u0441\u0435\u0440\u0438\u0438, \u0434\u0430\u0432\u0430\u0439\u0442\u0435 \u0440\u0430\u0437\u0431\u0435\u0440\u0435\u043c\u0441\u044f, \u043a\u0430\u043a \u0441\u043e\u0437\u0434\u0430\u0442\u044c \u043a\u043b\u0430\u0441\u0441\u043d\u044b\u0439 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u0441\u043a\u043e\u0433\u043e \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0430: \u201cShape Morphing\u201d \u043d\u0430 iOS.<\/p>\n<h3>Metaballs<\/h3>\n<p>\u0422\u0435\u0445\u043d\u0438\u043a\u0430 \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u043e\u0431\u044a\u0435\u043a\u0442\u043e\u0432, \u043e\u0440\u0433\u0430\u043d\u0438\u0447\u0435\u0441\u043a\u0438 \u043c\u043e\u0440\u0444\u0438\u0440\u0443\u044e\u0449\u0438\u0445 \u043c\u0435\u0436\u0434\u0443 \u0441\u043e\u0431\u043e\u0439 \u0438 \u0434\u0440\u0443\u0433 \u0432 \u0434\u0440\u0443\u0433\u0430, \u0438\u0437\u0432\u0435\u0441\u0442\u043d\u0430 \u043a\u0430\u043a <a href=\"https:\/\/en.wikipedia.org\/wiki\/Metaballs\">metaballs<\/a> (\u0438\u043b\u0438 \u043c\u0435\u0442\u0430\u0441\u0444\u0435\u0440\u044b).<\/p>\n<p>\u041a\u043e\u0433\u0434\u0430 \u0434\u0432\u0430 \u043e\u0431\u044a\u0435\u043a\u0442\u0430 \u043f\u0440\u0438\u0431\u043b\u0438\u0436\u0430\u044e\u0442\u0441\u044f \u0434\u0440\u0443\u0433 \u043a \u0434\u0440\u0443\u0433\u0443, \u0432\u043c\u0435\u0441\u0442\u043e \u0442\u043e\u0433\u043e, \u0447\u0442\u043e\u0431\u044b \u0441\u043f\u043e\u043a\u043e\u0439\u043d\u043e \u0436\u0434\u0430\u0442\u044c, \u043f\u043e\u043a\u0430 \u043e\u043d\u0438 \u043d\u0430\u0447\u043d\u0443\u0442 \u043f\u0435\u0440\u0435\u043a\u0440\u044b\u0432\u0430\u0442\u044c\u0441\u044f\u2026\u00a0<\/p>\n<figure class=\"\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/88c\/ed8\/447\/88ced844796476f72cbcb42540ed9cca.png\" width=\"500\" height=\"310\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/88c\/ed8\/447\/88ced844796476f72cbcb42540ed9cca.png\"\/><figcaption><\/figcaption><\/figure>\n<p>\u2026 \u044d\u0442\u0438 \u0434\u0432\u0430 \u043e\u0431\u044a\u0435\u043a\u0442\u0430 \u043d\u0430\u0447\u043d\u0443\u0442 \u0440\u0430\u0441\u0442\u044f\u0433\u0438\u0432\u0430\u0442\u044c\u0441\u044f \u0434\u0440\u0443\u0433 \u043a \u0434\u0440\u0443\u0433\u0443 \u0438 \u043e\u0431\u044a\u0435\u0434\u0438\u043d\u044f\u0442\u044c\u0441\u044f.\u00a0<\/p>\n<figure class=\"\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/b83\/eea\/91c\/b83eea91c6a92aa1a6ab792863b590c0.png\" width=\"500\" height=\"310\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/b83\/eea\/91c\/b83eea91c6a92aa1a6ab792863b590c0.png\"\/><figcaption><\/figcaption><\/figure>\n<h2>\u0420\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f \u0432 iOS<\/h2>\n<p>\u0427\u0442\u043e\u0431\u044b \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u0442\u044c \u044d\u0442\u043e\u0442 \u044d\u0444\u0444\u0435\u043a\u0442, \u043d\u0430\u043c \u043d\u0443\u0436\u043d\u043e \u043e\u0431\u044a\u0435\u0434\u0438\u043d\u0438\u0442\u044c \u0432\u043c\u0435\u0441\u0442\u0435 2 \u0440\u0430\u0437\u043d\u044b\u0445 \u044d\u0444\u0444\u0435\u043a\u0442\u0430 (\u043f\u043e \u043a\u0440\u0430\u0439\u043d\u0435\u0439 \u043c\u0435\u0440\u0435, \u0432 \u0440\u0430\u043c\u043a\u0430\u0445 \u0442\u043e\u0433\u043e \u0441\u043f\u043e\u0441\u043e\u0431\u0430, \u043a\u043e\u0442\u043e\u0440\u044b\u043c \u043c\u044b \u0441\u043e\u0431\u0438\u0440\u0430\u0435\u043c\u0441\u044f \u044d\u0442\u043e \u0441\u0434\u0435\u043b\u0430\u0442\u044c, \u043d\u043e \u0435\u0441\u0442\u044c \u0435\u0449\u0435 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0434\u0440\u0443\u0433\u0438\u0445 \u0441\u043f\u043e\u0441\u043e\u0431\u043e\u0432 \u0434\u043e\u0431\u0438\u0442\u044c\u0441\u044f \u044d\u0442\u043e\u0433\u043e \u044d\u0444\u0444\u0435\u043a\u0442\u0430, \u0441\u0441\u044b\u043b\u043a\u0438 \u043d\u0430 \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043f\u0440\u0438\u0432\u0435\u0434\u0435\u043d\u044b \u0432 \u043a\u043e\u043d\u0446\u0435 \u0441\u0442\u0430\u0442\u044c\u0438).<\/p>\n<p>\u041d\u0430\u043c \u043d\u0443\u0436\u043d\u043e \u043e\u0442\u0440\u0435\u043d\u0434\u0435\u0440\u0438\u0442\u044c \u043d\u0430\u0448\u0438 \u0444\u043e\u0440\u043c\u044b, \u0430 \u0437\u0430\u0442\u0435\u043c \u043f\u0440\u0438\u043c\u0435\u043d\u0438\u0442\u044c \u043f\u043e\u0432\u0435\u0440\u0445 \u043e\u0442\u0440\u0435\u043d\u0434\u0435\u0440\u0435\u043d\u043d\u043e\u0433\u043e \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f \u044d\u0442\u0438 \u044d\u0444\u0444\u0435\u043a\u0442\u044b \u0432 \u0440\u0430\u043c\u043a\u0430\u0445 \u044d\u0442\u0430\u043f\u0430 \u043f\u043e\u0441\u0442\u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0438.<\/p>\n<p>\u041a\u0430\u0436\u0435\u0442\u0441\u044f \u0443 CALayer \u0435\u0441\u0442\u044c <a href=\"https:\/\/developer.apple.com\/documentation\/quartzcore\/calayer\/1410901-filters\">\u0441\u0432\u043e\u0439\u0441\u0442\u0432\u043e<\/a> <a href=\"https:\/\/developer.apple.com\/documentation\/quartzcore\/calayer\/1410901-filters\">.filters <\/a>\u043a\u043e\u0442\u043e\u0440\u043e\u0435 \u0438\u0434\u0435\u0430\u043b\u044c\u043d\u043e \u043f\u043e\u0434\u0445\u043e\u0434\u0438\u0442 \u0434\u043b\u044f \u044d\u0442\u043e\u0433\u043e!<\/p>\n<blockquote>\n<p><em>\u041c\u0430\u0441\u0441\u0438\u0432 Core Image \u0444\u0438\u043b\u044c\u0442\u0440\u043e\u0432 \u043f\u0440\u0438\u043c\u0435\u043d\u044f\u0435\u043c\u044b\u0445 \u043a \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u043c\u043e\u043c\u0443 \u0441\u043b\u043e\u044f \u0438 \u0435\u0433\u043e \u043f\u043e\u0434\u0441\u043b\u043e\u044f\u043c. Animatable.<\/em><\/p>\n<\/blockquote>\n<p>\u041f\u043e\u043a\u0430 \u0432\u044b \u043d\u0435 \u0434\u043e\u0431\u0435\u0440\u0435\u0442\u0435\u0441\u044c \u0434\u043e \u043d\u0438\u0437\u0430 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u0446\u0438\u0438.<\/p>\n<blockquote>\n<p><em>\u042d\u0442\u043e \u0441\u0432\u043e\u0439\u0441\u0442\u0432\u043e \u043d\u0435 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442\u0441\u044f \u0434\u043b\u044f \u0441\u043b\u043e\u0435\u0432 \u0432 iOS<\/em><\/p>\n<\/blockquote>\n<p>\u041a \u0441\u043e\u0436\u0430\u043b\u0435\u043d\u0438\u044e, \u0442\u0430\u043a\u0438\u0435 \u044d\u0444\u0444\u0435\u043a\u0442\u044b \u0440\u0435\u043d\u0434\u0435\u0440\u0438\u043d\u0433\u0430 \u043d\u0435 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u044e\u0442\u0441\u044f \u0432 iOS.<\/p>\n<p>\u041f\u043e\u044d\u0442\u043e\u043c\u0443 \u0432\u043c\u0435\u0441\u0442\u043e \u044d\u0442\u043e\u0433\u043e \u043d\u0430\u043c \u043d\u0443\u0436\u043d\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c API \u0434\u043b\u044f \u0440\u0435\u043d\u0434\u0435\u0440\u0438\u043d\u0433\u0430, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u043d\u0430\u043c \u0434\u0435\u043b\u0430\u0442\u044c \u043f\u043e\u0441\u0442\u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0443 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u0430 \u0440\u0435\u043d\u0434\u0435\u0440\u0438\u043d\u0433\u0430.<\/p>\n<p>\u0412 iOS \u0443 \u043d\u0430\u0441 \u0435\u0441\u0442\u044c \u0432\u044b\u0431\u043e\u0440 \u043c\u0435\u0436\u0434\u0443 SpriteKit \u0438 SceneKit.<\/p>\n<p>\u0412 SceneKit \u0435\u0441\u0442\u044c \u043a\u0440\u0443\u0442\u043e\u0439 \u0442\u0440\u044e\u043a, \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u044e\u0449\u0438\u0439 \u0432\u0430\u043c \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c UIView \u0432 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u043c\u0430\u0442\u0435\u0440\u0438\u0430\u043b\u0430 \u0443\u0437\u043b\u0430, \u0447\u0442\u043e \u0431\u044b\u043b\u043e \u0431\u044b \u043e\u0447\u0435\u043d\u044c \u043f\u043e\u043b\u0435\u0437\u043d\u043e \u043f\u0440\u0438 \u043f\u0440\u0438\u043c\u0435\u043d\u0435\u043d\u0438\u0438 \u044d\u0442\u043e\u0433\u043e \u0442\u0440\u044e\u043a\u0430 \u043a UIView, \u043f\u043e\u0441\u043a\u043e\u043b\u044c\u043a\u0443 \u043c\u044b \u043c\u043e\u0433\u043b\u0438 \u0431\u044b \u0440\u0430\u0437\u043c\u0435\u0449\u0430\u0442\u044c \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u0432\u043d\u0443\u0442\u0440\u0438 \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u0438\u044f SceneKit.<\/p>\n<p>\u041e\u0434\u043d\u0430\u043a\u043e \u043f\u043e\u043a\u0430 \u044f \u043f\u0440\u043e\u0441\u0442\u043e \u0431\u0443\u0434\u0443 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c SpriteKit, \u0442\u0430\u043a \u043a\u0430\u043a \u043c\u043d\u0435 \u043d\u0435 \u043d\u0443\u0436\u043d\u043e \u043d\u0438\u0447\u0435\u0433\u043e \u0431\u043e\u043b\u044c\u0448\u0435\u0433\u043e \u0434\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u043f\u0440\u0438\u043c\u0435\u0440\u0430, \u0438 \u0441 \u043d\u0438\u043c \u043f\u0440\u043e\u0449\u0435 \u0440\u0430\u0431\u043e\u0442\u0430\u0442\u044c.<\/p>\n<h2>\u0420\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f \u0441\u043e SpriteKit<\/h2>\n<p>\u0421\u043d\u0430\u0447\u0430\u043b\u0430 \u043d\u0430\u043c \u043d\u0443\u0436\u043d\u043e \u043f\u043e\u0434\u0433\u043e\u0442\u043e\u0432\u0438\u0442\u044c \u043d\u0430\u0448\u0443 \u0441\u0446\u0435\u043d\u0443:<\/p>\n<pre><code class=\"kotlin\">class SimpleViewController: UIViewController { \/\/ \u041d\u0430\u0448\u0438 SpriteKit \u043e\u0431\u044a\u0435\u043a\u0442\u044b\u00a0 let skView = SKView() let scene = SKScene() \/\/ \u0414\u0432\u0435 \u0441\u0444\u0435\u0440\u044b var blobOne: SKShapeNode? = nil var blobTwo: SKShapeNode? = nil override func viewDidLoad() { super.viewDidLoad() \/\/ \u041f\u043e\u0434\u0433\u043e\u0442\u043e\u0432\u043a\u0430 \u0441\u0446\u0435\u043d\u044b self.scene.scaleMode = .resizeFill self.scene.physicsWorld.gravity = CGVector(dx: 0, dy: 0) self.skView.presentScene(self.scene) self.view.addSubview(self.skView); \/\/ \u0421\u043e\u0437\u0434\u0430\u0435\u043c \u0434\u0432\u0435 \u0441\u0444\u0435\u0440\u044b { let blob = SKShapeNode(circleOfRadius: 50) blob.fillColor = UIColor.yellow blob.strokeColor = blob.fillColor self.scene.addChild(blob) self.blobOne = blob }(); { let blob = SKShapeNode(circleOfRadius: 50) blob.fillColor = UIColor.white blob.strokeColor = blob.fillColor self.scene.addChild(blob) self.blobTwo = blob }(); } \/\/ \u0423\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0435\u043c \u043f\u043e\u0437\u0438\u0446\u0438\u0438 \u0432\u0441\u0435\u0445 \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u043e\u0432 override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() let bounds = self.view.bounds self.skView.frame = bounds let center = CGPoint(x: bounds.midX, y: bounds.midY) self.blobOne?.position = center.applying(CGAffineTransform(translationX: -50, y: 0)) self.blobTwo?.position = center.applying(CGAffineTransform(translationX: 50, y: 0)) } }<\/code><\/pre>\n<p>\u00a0\u042d\u0442\u043e \u043d\u0430\u0448 \u0431\u0430\u0437\u043e\u0432\u044b\u0439 \u0441\u0435\u0442\u0430\u043f. \u0414\u0432\u0435 \u0441\u0444\u0435\u0440\u044b \u0440\u0430\u0441\u043f\u043e\u043b\u043e\u0436\u0435\u043d\u044b \u0440\u044f\u0434\u043e\u043c \u0434\u0440\u0443\u0433 \u0441 \u0434\u0440\u0443\u0433\u043e\u043c, \u0435\u0434\u0432\u0430 \u0441\u043e\u043f\u0440\u0438\u043a\u0430\u0441\u0430\u044f\u0441\u044c.<\/p>\n<figure class=\"\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/69a\/d17\/a06\/69ad17a060deee85d665834c66bba118.png\" width=\"500\" height=\"310\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/69a\/d17\/a06\/69ad17a060deee85d665834c66bba118.png\"\/><figcaption><\/figcaption><\/figure>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u0434\u0430\u0432\u0430\u0439\u0442\u0435 \u0434\u043e\u0431\u0430\u0432\u0438\u043c \u044d\u0444\u0444\u0435\u043a\u0442 \u0440\u0430\u0437\u043c\u044b\u0442\u0438\u044f \u0432 \u043a\u043e\u043d\u0446\u0435 <code>-viewDidLoad()<\/code>.<\/p>\n<pre><code class=\"kotlin\">\/\/ - \u0412 \u043d\u0430\u0447\u0430\u043b\u043e \u0444\u0430\u0439\u043b\u0430.... import CoreImage import CoreImage.CIFilterBuiltins \/\/ - \u0412\u043d\u0438\u0437\u0443 viewDidLoad \/\/ \u0421\u043e\u0437\u0434\u0430\u0435\u043c \u0444\u0438\u043b\u044c\u0442\u0440 let blur = CIFilter.gaussianBlur() blur.radius = 20 self.scene.filter = blur \/\/ \u0423\u0431\u0435\u0434\u0438\u0442\u0435\u0441\u044c, \u0447\u0442\u043e \u0441\u0446\u0435\u043d\u0430 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442 \u0441\u043e\u0437\u0434\u0430\u043d\u043d\u044b\u0439 \u043d\u0430\u043c\u0438 \u0444\u0438\u043b\u044c\u0442\u0440 self.scene.shouldEnableEffects = true<\/code><\/pre>\n<p>\u00a0<\/p>\n<figure class=\"\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/90e\/89c\/0c6\/90e89c0c6b112cc9b3817c941bea6929.png\" width=\"500\" height=\"310\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/90e\/89c\/0c6\/90e89c0c6b112cc9b3817c941bea6929.png\"\/><figcaption><\/figcaption><\/figure>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c, \u043a\u043e\u0433\u0434\u0430 \u043c\u044b \u044d\u0442\u043e \u0441\u0434\u0435\u043b\u0430\u043b\u0438, \u043c\u044b \u043c\u043e\u0436\u0435\u043c \u0432\u0438\u0434\u0435\u0442\u044c, \u043a\u0430\u043a \u0444\u0438\u0433\u0443\u0440\u044b \u0441\u043b\u0438\u0432\u0430\u044e\u0442\u0441\u044f \u0432 \u043e\u0434\u043d\u0443 \u0431\u043b\u0430\u0433\u043e\u0434\u0430\u0440\u044f \u043f\u0435\u0440\u0435\u043a\u0440\u044b\u0442\u0438\u044e \u0438\u0445 \u0440\u0430\u0437\u043c\u044b\u0442\u044b\u0445 \u0432\u0435\u0440\u0441\u0438\u0439.<\/p>\n<p>\u041e\u0434\u043d\u0430\u043a\u043e \u044d\u0442\u043e \u0432\u0441\u0435 \u0435\u0449\u0435 \u043d\u0435 \u043f\u043e\u0445\u043e\u0436\u0435 \u043d\u0430 \u0442\u043e\u0442 \u044d\u0444\u0444\u0435\u043a\u0442, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043d\u0430\u043c \u043d\u0443\u0436\u0435\u043d. \u0414\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u043d\u0430\u043c \u043d\u0443\u0436\u043d\u043e \u0431\u0443\u0434\u0435\u0442 \u043f\u0440\u0438\u043c\u0435\u043d\u0438\u0442\u044c \u0444\u0438\u043b\u044c\u0442\u0440 \u201c\u043f\u043e\u0440\u043e\u0433\u201d (threshold), \u0433\u0434\u0435 \u043c\u044b \u0434\u0435\u043b\u0430\u0435\u043c \u043f\u043e\u043b\u043d\u043e\u0441\u0442\u044c\u044e \u043d\u0435\u043f\u0440\u043e\u0437\u0440\u0430\u0447\u043d\u044b\u043c\u0438 \u0432\u0441\u0435, \u0447\u0442\u043e \u0432\u044b\u0448\u0435 \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u043d\u043e\u0433\u043e \u043f\u043e\u0440\u043e\u0433\u0430, \u0438 \u0441\u043a\u0440\u044b\u0432\u0430\u0435\u043c \u043a\u0430\u0436\u0434\u044b\u0439 \u043f\u0438\u043a\u0441\u0435\u043b\u044c \u043d\u0438\u0436\u0435 \u044d\u0442\u043e\u0433\u043e \u043f\u043e\u0440\u043e\u0433\u0430.<\/p>\n<p>\u042d\u0442\u043e \u043f\u043e\u0437\u0432\u043e\u043b\u0438\u0442 \u043d\u0430\u0448\u0438\u043c \u0444\u043e\u0440\u043c\u0430\u043c \u0441\u043e\u0445\u0440\u0430\u043d\u0438\u0442\u044c \u0447\u0435\u0442\u043a\u0438\u0435 \u0433\u0440\u0430\u043d\u0438\u0446\u044b \u0438 \u0432\u044b\u0433\u043b\u044f\u0434\u0435\u0442\u044c \u0441\u043e\u0435\u0434\u0438\u043d\u044f\u044e\u0449\u0438\u043c\u0438\u0441\u044f, \u0430 \u043d\u0435 \u043f\u0440\u043e\u0441\u0442\u043e \u0441\u043b\u0438\u0432\u0430\u044e\u0449\u0438\u043c\u0438\u0441\u044f \u0434\u0440\u0443\u0433 \u0441 \u0434\u0440\u0443\u0433\u043e\u043c.<\/p>\n<p>\u041c\u044b \u043d\u0435 \u043c\u043e\u0436\u0435\u043c \u043f\u0440\u0438\u043c\u0435\u043d\u044f\u0442\u044c \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0444\u0438\u043b\u044c\u0442\u0440\u043e\u0432 \u043a SpriteKit \u0441\u0446\u0435\u043d\u0435, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u043d\u0430\u043c \u043d\u0443\u0436\u043d\u043e \u0441\u043e\u0437\u0434\u0430\u0442\u044c \u043a\u043b\u0430\u0441\u0441, \u043d\u0430\u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0439 CIFilter, \u0438 \u0441\u043e\u0437\u0434\u0430\u0442\u044c \u0441\u043e\u0431\u0441\u0442\u0432\u0435\u043d\u043d\u044b\u0439 \u043a\u043e\u043c\u0431\u0438\u043d\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0439 \u0444\u0438\u043b\u044c\u0442\u0440.<\/p>\n<pre><code class=\"kotlin\">class MetaballEffectFilter: CIFilter { \/\/ \u0412\u043d\u0443\u0442\u0440\u0435\u043d\u043d\u0438\u0435 \u0444\u0438\u043b\u044c\u0442\u0440\u044b let blurFilter: CIFilter &amp; CIGaussianBlur = { let blur = CIFilter.gaussianBlur() blur.radius = 30 return blur }() let thresholdFilter = LumaThresholdFilter() \/\/ - \u0421\u0432\u043e\u0439\u0441\u0442\u0432\u0430 \u0434\u043e\u0447\u0435\u0440\u043d\u0435\u0433\u043e \u043a\u043b\u0430\u0441\u0441\u0430 CIFilter @objc dynamic var inputImage : CIImage? override var outputImage: CIImage! { guard let inputImage = self.inputImage else { return nil } \/\/ \u0420\u0430\u0437\u043c\u044b\u0442\u0438\u0435 \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f self.blurFilter.inputImage = inputImage let blurredOutput = self.blurFilter.outputImage \/\/ \u041e\u0431\u0440\u0435\u0437\u043a\u0430 \u043f\u043e \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u043d\u043e\u043c\u0443 \u043f\u043e\u0440\u043e\u0433\u0443\u00a0 self.thresholdFilter.inputImage = blurredOutput return self.thresholdFilter.outputImage } }<\/code><\/pre>\n<p>\u00a0\u0421\u043d\u0430\u0447\u0430\u043b\u0430 \u043c\u044b \u0440\u0430\u0437\u043c\u044b\u0432\u0430\u0435\u043c \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435, \u0437\u0430\u0442\u0435\u043c \u043e\u0431\u0440\u0435\u0437\u0430\u0435\u043c \u0432\u044b\u0432\u043e\u0434 \u043f\u043e \u043f\u043e\u0440\u043e\u0433\u043e\u0432\u043e\u043c\u0443 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044e.<\/p>\n<p>\u041d\u043e \u043e\u0442\u043a\u0443\u0434\u0430 \u0432\u0437\u044f\u043b\u0441\u044f \u044d\u0442\u043e\u0442 <code>LumaThresholdFilter<\/code> ? \u042d\u0442\u043e \u0435\u0449\u0435 \u043e\u0434\u0438\u043d \u043a\u043b\u0430\u0441\u0441, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043d\u0430\u043c \u043d\u0443\u0436\u043d\u043e \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u0442\u044c \u0441\u0430\u043c\u0438\u043c.<\/p>\n<p>\u041d\u0430\u0438\u0431\u043e\u043b\u0435\u0435 \u043f\u0440\u043e\u0441\u0442\u043e\u0439 \u0441\u043f\u043e\u0441\u043e\u0431 \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u044d\u0442\u043e \u2014 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c <code>Core Image Kernel Language API<\/code>, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0443\u0441\u0442\u0430\u0440\u0435\u043b. \u0412\u043c\u0435\u0441\u0442\u043e \u044d\u0442\u043e\u0433\u043e \u043d\u0430\u043c \u0441\u043b\u0435\u0434\u0443\u0435\u0442 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u043d\u043e\u0432\u044b\u0439 Metal Shader CoreImage Filter API.<\/p>\n<p>\u041e\u0434\u043d\u0430\u043a\u043e \u0434\u043b\u044f \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f \u043c\u0435\u0442\u0430\u043b\u043b\u0438\u0447\u0435\u0441\u043a\u0438\u0445 \u0448\u0435\u0439\u0434\u0435\u0440\u043e\u0432 CoreImage \u0442\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044f \u0435\u0449\u0435 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0448\u0430\u0433\u043e\u0432 \u043f\u043e \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0435 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u0438 \u0441\u0431\u043e\u0440\u043a\u0438 \u043f\u0443\u0442\u0435\u043c \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u0441\u043a\u0438\u0445 \u043f\u0440\u0430\u0432\u0438\u043b \u0438 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u043e\u0432 \u0441\u0431\u043e\u0440\u043a\u0438. \u041f\u043e\u044d\u0442\u043e\u043c\u0443 \u0441\u0435\u0439\u0447\u0430\u0441 \u044f \u043f\u043e\u043a\u0430\u0436\u0443 \u043f\u0440\u0438\u043c\u0435\u0440\u044b \u043a\u0430\u043a \u043c\u0435\u0442\u0430\u043b\u043b\u0438\u0447\u0435\u0441\u043a\u0438\u0445 \u0448\u0435\u0439\u0434\u0435\u0440\u043e\u0432, \u0442\u0430\u043a \u0438 \u0448\u0435\u0439\u0434\u0435\u0440\u043e\u0432 CoreImage, \u043d\u043e \u043f\u043e\u0437\u0436\u0435 \u0431\u0443\u0434\u0443 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0442\u043e\u043b\u044c\u043a\u043e <code>Core Image Kernel Language<\/code> \u0434\u043b\u044f \u043e\u0441\u0442\u0430\u043b\u044c\u043d\u044b\u0445 \u043f\u0440\u0438\u043c\u0435\u0440\u043e\u0432.<\/p>\n<p>Metal Shader<\/p>\n<pre><code>\/\/ LumaThreshold.ci.metal  #include &lt;metal_stdlib> using namespace metal; #include &lt;CoreImage\/CoreImage.h>  extern \"C\" float4 lumaThreshold(coreimage::sample_t pixelColor, float threshold, coreimage::destination destination) { float3 pixelRGB = pixelColor.rgb;  float luma = (pixelRGB.r * 0.2126) + (pixelRGB.g * 0.7152) + (pixelRGB.b * 0.0722);  return (luma > threshold) ? float4(1.0, 1.0, 1.0, 1.0) : float4(0.0, 0.0, 0.0, 0.0); }<\/code><\/pre>\n<p>\u00a0Core Image Kernel Language Shader  <\/p>\n<pre><code>\/\/ LumaThresholdFilter.swift  class LumaThresholdFilter: CIFilter { var threshold: CGFloat = 0.5  static let thresholdKernel = CIColorKernel(source:\"\"\" kernel vec4 thresholdFilter(__sample image, float threshold) { float luma = (image.r * 0.2126) + (image.g * 0.7152) + (image.b * 0.0722); return (luma > threshold) ? vec4(1.0, 1.0, 1.0, 1.0) : vec4(0.0, 0.0, 0.0, 0.0); } \"\"\")!  \/\/  @objc dynamic var inputImage : CIImage?  override var outputImage : CIImage! { guard let inputImage = self.inputImage else { return nil }  let arguments = [inputImage, Float(self.threshold)] as [Any]  return Self.thresholdKernel.apply(extent: inputImage.extent, arguments: arguments) }  }<\/code><\/pre>\n<p>\u041c\u044b \u043d\u0430\u043a\u043e\u043d\u0435\u0446 \u043f\u043e\u043b\u0443\u0447\u0438\u043b\u0438 \u0436\u0435\u043b\u0430\u0435\u043c\u044b\u0439 \u044d\u0444\u0444\u0435\u043a\u0442:<\/p>\n<figure class=\"\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/ec7\/285\/2d1\/ec72852d13e40493d5062fec16b5c582.png\" width=\"500\" height=\"310\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/ec7\/285\/2d1\/ec72852d13e40493d5062fec16b5c582.png\"\/><figcaption><\/figcaption><\/figure>\n<p>\u00a0<\/p>\n<h2>\u041e\u0442 \u043c\u0435\u0442\u0430\u0441\u0444\u0435\u0440 \u043a \u043c\u043e\u0440\u0444\u0438\u043d\u0433\u0443 \u0438\u043a\u043e\u043d\u043e\u043a<\/h2>\n<p>\u0427\u0442\u043e\u0436, \u043c\u044b \u0434\u043e\u0431\u0438\u043b\u0438\u0441\u044c \u044d\u0444\u0444\u0435\u043a\u0442\u0430 \u043c\u0435\u0442\u0430\u0441\u0444\u0435\u0440. \u041d\u043e \u044d\u0442\u043e \u0435\u0449\u0435 \u043d\u0435 \u043c\u043e\u0440\u0444\u0438\u043d\u0433 \u0438\u043a\u043e\u043d\u043e\u043a, \u043a\u0430\u043a \u043f\u043e\u043a\u0430\u0437\u0430\u043d\u043e \u0432 \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b\u044c\u043d\u043e\u043c \u0432\u0438\u0434\u0435\u043e \u0432 \u0422\u0432\u0438\u0442\u0442\u0435\u0440\u0435. \u041e\u0434\u043d\u0430\u043a\u043e \u0442\u0435\u043f\u0435\u0440\u044c \u0443 \u043d\u0430\u0441 \u0435\u0441\u0442\u044c \u0432\u0441\u0435 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e\u0435.<\/p>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u043d\u0430\u043c \u043f\u0440\u043e\u0441\u0442\u043e \u043d\u0443\u0436\u043d\u043e \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u043d\u043e\u0432\u0443\u044e \u0438\u043a\u043e\u043d\u043a\u0443 \u043f\u043e\u0432\u0435\u0440\u0445 \u0441\u0442\u0430\u0440\u043e\u0439, \u043f\u0440\u0438 \u044d\u0442\u043e\u043c \u043f\u043e\u0441\u0442\u0435\u043f\u0435\u043d\u043d\u043e \u0443\u0431\u0438\u0440\u0430\u044f \u0441\u0442\u0430\u0440\u0443\u044e \u0438\u043a\u043e\u043d\u043a\u0443.<\/p>\n<p>\u0414\u043b\u044f \u043d\u0430\u0447\u0430\u043b\u0430 \u043d\u0430\u043c \u043d\u0443\u0436\u043d\u043e \u043f\u043e\u043c\u0435\u043d\u044f\u0442\u044c \u0434\u0432\u0430 \u043a\u0440\u0443\u0433\u0430 \u043d\u0430 \u043e\u0434\u043d\u0443 \u0438\u043a\u043e\u043d\u043a\u0443. \u0422\u0435\u043f\u0435\u0440\u044c \u043d\u0430\u043c \u0442\u0430\u043a\u0436\u0435 \u043d\u0443\u0436\u043d\u043e \u0441\u043e\u0445\u0440\u0430\u043d\u0438\u0442\u044c \u0441\u0441\u044b\u043b\u043a\u0443 \u043d\u0430 \u0444\u0438\u043b\u044c\u0442\u0440, \u0447\u0442\u043e\u0431\u044b \u043c\u044b \u043c\u043e\u0433\u043b\u0438 \u0435\u0433\u043e \u0430\u043d\u0438\u043c\u0438\u0440\u043e\u0432\u0430\u0442\u044c.<\/p>\n<pre><code>var currentIcon: SKSpriteNode?  let filter = MetaballEffectFilter()<\/code><\/pre>\n<p>\u0418 \u0434\u0430\u0432\u0430\u0439\u0442\u0435 \u0434\u043e\u0431\u0430\u0432\u0438\u043c \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u043a\u043d\u043e\u043f\u043e\u043a \u043d\u0430 \u044d\u043a\u0440\u0430\u043d, \u0447\u0442\u043e\u0431\u044b \u043c\u044b \u043c\u043e\u0433\u043b\u0438 \u0438\u0445 \u0437\u0430\u0434\u0435\u0439\u0441\u0442\u0432\u043e\u0432\u0430\u0442\u044c.<\/p>\n<pre><code>\/\/ \u0421\u0432\u043e\u0439\u0441\u0442\u0432\u043e  lazy var buttons : [UIButton] = { [unowned self] in return [ \"circle.fill\", \"heart.fill\", \"star.fill\", \"bell.fill\", \"bookmark.fill\", \"tag.fill\", \"bolt.fill\",  \"play.fill\", \"pause.fill\", \"squareshape.fill\", \"key.fill\", \"hexagon.fill\", \"gearshape.fill\", \"car.fill\", ].map({ buttonName in  var config = UIButton.Configuration.filled() config.image = UIImage(systemName: buttonName) config.baseBackgroundColor = UIColor.white config.baseForegroundColor = UIColor.black  let button = UIButton(configuration: config)  button.addAction(UIAction(handler: { [weak self] _ in self?.animateIconChange(newIconName: buttonName, duration: 0.5) }), for: .touchUpInside)  return button }) }()   \/\/ ...in viewDidLoad()  for button in self.buttons { self.view.addSubview(button) }   \/\/ .. In viewDidLayoutSubviews()  let buttonSize: CGSize = CGSize(width: 70, height: 50)  var x: CGFloat = 0 var y: CGFloat = bounds.height - (self.view.safeAreaInsets.bottom + 5 + buttonSize.height)  for button in self.buttons { button.bounds = CGRect(origin: .zero, size: buttonSize) button.center.x = x + (buttonSize.width * 0.5) button.center.y = y + (buttonSize.height * 0.5)  x += buttonSize.width + 5  if x > bounds.size.width - (buttonSize.width + 5) { x = 0 y -= buttonSize.height + 5 } }<\/code><\/pre>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u0434\u0430\u0432\u0430\u0439\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u043c \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435 \u0438\u043a\u043e\u043d\u043a\u0438. (\u0418\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c <a href=\"https:\/\/gist.github.com\/kylehowells\/27e6e1bf42f283d22c3ae8bb9faaeece\">\u044d\u0442\u043e\u0442 UIImage<\/a> <a href=\"https:\/\/gist.github.com\/kylehowells\/27e6e1bf42f283d22c3ae8bb9faaeece\">\u0445\u0435\u043b\u043f\u0435\u0440<\/a>).<\/p>\n<pre><code>\/\/ ...\u0432 \u043a\u043e\u043d\u0446\u0435 viewDidLoad()  self.animateIconChange(newIconName: \"circle.fill\", duration: 0)   \/\/ \u0421\u043e\u0437\u0434\u0430\u0435\u043c \u043d\u043e\u0432\u044b\u0439 \u043c\u0435\u0442\u043e\u0434  func animateIconChange(newIconName: String, duration: CGFloat, showPlayIcon: Bool = false) { \/\/ \u0421\u043e\u0437\u0434\u0430\u0435\u043c \u043d\u043e\u0432\u0443\u044e \u0444\u043e\u0440\u043c\u0443 \u0434\u043b\u044f \u0438\u043a\u043e\u043d\u043a\u0438 let newIconShape: SKSpriteNode? = { let iconSize = CGSize(width: 80, height: 80)  guard let image = UIImage(systemName: newIconName)?.withTintColor(UIColor.white).resized(within: iconSize) else { return nil }  let texture = SKTexture(image: image) let sprite = SKSpriteNode(texture: texture, size: iconSize) return sprite }()  newIconShape?.position = CGPoint( x: self.view.bounds.midX, y: self.view.bounds.midY ) newIconShape?.alpha = 0  \/\/ \u0414\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u043c \u043d\u043e\u0432\u0443\u044e \u0438\u043a\u043e\u043d\u043a\u0443 if let newIconShape = newIconShape { self.scene.addChild(newIconShape) }  let oldIconShape = self.currentIcon self.currentIcon = nil  self.currentIcon = newIconShape   if duration == 0 { newIconShape?.alpha = 1 oldIconShape?.removeFromParent() return }  ... }<\/code><\/pre>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u0443 \u043d\u0430\u0441 \u0435\u0441\u0442\u044c \u0431\u0430\u0437\u043e\u0432\u0430\u044f \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0430.<\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/21e\/051\/445\/21e051445001e14421fdcad06082966c.png\" width=\"525\" height=\"375\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/21e\/051\/445\/21e051445001e14421fdcad06082966c.png\"\/><figcaption><\/figcaption><\/figure>\n<p>\u0427\u0442\u043e\u0431\u044b \u0430\u043d\u0438\u043c\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0441\u043c\u0435\u043d\u0443 \u0438\u043a\u043e\u043d\u043e\u043a \u0430\u043d\u0430\u043b\u043e\u0433\u0438\u0447\u043d\u043e \u0442\u043e\u043c\u0443, \u043a\u0430\u043a \u043e\u043d\u0438 \u043f\u043e\u044f\u0432\u043b\u044f\u044e\u0442\u0441\u044f \u0432 \u0422\u0432\u0438\u0442\u0442\u0435\u0440\u0435, \u043d\u0430\u043c \u043d\u0443\u0436\u043d\u043e \u0431\u044b\u0441\u0442\u0440\u043e \u0432\u044b\u043f\u043e\u043b\u043d\u0438\u0442\u044c \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0443\u044e \u043f\u043e\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u044c:<\/p>\n<ul>\n<li>\n<p>\u0421\u043d\u0430\u0447\u0430\u043b\u0430 \u0430\u043d\u0438\u043c\u0438\u0440\u0443\u0435\u043c \u0440\u0430\u0437\u043c\u044b\u0442\u0438\u0435 \u043e\u0442 0 \u0434\u043e \u043a\u0430\u043a\u043e\u0433\u043e-\u043b\u0438\u0431\u043e \u043f\u0440\u0438\u0435\u043c\u043b\u0435\u043c\u043e\u0433\u043e \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f.<\/p>\n<\/li>\n<li>\n<p>\u041d\u0430 \u043f\u043e\u043b\u043e\u0432\u0438\u043d\u0435 \u044d\u0442\u043e\u0433\u043e \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u0430 \u043d\u0430\u0447\u0438\u043d\u0430\u0435\u043c \u0443\u0432\u0435\u043b\u0438\u0447\u0438\u0432\u0430\u0442\u044c \u0430\u043b\u044c\u0444\u0430-\u043a\u0430\u043d\u0430\u043b \u043d\u043e\u0432\u043e\u0439 \u0444\u043e\u0440\u043c\u044b \u0438 \u043f\u043e\u043d\u0438\u0436\u0430\u0442\u044c \u0443 \u0441\u0442\u0430\u0440\u043e\u0439 \u0444\u043e\u0440\u043c\u044b.<\/p>\n<\/li>\n<li>\n<p>\u041f\u0440\u0438\u043c\u0435\u0440\u043d\u043e \u043d\u0430 \u0442\u0440\u0435\u0442\u0435\u0439 \u0447\u0435\u0442\u0432\u0435\u0440\u0442\u0438 \u043f\u0443\u0442\u0438 \u0444\u043e\u0440\u043c\u044b \u0437\u0430\u043c\u0435\u043d\u044f\u044e\u0442\u0441\u044f \u043e\u0434\u043d\u0430 \u0434\u0440\u0443\u0433\u043e\u0439, \u0430 \u043c\u044b \u0430\u043d\u0438\u043c\u0438\u0440\u0443\u0435\u043c \u0440\u0430\u0437\u043c\u044b\u0442\u0438\u0435 \u043e\u0431\u0440\u0430\u0442\u043d\u043e \u0434\u043e 0, \u0447\u0442\u043e\u0431\u044b \u043a \u043a\u043e\u043d\u0446\u0443 \u0443 \u043d\u0430\u0441 \u043e\u0441\u0442\u0430\u043b\u0430\u0441\u044c \u0442\u043e\u043b\u044c\u043a\u043e \u043d\u043e\u0432\u0430\u044f \u0446\u0435\u043b\u0435\u0432\u0430\u044f \u0444\u043e\u0440\u043c\u0430.<\/p>\n<\/li>\n<\/ul>\n<pre><code class=\"kotlin\">\/\/ ... \u043f\u0440\u043e\u0434\u043e\u043b\u0436\u0430\u0435\u043c  \/\/ \u0410\u043d\u0438\u043c\u0438\u0440\u0443\u0435\u043c \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f  let fadeDuration = (duration * 0.25)  \/\/ \u0410\u043d\u0438\u043c\u0438\u0440\u0443\u0435\u043c \u044d\u0444\u0444\u0435\u043a\u0442 \u0440\u0430\u0437\u043c\u044b\u0442\u0438\u044f self.animateBlur(duration: fadeDuration, blur: 5, from: 0)  \/\/ \u041d\u0435\u043c\u043d\u043e\u0433\u043e \u0436\u0434\u0435\u043c, \u0430 \u0437\u0430\u0442\u0435\u043c \u043d\u0430\u0447\u0438\u043d\u0430\u0435\u043c \u043f\u043e\u0441\u0442\u0435\u043f\u0435\u043d\u043d\u043e \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0430\u0442\u044c \u043d\u043e\u0432\u0443\u044e \u0438\u043a\u043e\u043d\u043a\u0443 \u0438 \u0441\u043a\u0440\u044b\u0432\u0430\u0442\u044c \u0441\u0442\u0430\u0440\u0443\u044e DispatchQueue.main.asyncAfter(deadline: .now() + (fadeDuration * 0.75), execute: {  let swapDuration = duration * 0.5  newIconShape?.run(SKAction.fadeAlpha(to: 1, duration: swapDuration)) oldIconShape?.run(SKAction.fadeAlpha(to: 0, duration: swapDuration))  \/\/ \u0421\u043d\u043e\u0432\u0430 \u0436\u0434\u0435\u043c, \u0430 \u0437\u0430\u0442\u0435\u043c \u043d\u0430\u0447\u0438\u043d\u0430\u0435\u043c \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0442\u044c \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u043e\u0431\u0440\u0430\u0442\u043d\u043e \u043a \u0447\u0435\u0442\u043a\u043e\u0439 \u0432\u0435\u0440\u0441\u0438\u0438 DispatchQueue.main.asyncAfter(deadline: .now() + (swapDuration * 0.75), execute: {  self.animateBlur(duration: fadeDuration, blur: 0, from: 5)  \/\/ \u041d\u0430\u0432\u043e\u0434\u0438\u043c \u043f\u043e\u0440\u044f\u0434\u043e\u043a DispatchQueue.main.asyncAfter(deadline: .now() + fadeDuration, execute: { oldIconShape?.removeFromParent() }) }) }) }  \/\/ \u0425\u0435\u043b\u043f\u0435\u0440, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0430\u043d\u0438\u043c\u0438\u0440\u0443\u0435\u0442 \u0440\u0430\u0437\u043c\u044b\u0442\u0438\u0435 func animateBlur(duration: CGFloat, blur targetBlur: CGFloat, from: CGFloat) { let blurFade = SKAction.customAction(withDuration: duration, actionBlock: { (node, elapsed) in let percent = elapsed \/ CGFloat(duration)  let difference = (targetBlur - from) let currentBlur = from + (difference * percent)  self.filter.blurFilter.setValue(currentBlur, forKey: kCIInputRadiusKey) self.scene.shouldEnableEffects = true })  self.scene.run(blurFade) }<\/code><\/pre>\n<p>\u041f\u043e \u043c\u0435\u0440\u0435 \u0442\u043e\u0433\u043e, \u043a\u0430\u043a \u0440\u0430\u0437\u043b\u0438\u0447\u043d\u044b\u0435 \u0447\u0430\u0441\u0442\u0438 \u0444\u0438\u0433\u0443\u0440 \u0438\u0441\u0447\u0435\u0437\u0430\u044e\u0442 \u0438 \u043f\u0440\u0435\u0432\u044b\u0448\u0430\u044e\u0442 \u043f\u043e\u0440\u043e\u0433\u043e\u0432\u043e\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435, \u043e\u043d\u0438 \u043f\u0440\u043e\u044f\u0432\u043b\u044f\u044e\u0442\u0441\u044f.<\/p>\n<p>\u0420\u0430\u0437\u043c\u044b\u0442\u0438\u0435 \u0442\u0430\u043a\u0436\u0435 \u043e\u0437\u043d\u0430\u0447\u0430\u0435\u0442, \u0447\u0442\u043e \u0444\u043e\u0440\u043c\u044b \u043a\u0430\u043a \u0431\u044b \u0441\u043b\u0438\u0432\u0430\u044e\u0442\u0441\u044f \u0432\u043c\u0435\u0441\u0442\u0435, \u043a\u0430\u043a \u043c\u044b \u0432\u0438\u0434\u0435\u043b\u0438 \u044d\u0442\u043e \u0440\u0430\u043d\u0435\u0435 \u0432 \u0432\u0430\u0440\u0438\u0430\u043d\u0442\u0435 \u0441 \u043c\u0435\u0442\u0430\u0441\u0444\u0435\u0440\u0430\u043c\u0438.<\/p>\n<p>\u041c\u044b \u043f\u043e \u0441\u0443\u0442\u0438 \u043f\u043b\u0430\u0432\u043d\u043e \u043f\u0435\u0440\u0435\u0445\u043e\u0434\u0438\u043c \u043e\u0442 \u043e\u0434\u043d\u043e\u0439 \u0438\u043a\u043e\u043d\u043a\u0438 \u043a \u0434\u0440\u0443\u0433\u043e\u0439, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044f \u044d\u0444\u0444\u0435\u043a\u0442 \u043c\u0435\u0442\u0430\u0441\u0444\u0435\u0440 \u0434\u043b\u044f \u043f\u0435\u0440\u0435\u0445\u043e\u0434\u0430.<\/p>\n<p>\u0420\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442 \u043f\u043e\u0442\u0440\u044f\u0441\u0430\u044e\u0449\u0438\u0439!<\/p>\n<p>\u0412\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u0438\u0437\u043c\u0435\u043d\u0438\u0442\u044c \u043e\u0449\u0443\u0449\u0435\u043d\u0438\u0435 \u044d\u0444\u0444\u0435\u043a\u0442\u0430, \u0438\u0437\u043c\u0435\u043d\u0438\u0432 \u0441\u043a\u043e\u0440\u043e\u0441\u0442\u044c \u0438 \u043d\u0430\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u0440\u0430\u0437\u043b\u0438\u0447\u043d\u044b\u0445 \u0447\u0430\u0441\u0442\u0435\u0439 \u0430\u043d\u0438\u043c\u0430\u0446\u0438\u0438; \u0438 \u0442\u0435\u043c, \u043d\u0430\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0441\u0438\u043b\u044c\u043d\u043e \u0440\u0430\u0437\u043c\u044b\u0442\u044b\u0435 \u043f\u0440\u043e\u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0432\u043e \u0432\u0440\u0435\u043c\u044f \u0430\u043d\u0438\u043c\u0430\u0446\u0438\u0438.<\/p>\n<p>\u041d\u043e \u044f \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0438\u043b, \u0447\u0442\u043e \u043c\u043e\u0438 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f \u0432\u044b\u0433\u043b\u044f\u0434\u044f\u0442 \u0434\u043e\u0432\u043e\u043b\u044c\u043d\u043e \u043a\u0440\u0430\u0441\u0438\u0432\u043e \u0438 \u0434\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u043e \u0445\u043e\u0440\u043e\u0448\u043e \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u044e\u0442 \u0441\u0442\u0438\u043b\u044e \u044d\u0444\u0444\u0435\u043a\u0442\u0430 \u0432 \u0438\u0441\u0445\u043e\u0434\u043d\u043e\u043c \u0432\u0438\u0434\u0435\u043e.<\/p>\n<h4>\u041f\u043e\u043b\u0435\u0437\u043d\u044b\u0435 \u0441\u0441\u044b\u043b\u043a\u0438<\/h4>\n<ul>\n<li>\n<p><a href=\"https:\/\/www.gamedev.net\/tutorials\/_\/technical\/graphics-programming-and-theory\/exploring-metaballs-and-isosurfaces-in-2d-r2556\/\">Exploring Metaballs and Isosurfaces in 2D<\/a><\/p>\n<\/li>\n<li>\n<p><a href=\"https:\/\/blobs.webflow.io\/\">Make and animate Metaballs with Webflow<\/a><\/p>\n<\/li>\n<li>\n<p><a href=\"http:\/\/jamie-wong.com\/2014\/08\/19\/metaballs-and-marching-squares\/\">Metaballs and Marching Squares<\/a><\/p>\n<\/li>\n<li>\n<p><a href=\"https:\/\/gist.github.com\/robb\/094f4f946d7f02c69b65bed19b0f25d9\">GooeyView.swift<\/a><\/p>\n<\/li>\n<li>\n<p><a href=\"https:\/\/github.com\/rnkyr\/metaballs\">Blob effect implementation with UIKIt<\/a><\/p>\n<\/li>\n<li>\n<p><a href=\"https:\/\/github.com\/dabing1022\/DBMetaballLoading\">Metaball loading written in Swift<\/a><\/p>\n<\/li>\n<li>\n<p><a href=\"https:\/\/github.com\/FlexMonkey\/Globular\">Colourful SpriteKit Metaballs Controlled by 3D Touch<\/a><\/p>\n<\/li>\n<\/ul>\n<hr\/>\n<blockquote>\n<p>\u041f\u0440\u0438\u0433\u043b\u0430\u0448\u0430\u0435\u043c \u0432\u0441\u0435\u0445 \u0436\u0435\u043b\u0430\u044e\u0449\u0438\u0445 \u043d\u0430 \u043e\u0442\u043a\u0440\u044b\u0442\u044b\u0439 \u0443\u0440\u043e\u043a \u00ab<strong>\u0421\u043e\u0432\u0440\u0435\u043c\u0435\u043d\u043d\u043e\u0435 KMM \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435<\/strong>\u00bb. \u041d\u0430 \u043d\u0430\u0448\u0435\u043c \u043e\u0442\u043a\u0440\u044b\u0442\u043e\u043c \u0437\u0430\u043d\u044f\u0442\u0438\u0438 \u043f\u043e\u0441\u043c\u043e\u0442\u0440\u0438\u043c, \u043a\u0430\u043a \u043f\u0440\u0435\u0432\u0440\u0430\u0442\u0438\u0442\u044c Android \u0432 iOS, \u043c\u0438\u0433\u0440\u0438\u0440\u043e\u0432\u0430\u0432 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e KMM, \u0438 \u043a\u0430\u043a\u0438\u0435 \u043f\u043e\u0434\u0432\u043e\u0434\u043d\u044b\u0435 \u043a\u0430\u043c\u043d\u0438 \u0432\u0441\u0442\u0440\u0435\u0447\u0430\u044e\u0442\u0441\u044f. \u0420\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u044f <a href=\"https:\/\/otus.pw\/NX0R\/\"><strong>\u0434\u043e\u0441\u0442\u0443\u043f\u043d\u0430 \u043f\u043e \u0441\u0441\u044b\u043b\u043a\u0435.<\/strong><\/a><\/p>\n<\/blockquote>\n<\/div>\n<\/div>\n<\/div>\n<div class=\"v-portal\" style=\"display:none;\"><\/div>\n<\/div>\n<p> <!----> <!----><br \/> \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\/company\/otus\/blog\/681820\/\"> https:\/\/habr.com\/ru\/company\/otus\/blog\/681820\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<div><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-2\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<figure class=\"full-width\"><figcaption><\/figcaption><\/figure>\n<p>\u0421\u0435\u0440\u0438\u044f, \u043f\u043e\u0441\u0432\u044f\u0449\u0435\u043d\u043d\u0430\u044f \u0432\u043e\u0441\u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044e \u043a\u043b\u0430\u0441\u0441\u043d\u044b\u0445 \u0434\u0435\u043c\u043e\u043a \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u0441\u043a\u043e\u0433\u043e \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0430, \u043d\u0430 \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u044f \u043d\u0430\u0442\u043a\u043d\u0443\u043b\u0441\u044f \u043d\u0430 \u043f\u0440\u043e\u0441\u0442\u043e\u0440\u0430\u0445 \u0438\u043d\u0442\u0435\u0440\u043d\u0435\u0442\u0430. \u0421\u0435\u0433\u043e\u0434\u043d\u044f \u043c\u044b \u0440\u0435\u0430\u043b\u0438\u0437\u0443\u0435\u043c \u043c\u043e\u0440\u0444\u0438\u043d\u0433 \u043c\u0435\u0436\u0434\u0443 \u0440\u0430\u0437\u043d\u044b\u043c\u0438 \u0444\u043e\u0440\u043c\u0430\u043c\u0438 \u0438\u043a\u043e\u043d\u043e\u043a \u0438 \u0440\u0430\u0437\u0431\u0435\u0440\u0435\u043c\u0441\u044f, \u0447\u0442\u043e \u0442\u0430\u043a\u043e\u0435 Metaballs.<\/p>\n<p>\u041d\u0430 \u0434\u043d\u044f\u0445 \u0432 \u0422\u0432\u0438\u0442\u0442\u0435\u0440\u0435 \u044f \u043d\u0430\u0442\u043a\u043d\u0443\u043b\u0441\u044f <a href=\"https:\/\/twitter.com\/DLX\/status\/1541101687267594252\">\u043d\u0430 \u0442\u0432\u0438\u0442, \u0434\u0435\u043c\u043e\u043d\u0441\u0442\u0440\u0438\u0440\u0443\u044e\u0449\u0438\u0439 \u043e\u0434\u043d\u0443 \u043a\u043b\u0430\u0441\u0441\u043d\u0443\u044e \u0442\u0435\u0445\u043d\u0438\u043a\u0443 \u0432 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u0441\u043a\u043e\u043c \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0435<\/a>, \u0438 \u0437\u0430\u0438\u043d\u0442\u0435\u0440\u0435\u0441\u043e\u0432\u0430\u043b\u0441\u044f, \u043a\u0430\u043a \u043e\u043d\u0430 \u0431\u044b\u043b\u0430 \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u043d\u0430.<\/p>\n<p>\u0423\u0432\u0438\u0434\u0435\u0432 \u044d\u0442\u043e\u0442 \u0442\u0432\u0438\u0442, \u044f \u0432\u0441\u043f\u043e\u043c\u043d\u0438\u043b \u043e\u0431 \u0438\u0434\u0435\u0435, \u0432\u0435\u0440\u0442\u0435\u0432\u0448\u0435\u0439\u0441\u044f \u0443 \u043c\u0435\u043d\u044f \u0432 \u0433\u043e\u043b\u043e\u0432\u0435 \u043d\u0430 \u043f\u0440\u043e\u0442\u044f\u0436\u0435\u043d\u0438\u0438 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u0438\u0445 \u043b\u0435\u0442. \u0418\u0434\u0435\u044f \u0437\u0430\u043a\u043b\u044e\u0447\u0430\u043b\u0430\u0441\u044c \u0432 \u0442\u043e\u043c, \u0447\u0442\u043e\u0431\u044b \u043d\u0430\u043f\u0438\u0441\u0430\u0442\u044c \u0441\u0435\u0440\u0438\u044e \u0441\u0442\u0430\u0442\u0435\u0439, \u043f\u043e\u0441\u0432\u044f\u0449\u0435\u043d\u043d\u044b\u0445 \u0432\u043e\u0441\u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044e \u0440\u0430\u0437\u043b\u0438\u0447\u043d\u044b\u0445 \u043a\u043b\u0430\u0441\u0441\u043d\u044b\u0445 \u0448\u0442\u0443\u043a \u0438\u0437 \u043a\u043e\u043d\u0446\u0435\u043f\u0442\u0443\u0430\u043b\u044c\u043d\u044b\u0445 \u0432\u0438\u0434\u0435\u043e, \u043f\u043e\u0441\u0442\u043e\u0432 \u043d\u0430 Dribbble \u0438\u043b\u0438 \u043d\u0435\u043e\u0431\u044b\u0447\u043d\u044b\u0445 \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u043e\u0432 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u0441\u043a\u043e\u0433\u043e \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0430 \u0438\u0437 \u0434\u0440\u0443\u0433\u0438\u0445 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0439.<\/p>\n<p>\u0418\u0442\u0430\u043a, \u0432 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u043f\u0435\u0440\u0432\u043e\u0439 \u0441\u0442\u0430\u0442\u044c\u0438 \u0438\u0437 (\u044f \u043d\u0430\u0434\u0435\u044e\u0441\u044c) \u0431\u043e\u043b\u044c\u0448\u043e\u0439 \u0441\u0435\u0440\u0438\u0438, \u0434\u0430\u0432\u0430\u0439\u0442\u0435 \u0440\u0430\u0437\u0431\u0435\u0440\u0435\u043c\u0441\u044f, \u043a\u0430\u043a \u0441\u043e\u0437\u0434\u0430\u0442\u044c \u043a\u043b\u0430\u0441\u0441\u043d\u044b\u0439 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u0441\u043a\u043e\u0433\u043e \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0430: \u201cShape Morphing\u201d \u043d\u0430 iOS.<\/p>\n<h3>Metaballs<\/h3>\n<p>\u0422\u0435\u0445\u043d\u0438\u043a\u0430 \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u043e\u0431\u044a\u0435\u043a\u0442\u043e\u0432, \u043e\u0440\u0433\u0430\u043d\u0438\u0447\u0435\u0441\u043a\u0438 \u043c\u043e\u0440\u0444\u0438\u0440\u0443\u044e\u0449\u0438\u0445 \u043c\u0435\u0436\u0434\u0443 \u0441\u043e\u0431\u043e\u0439 \u0438 \u0434\u0440\u0443\u0433 \u0432 \u0434\u0440\u0443\u0433\u0430, \u0438\u0437\u0432\u0435\u0441\u0442\u043d\u0430 \u043a\u0430\u043a <a href=\"https:\/\/en.wikipedia.org\/wiki\/Metaballs\">metaballs<\/a> (\u0438\u043b\u0438 \u043c\u0435\u0442\u0430\u0441\u0444\u0435\u0440\u044b).<\/p>\n<p>\u041a\u043e\u0433\u0434\u0430 \u0434\u0432\u0430 \u043e\u0431\u044a\u0435\u043a\u0442\u0430 \u043f\u0440\u0438\u0431\u043b\u0438\u0436\u0430\u044e\u0442\u0441\u044f \u0434\u0440\u0443\u0433 \u043a \u0434\u0440\u0443\u0433\u0443, \u0432\u043c\u0435\u0441\u0442\u043e \u0442\u043e\u0433\u043e, \u0447\u0442\u043e\u0431\u044b \u0441\u043f\u043e\u043a\u043e\u0439\u043d\u043e \u0436\u0434\u0430\u0442\u044c, \u043f\u043e\u043a\u0430 \u043e\u043d\u0438 \u043d\u0430\u0447\u043d\u0443\u0442 \u043f\u0435\u0440\u0435\u043a\u0440\u044b\u0432\u0430\u0442\u044c\u0441\u044f\u2026\u00a0<\/p>\n<figure class=\"\"><figcaption><\/figcaption><\/figure>\n<p>\u2026 \u044d\u0442\u0438 \u0434\u0432\u0430 \u043e\u0431\u044a\u0435\u043a\u0442\u0430 \u043d\u0430\u0447\u043d\u0443\u0442 \u0440\u0430\u0441\u0442\u044f\u0433\u0438\u0432\u0430\u0442\u044c\u0441\u044f \u0434\u0440\u0443\u0433 \u043a \u0434\u0440\u0443\u0433\u0443 \u0438 \u043e\u0431\u044a\u0435\u0434\u0438\u043d\u044f\u0442\u044c\u0441\u044f.\u00a0<\/p>\n<figure class=\"\"><figcaption><\/figcaption><\/figure>\n<h2>\u0420\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f \u0432 iOS<\/h2>\n<p>\u0427\u0442\u043e\u0431\u044b \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u0442\u044c \u044d\u0442\u043e\u0442 \u044d\u0444\u0444\u0435\u043a\u0442, \u043d\u0430\u043c \u043d\u0443\u0436\u043d\u043e \u043e\u0431\u044a\u0435\u0434\u0438\u043d\u0438\u0442\u044c \u0432\u043c\u0435\u0441\u0442\u0435 2 \u0440\u0430\u0437\u043d\u044b\u0445 \u044d\u0444\u0444\u0435\u043a\u0442\u0430 (\u043f\u043e \u043a\u0440\u0430\u0439\u043d\u0435\u0439 \u043c\u0435\u0440\u0435, \u0432 \u0440\u0430\u043c\u043a\u0430\u0445 \u0442\u043e\u0433\u043e \u0441\u043f\u043e\u0441\u043e\u0431\u0430, \u043a\u043e\u0442\u043e\u0440\u044b\u043c \u043c\u044b \u0441\u043e\u0431\u0438\u0440\u0430\u0435\u043c\u0441\u044f \u044d\u0442\u043e \u0441\u0434\u0435\u043b\u0430\u0442\u044c, \u043d\u043e \u0435\u0441\u0442\u044c \u0435\u0449\u0435 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0434\u0440\u0443\u0433\u0438\u0445 \u0441\u043f\u043e\u0441\u043e\u0431\u043e\u0432 \u0434\u043e\u0431\u0438\u0442\u044c\u0441\u044f \u044d\u0442\u043e\u0433\u043e \u044d\u0444\u0444\u0435\u043a\u0442\u0430, \u0441\u0441\u044b\u043b\u043a\u0438 \u043d\u0430 \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043f\u0440\u0438\u0432\u0435\u0434\u0435\u043d\u044b \u0432 \u043a\u043e\u043d\u0446\u0435 \u0441\u0442\u0430\u0442\u044c\u0438).<\/p>\n<p>\u041d\u0430\u043c \u043d\u0443\u0436\u043d\u043e \u043e\u0442\u0440\u0435\u043d\u0434\u0435\u0440\u0438\u0442\u044c \u043d\u0430\u0448\u0438 \u0444\u043e\u0440\u043c\u044b, \u0430 \u0437\u0430\u0442\u0435\u043c \u043f\u0440\u0438\u043c\u0435\u043d\u0438\u0442\u044c \u043f\u043e\u0432\u0435\u0440\u0445 \u043e\u0442\u0440\u0435\u043d\u0434\u0435\u0440\u0435\u043d\u043d\u043e\u0433\u043e \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f \u044d\u0442\u0438 \u044d\u0444\u0444\u0435\u043a\u0442\u044b \u0432 \u0440\u0430\u043c\u043a\u0430\u0445 \u044d\u0442\u0430\u043f\u0430 \u043f\u043e\u0441\u0442\u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0438.<\/p>\n<p>\u041a\u0430\u0436\u0435\u0442\u0441\u044f \u0443 CALayer \u0435\u0441\u0442\u044c <a href=\"https:\/\/developer.apple.com\/documentation\/quartzcore\/calayer\/1410901-filters\">\u0441\u0432\u043e\u0439\u0441\u0442\u0432\u043e<\/a> <a href=\"https:\/\/developer.apple.com\/documentation\/quartzcore\/calayer\/1410901-filters\">.filters <\/a>\u043a\u043e\u0442\u043e\u0440\u043e\u0435 \u0438\u0434\u0435\u0430\u043b\u044c\u043d\u043e \u043f\u043e\u0434\u0445\u043e\u0434\u0438\u0442 \u0434\u043b\u044f \u044d\u0442\u043e\u0433\u043e!<\/p>\n<blockquote>\n<p><em>\u041c\u0430\u0441\u0441\u0438\u0432 Core Image \u0444\u0438\u043b\u044c\u0442\u0440\u043e\u0432 \u043f\u0440\u0438\u043c\u0435\u043d\u044f\u0435\u043c\u044b\u0445 \u043a \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u043c\u043e\u043c\u0443 \u0441\u043b\u043e\u044f \u0438 \u0435\u0433\u043e \u043f\u043e\u0434\u0441\u043b\u043e\u044f\u043c. Animatable.<\/em><\/p>\n<\/blockquote>\n<p>\u041f\u043e\u043a\u0430 \u0432\u044b \u043d\u0435 \u0434\u043e\u0431\u0435\u0440\u0435\u0442\u0435\u0441\u044c \u0434\u043e \u043d\u0438\u0437\u0430 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u0446\u0438\u0438.<\/p>\n<blockquote>\n<p><em>\u042d\u0442\u043e \u0441\u0432\u043e\u0439\u0441\u0442\u0432\u043e \u043d\u0435 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442\u0441\u044f \u0434\u043b\u044f \u0441\u043b\u043e\u0435\u0432 \u0432 iOS<\/em><\/p>\n<\/blockquote>\n<p>\u041a \u0441\u043e\u0436\u0430\u043b\u0435\u043d\u0438\u044e, \u0442\u0430\u043a\u0438\u0435 \u044d\u0444\u0444\u0435\u043a\u0442\u044b \u0440\u0435\u043d\u0434\u0435\u0440\u0438\u043d\u0433\u0430 \u043d\u0435 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u044e\u0442\u0441\u044f \u0432 iOS.<\/p>\n<p>\u041f\u043e\u044d\u0442\u043e\u043c\u0443 \u0432\u043c\u0435\u0441\u0442\u043e \u044d\u0442\u043e\u0433\u043e \u043d\u0430\u043c \u043d\u0443\u0436\u043d\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c API \u0434\u043b\u044f \u0440\u0435\u043d\u0434\u0435\u0440\u0438\u043d\u0433\u0430, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u043d\u0430\u043c \u0434\u0435\u043b\u0430\u0442\u044c \u043f\u043e\u0441\u0442\u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0443 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u0430 \u0440\u0435\u043d\u0434\u0435\u0440\u0438\u043d\u0433\u0430.<\/p>\n<p>\u0412 iOS \u0443 \u043d\u0430\u0441 \u0435\u0441\u0442\u044c \u0432\u044b\u0431\u043e\u0440 \u043c\u0435\u0436\u0434\u0443 SpriteKit \u0438 SceneKit.<\/p>\n<p>\u0412 SceneKit \u0435\u0441\u0442\u044c \u043a\u0440\u0443\u0442\u043e\u0439 \u0442\u0440\u044e\u043a, \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u044e\u0449\u0438\u0439 \u0432\u0430\u043c \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c UIView \u0432 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u043c\u0430\u0442\u0435\u0440\u0438\u0430\u043b\u0430 \u0443\u0437\u043b\u0430, \u0447\u0442\u043e \u0431\u044b\u043b\u043e \u0431\u044b \u043e\u0447\u0435\u043d\u044c \u043f\u043e\u043b\u0435\u0437\u043d\u043e \u043f\u0440\u0438 \u043f\u0440\u0438\u043c\u0435\u043d\u0435\u043d\u0438\u0438 \u044d\u0442\u043e\u0433\u043e \u0442\u0440\u044e\u043a\u0430 \u043a UIView, \u043f\u043e\u0441\u043a\u043e\u043b\u044c\u043a\u0443 \u043c\u044b \u043c\u043e\u0433\u043b\u0438 \u0431\u044b \u0440\u0430\u0437\u043c\u0435\u0449\u0430\u0442\u044c \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u0432\u043d\u0443\u0442\u0440\u0438 \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u0438\u044f SceneKit.<\/p>\n<p>\u041e\u0434\u043d\u0430\u043a\u043e \u043f\u043e\u043a\u0430 \u044f \u043f\u0440\u043e\u0441\u0442\u043e \u0431\u0443\u0434\u0443 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c SpriteKit, \u0442\u0430\u043a \u043a\u0430\u043a \u043c\u043d\u0435 \u043d\u0435 \u043d\u0443\u0436\u043d\u043e \u043d\u0438\u0447\u0435\u0433\u043e \u0431\u043e\u043b\u044c\u0448\u0435\u0433\u043e \u0434\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u043f\u0440\u0438\u043c\u0435\u0440\u0430, \u0438 \u0441 \u043d\u0438\u043c \u043f\u0440\u043e\u0449\u0435 \u0440\u0430\u0431\u043e\u0442\u0430\u0442\u044c.<\/p>\n<h2>\u0420\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f \u0441\u043e SpriteKit<\/h2>\n<p>\u0421\u043d\u0430\u0447\u0430\u043b\u0430 \u043d\u0430\u043c \u043d\u0443\u0436\u043d\u043e \u043f\u043e\u0434\u0433\u043e\u0442\u043e\u0432\u0438\u0442\u044c \u043d\u0430\u0448\u0443 \u0441\u0446\u0435\u043d\u0443:<\/p>\n<pre><code class=\"kotlin\">class SimpleViewController: UIViewController { \/\/ \u041d\u0430\u0448\u0438 SpriteKit \u043e\u0431\u044a\u0435\u043a\u0442\u044b\u00a0 let skView = SKView() let scene = SKScene() \/\/ \u0414\u0432\u0435 \u0441\u0444\u0435\u0440\u044b var blobOne: SKShapeNode? = nil var blobTwo: SKShapeNode? = nil override func viewDidLoad() { super.viewDidLoad() \/\/ \u041f\u043e\u0434\u0433\u043e\u0442\u043e\u0432\u043a\u0430 \u0441\u0446\u0435\u043d\u044b self.scene.scaleMode = .resizeFill self.scene.physicsWorld.gravity = CGVector(dx: 0, dy: 0) self.skView.presentScene(self.scene) self.view.addSubview(self.skView); \/\/ \u0421\u043e\u0437\u0434\u0430\u0435\u043c \u0434\u0432\u0435 \u0441\u0444\u0435\u0440\u044b { let blob = SKShapeNode(circleOfRadius: 50) blob.fillColor = UIColor.yellow blob.strokeColor = blob.fillColor self.scene.addChild(blob) self.blobOne = blob }(); { let blob = SKShapeNode(circleOfRadius: 50) blob.fillColor = UIColor.white blob.strokeColor = blob.fillColor self.scene.addChild(blob) self.blobTwo = blob }(); } \/\/ \u0423\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0435\u043c \u043f\u043e\u0437\u0438\u0446\u0438\u0438 \u0432\u0441\u0435\u0445 \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u043e\u0432 override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() let bounds = self.view.bounds self.skView.frame = bounds let center = CGPoint(x: bounds.midX, y: bounds.midY) self.blobOne?.position = center.applying(CGAffineTransform(translationX: -50, y: 0)) self.blobTwo?.position = center.applying(CGAffineTransform(translationX: 50, y: 0)) } }<\/code><\/pre>\n<p>\u00a0\u042d\u0442\u043e \u043d\u0430\u0448 \u0431\u0430\u0437\u043e\u0432\u044b\u0439 \u0441\u0435\u0442\u0430\u043f. \u0414\u0432\u0435 \u0441\u0444\u0435\u0440\u044b \u0440\u0430\u0441\u043f\u043e\u043b\u043e\u0436\u0435\u043d\u044b \u0440\u044f\u0434\u043e\u043c \u0434\u0440\u0443\u0433 \u0441 \u0434\u0440\u0443\u0433\u043e\u043c, \u0435\u0434\u0432\u0430 \u0441\u043e\u043f\u0440\u0438\u043a\u0430\u0441\u0430\u044f\u0441\u044c.<\/p>\n<figure class=\"\"><figcaption><\/figcaption><\/figure>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u0434\u0430\u0432\u0430\u0439\u0442\u0435 \u0434\u043e\u0431\u0430\u0432\u0438\u043c \u044d\u0444\u0444\u0435\u043a\u0442 \u0440\u0430\u0437\u043c\u044b\u0442\u0438\u044f \u0432 \u043a\u043e\u043d\u0446\u0435 <code>-viewDidLoad()<\/code>.<\/p>\n<pre><code class=\"kotlin\">\/\/ - \u0412 \u043d\u0430\u0447\u0430\u043b\u043e \u0444\u0430\u0439\u043b\u0430.... import CoreImage import CoreImage.CIFilterBuiltins \/\/ - \u0412\u043d\u0438\u0437\u0443 viewDidLoad \/\/ \u0421\u043e\u0437\u0434\u0430\u0435\u043c \u0444\u0438\u043b\u044c\u0442\u0440 let blur = CIFilter.gaussianBlur() blur.radius = 20 self.scene.filter = blur \/\/ \u0423\u0431\u0435\u0434\u0438\u0442\u0435\u0441\u044c, \u0447\u0442\u043e \u0441\u0446\u0435\u043d\u0430 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442 \u0441\u043e\u0437\u0434\u0430\u043d\u043d\u044b\u0439 \u043d\u0430\u043c\u0438 \u0444\u0438\u043b\u044c\u0442\u0440 self.scene.shouldEnableEffects = true<\/code><\/pre>\n<p>\u00a0<\/p>\n<figure class=\"\"><figcaption><\/figcaption><\/figure>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c, \u043a\u043e\u0433\u0434\u0430 \u043c\u044b \u044d\u0442\u043e \u0441\u0434\u0435\u043b\u0430\u043b\u0438, \u043c\u044b \u043c\u043e\u0436\u0435\u043c \u0432\u0438\u0434\u0435\u0442\u044c, \u043a\u0430\u043a \u0444\u0438\u0433\u0443\u0440\u044b \u0441\u043b\u0438\u0432\u0430\u044e\u0442\u0441\u044f \u0432 \u043e\u0434\u043d\u0443 \u0431\u043b\u0430\u0433\u043e\u0434\u0430\u0440\u044f \u043f\u0435\u0440\u0435\u043a\u0440\u044b\u0442\u0438\u044e \u0438\u0445 \u0440\u0430\u0437\u043c\u044b\u0442\u044b\u0445 \u0432\u0435\u0440\u0441\u0438\u0439.<\/p>\n<p>\u041e\u0434\u043d\u0430\u043a\u043e \u044d\u0442\u043e \u0432\u0441\u0435 \u0435\u0449\u0435 \u043d\u0435 \u043f\u043e\u0445\u043e\u0436\u0435 \u043d\u0430 \u0442\u043e\u0442 \u044d\u0444\u0444\u0435\u043a\u0442, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043d\u0430\u043c \u043d\u0443\u0436\u0435\u043d. \u0414\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u043d\u0430\u043c \u043d\u0443\u0436\u043d\u043e \u0431\u0443\u0434\u0435\u0442 \u043f\u0440\u0438\u043c\u0435\u043d\u0438\u0442\u044c \u0444\u0438\u043b\u044c\u0442\u0440 \u201c\u043f\u043e\u0440\u043e\u0433\u201d (threshold), \u0433\u0434\u0435 \u043c\u044b \u0434\u0435\u043b\u0430\u0435\u043c \u043f\u043e\u043b\u043d\u043e\u0441\u0442\u044c\u044e \u043d\u0435\u043f\u0440\u043e\u0437\u0440\u0430\u0447\u043d\u044b\u043c\u0438 \u0432\u0441\u0435, \u0447\u0442\u043e \u0432\u044b\u0448\u0435 \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u043d\u043e\u0433\u043e \u043f\u043e\u0440\u043e\u0433\u0430, \u0438 \u0441\u043a\u0440\u044b\u0432\u0430\u0435\u043c \u043a\u0430\u0436\u0434\u044b\u0439 \u043f\u0438\u043a\u0441\u0435\u043b\u044c \u043d\u0438\u0436\u0435 \u044d\u0442\u043e\u0433\u043e \u043f\u043e\u0440\u043e\u0433\u0430.<\/p>\n<p>\u042d\u0442\u043e \u043f\u043e\u0437\u0432\u043e\u043b\u0438\u0442 \u043d\u0430\u0448\u0438\u043c \u0444\u043e\u0440\u043c\u0430\u043c \u0441\u043e\u0445\u0440\u0430\u043d\u0438\u0442\u044c \u0447\u0435\u0442\u043a\u0438\u0435 \u0433\u0440\u0430\u043d\u0438\u0446\u044b \u0438 \u0432\u044b\u0433\u043b\u044f\u0434\u0435\u0442\u044c \u0441\u043e\u0435\u0434\u0438\u043d\u044f\u044e\u0449\u0438\u043c\u0438\u0441\u044f, \u0430 \u043d\u0435 \u043f\u0440\u043e\u0441\u0442\u043e \u0441\u043b\u0438\u0432\u0430\u044e\u0449\u0438\u043c\u0438\u0441\u044f \u0434\u0440\u0443\u0433 \u0441 \u0434\u0440\u0443\u0433\u043e\u043c.<\/p>\n<p>\u041c\u044b \u043d\u0435 \u043c\u043e\u0436\u0435\u043c \u043f\u0440\u0438\u043c\u0435\u043d\u044f\u0442\u044c \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0444\u0438\u043b\u044c\u0442\u0440\u043e\u0432 \u043a SpriteKit \u0441\u0446\u0435\u043d\u0435, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u043d\u0430\u043c \u043d\u0443\u0436\u043d\u043e \u0441\u043e\u0437\u0434\u0430\u0442\u044c \u043a\u043b\u0430\u0441\u0441, \u043d\u0430\u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0439 CIFilter, \u0438 \u0441\u043e\u0437\u0434\u0430\u0442\u044c \u0441\u043e\u0431\u0441\u0442\u0432\u0435\u043d\u043d\u044b\u0439 \u043a\u043e\u043c\u0431\u0438\u043d\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0439 \u0444\u0438\u043b\u044c\u0442\u0440.<\/p>\n<pre><code class=\"kotlin\">class MetaballEffectFilter: CIFilter { \/\/ \u0412\u043d\u0443\u0442\u0440\u0435\u043d\u043d\u0438\u0435 \u0444\u0438\u043b\u044c\u0442\u0440\u044b let blurFilter: CIFilter &amp; CIGaussianBlur = { let blur = CIFilter.gaussianBlur() blur.radius = 30 return blur }() let thresholdFilter = LumaThresholdFilter() \/\/ - \u0421\u0432\u043e\u0439\u0441\u0442\u0432\u0430 \u0434\u043e\u0447\u0435\u0440\u043d\u0435\u0433\u043e \u043a\u043b\u0430\u0441\u0441\u0430 CIFilter @objc dynamic var inputImage : CIImage? override var outputImage: CIImage! { guard let inputImage = self.inputImage else { return nil } \/\/ \u0420\u0430\u0437\u043c\u044b\u0442\u0438\u0435 \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f self.blurFilter.inputImage = inputImage let blurredOutput = self.blurFilter.outputImage \/\/ \u041e\u0431\u0440\u0435\u0437\u043a\u0430 \u043f\u043e \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u043d\u043e\u043c\u0443 \u043f\u043e\u0440\u043e\u0433\u0443\u00a0 self.thresholdFilter.inputImage = blurredOutput return self.thresholdFilter.outputImage } }<\/code><\/pre>\n<p>\u00a0\u0421\u043d\u0430\u0447\u0430\u043b\u0430 \u043c\u044b \u0440\u0430\u0437\u043c\u044b\u0432\u0430\u0435\u043c \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435, \u0437\u0430\u0442\u0435\u043c \u043e\u0431\u0440\u0435\u0437\u0430\u0435\u043c \u0432\u044b\u0432\u043e\u0434 \u043f\u043e \u043f\u043e\u0440\u043e\u0433\u043e\u0432\u043e\u043c\u0443 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044e.<\/p>\n<p>\u041d\u043e \u043e\u0442\u043a\u0443\u0434\u0430 \u0432\u0437\u044f\u043b\u0441\u044f \u044d\u0442\u043e\u0442 <code>LumaThresholdFilter<\/code> ? \u042d\u0442\u043e \u0435\u0449\u0435 \u043e\u0434\u0438\u043d \u043a\u043b\u0430\u0441\u0441, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043d\u0430\u043c \u043d\u0443\u0436\u043d\u043e \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u0442\u044c \u0441\u0430\u043c\u0438\u043c.<\/p>\n<p>\u041d\u0430\u0438\u0431\u043e\u043b\u0435\u0435 \u043f\u0440\u043e\u0441\u0442\u043e\u0439 \u0441\u043f\u043e\u0441\u043e\u0431 \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u044d\u0442\u043e \u2014 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c <code>Core Image Kernel Language API<\/code>, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0443\u0441\u0442\u0430\u0440\u0435\u043b. \u0412\u043c\u0435\u0441\u0442\u043e \u044d\u0442\u043e\u0433\u043e \u043d\u0430\u043c \u0441\u043b\u0435\u0434\u0443\u0435\u0442 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u043d\u043e\u0432\u044b\u0439 Metal Shader CoreImage Filter API.<\/p>\n<p>\u041e\u0434\u043d\u0430\u043a\u043e \u0434\u043b\u044f \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f \u043c\u0435\u0442\u0430\u043b\u043b\u0438\u0447\u0435\u0441\u043a\u0438\u0445 \u0448\u0435\u0439\u0434\u0435\u0440\u043e\u0432 CoreImage \u0442\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044f \u0435\u0449\u0435 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0448\u0430\u0433\u043e\u0432 \u043f\u043e \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0435 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u0438 \u0441\u0431\u043e\u0440\u043a\u0438 \u043f\u0443\u0442\u0435\u043c \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u0441\u043a\u0438\u0445 \u043f\u0440\u0430\u0432\u0438\u043b \u0438 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u043e\u0432 \u0441\u0431\u043e\u0440\u043a\u0438. \u041f\u043e\u044d\u0442\u043e\u043c\u0443 \u0441\u0435\u0439\u0447\u0430\u0441 \u044f \u043f\u043e\u043a\u0430\u0436\u0443 \u043f\u0440\u0438\u043c\u0435\u0440\u044b \u043a\u0430\u043a \u043c\u0435\u0442\u0430\u043b\u043b\u0438\u0447\u0435\u0441\u043a\u0438\u0445 \u0448\u0435\u0439\u0434\u0435\u0440\u043e\u0432, \u0442\u0430\u043a \u0438 \u0448\u0435\u0439\u0434\u0435\u0440\u043e\u0432 CoreImage, \u043d\u043e \u043f\u043e\u0437\u0436\u0435 \u0431\u0443\u0434\u0443 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0442\u043e\u043b\u044c\u043a\u043e <code>Core Image Kernel Language<\/code> \u0434\u043b\u044f \u043e\u0441\u0442\u0430\u043b\u044c\u043d\u044b\u0445 \u043f\u0440\u0438\u043c\u0435\u0440\u043e\u0432.<\/p>\n<p>Metal Shader<\/p>\n<pre><code>\/\/ LumaThreshold.ci.metal  #include &lt;metal_stdlib> using namespace metal; #include &lt;CoreImage\/CoreImage.h>  extern \"C\" float4 lumaThreshold(coreimage::sample_t pixelColor, float threshold, coreimage::destination destination) { float3 pixelRGB = pixelColor.rgb;  float luma = (pixelRGB.r * 0.2126) + (pixelRGB.g * 0.7152) + (pixelRGB.b * 0.0722);  return (luma > threshold) ? float4(1.0, 1.0, 1.0, 1.0) : float4(0.0, 0.0, 0.0, 0.0); }<\/code><\/pre>\n<p>\u00a0Core Image Kernel Language Shader  <\/p>\n<pre><code>\/\/ LumaThresholdFilter.swift  class LumaThresholdFilter: CIFilter { var threshold: CGFloat = 0.5  static let thresholdKernel = CIColorKernel(source:\"\"\" kernel vec4 thresholdFilter(__sample image, float threshold) { float luma = (image.r * 0.2126) + (image.g * 0.7152) + (image.b * 0.0722); return (luma > threshold) ? vec4(1.0, 1.0, 1.0, 1.0) : vec4(0.0, 0.0, 0.0, 0.0); } \"\"\")!  \/\/  @objc dynamic var inputImage : CIImage?  override var outputImage : CIImage! { guard let inputImage = self.inputImage else { return nil }  let arguments = [inputImage, Float(self.threshold)] as [Any]  return Self.thresholdKernel.apply(extent: inputImage.extent, arguments: arguments) }  }<\/code><\/pre>\n<p>\u041c\u044b \u043d\u0430\u043a\u043e\u043d\u0435\u0446 \u043f\u043e\u043b\u0443\u0447\u0438\u043b\u0438 \u0436\u0435\u043b\u0430\u0435\u043c\u044b\u0439 \u044d\u0444\u0444\u0435\u043a\u0442:<\/p>\n<figure class=\"\"><figcaption><\/figcaption><\/figure>\n<p>\u00a0<\/p>\n<h2>\u041e\u0442 \u043c\u0435\u0442\u0430\u0441\u0444\u0435\u0440 \u043a \u043c\u043e\u0440\u0444\u0438\u043d\u0433\u0443 \u0438\u043a\u043e\u043d\u043e\u043a<\/h2>\n<p>\u0427\u0442\u043e\u0436, \u043c\u044b \u0434\u043e\u0431\u0438\u043b\u0438\u0441\u044c \u044d\u0444\u0444\u0435\u043a\u0442\u0430 \u043c\u0435\u0442\u0430\u0441\u0444\u0435\u0440. \u041d\u043e \u044d\u0442\u043e \u0435\u0449\u0435 \u043d\u0435 \u043c\u043e\u0440\u0444\u0438\u043d\u0433 \u0438\u043a\u043e\u043d\u043e\u043a, \u043a\u0430\u043a \u043f\u043e\u043a\u0430\u0437\u0430\u043d\u043e \u0432 \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b\u044c\u043d\u043e\u043c \u0432\u0438\u0434\u0435\u043e \u0432 \u0422\u0432\u0438\u0442\u0442\u0435\u0440\u0435. \u041e\u0434\u043d\u0430\u043a\u043e \u0442\u0435\u043f\u0435\u0440\u044c \u0443 \u043d\u0430\u0441 \u0435\u0441\u0442\u044c \u0432\u0441\u0435 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e\u0435.<\/p>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u043d\u0430\u043c \u043f\u0440\u043e\u0441\u0442\u043e \u043d\u0443\u0436\u043d\u043e \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u043d\u043e\u0432\u0443\u044e \u0438\u043a\u043e\u043d\u043a\u0443 \u043f\u043e\u0432\u0435\u0440\u0445 \u0441\u0442\u0430\u0440\u043e\u0439, \u043f\u0440\u0438 \u044d\u0442\u043e\u043c \u043f\u043e\u0441\u0442\u0435\u043f\u0435\u043d\u043d\u043e \u0443\u0431\u0438\u0440\u0430\u044f \u0441\u0442\u0430\u0440\u0443\u044e \u0438\u043a\u043e\u043d\u043a\u0443.<\/p>\n<p>\u0414\u043b\u044f \u043d\u0430\u0447\u0430\u043b\u0430 \u043d\u0430\u043c \u043d\u0443\u0436\u043d\u043e \u043f\u043e\u043c\u0435\u043d\u044f\u0442\u044c \u0434\u0432\u0430 \u043a\u0440\u0443\u0433\u0430 \u043d\u0430 \u043e\u0434\u043d\u0443 \u0438\u043a\u043e\u043d\u043a\u0443. \u0422\u0435\u043f\u0435\u0440\u044c \u043d\u0430\u043c \u0442\u0430\u043a\u0436\u0435 \u043d\u0443\u0436\u043d\u043e \u0441\u043e\u0445\u0440\u0430\u043d\u0438\u0442\u044c \u0441\u0441\u044b\u043b\u043a\u0443 \u043d\u0430 \u0444\u0438\u043b\u044c\u0442\u0440, \u0447\u0442\u043e\u0431\u044b \u043c\u044b \u043c\u043e\u0433\u043b\u0438 \u0435\u0433\u043e \u0430\u043d\u0438\u043c\u0438\u0440\u043e\u0432\u0430\u0442\u044c.<\/p>\n<pre><code>var currentIcon: SKSpriteNode?  let filter = MetaballEffectFilter()<\/code><\/pre>\n<p>\u0418 \u0434\u0430\u0432\u0430\u0439\u0442\u0435 \u0434\u043e\u0431\u0430\u0432\u0438\u043c \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u043a\u043d\u043e\u043f\u043e\u043a \u043d\u0430 \u044d\u043a\u0440\u0430\u043d, \u0447\u0442\u043e\u0431\u044b \u043c\u044b \u043c\u043e\u0433\u043b\u0438 \u0438\u0445 \u0437\u0430\u0434\u0435\u0439\u0441\u0442\u0432\u043e\u0432\u0430\u0442\u044c.<\/p>\n<pre><code>\/\/ \u0421\u0432\u043e\u0439\u0441\u0442\u0432\u043e  lazy var buttons : [UIButton] = { [unowned self] in return [ \"circle.fill\", \"heart.fill\", \"star.fill\", \"bell.fill\", \"bookmark.fill\", \"tag.fill\", \"bolt.fill\",  \"play.fill\", \"pause.fill\", \"squareshape.fill\", \"key.fill\", \"hexagon.fill\", \"gearshape.fill\", \"car.fill\", ].map({ buttonName in  var config = UIButton.Configuration.filled() config.image = UIImage(systemName: buttonName) config.baseBackgroundColor = UIColor.white config.baseForegroundColor = UIColor.black  let button = UIButton(configuration: config)  button.addAction(UIAction(handler: { [weak self] _ in self?.animateIconChange(newIconName: buttonName, duration: 0.5) }), for: .touchUpInside)  return button }) }()   \/\/ ...in viewDidLoad()  for button in self.buttons { self.view.addSubview(button) }   \/\/ .. In viewDidLayoutSubviews()  let buttonSize: CGSize = CGSize(width: 70, height: 50)  var x: CGFloat = 0 var y: CGFloat = bounds.height - (self.view.safeAreaInsets.bottom + 5 + buttonSize.height)  for button in self.buttons { button.bounds = CGRect(origin: .zero, size: buttonSize) button.center.x = x + (buttonSize.width * 0.5) button.center.y = y + (buttonSize.height * 0.5)  x += buttonSize.width + 5  if x > bounds.size.width - (buttonSize.width + 5) { x = 0 y -= buttonSize.height + 5 } }<\/code><\/pre>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u0434\u0430\u0432\u0430\u0439\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u043c \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435 \u0438\u043a\u043e\u043d\u043a\u0438. (\u0418\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c <a href=\"https:\/\/gist.github.com\/kylehowells\/27e6e1bf42f283d22c3ae8bb9faaeece\">\u044d\u0442\u043e\u0442 UIImage<\/a> <a href=\"https:\/\/gist.github.com\/kylehowells\/27e6e1bf42f283d22c3ae8bb9faaeece\">\u0445\u0435\u043b\u043f\u0435\u0440<\/a>).<\/p>\n<pre><code>\/\/ ...\u0432 \u043a\u043e\u043d\u0446\u0435 viewDidLoad()  self.animateIconChange(newIconName: \"circle.fill\", duration: 0)   \/\/ \u0421\u043e\u0437\u0434\u0430\u0435\u043c \u043d\u043e\u0432\u044b\u0439 \u043c\u0435\u0442\u043e\u0434  func animateIconChange(newIconName: String, duration: CGFloat, showPlayIcon: Bool = false) { \/\/ \u0421\u043e\u0437\u0434\u0430\u0435\u043c \u043d\u043e\u0432\u0443\u044e \u0444\u043e\u0440\u043c\u0443 \u0434\u043b\u044f \u0438\u043a\u043e\u043d\u043a\u0438 let newIconShape: SKSpriteNode? = { let iconSize = CGSize(width: 80, height: 80)  guard let image = UIImage(systemName: newIconName)?.withTintColor(UIColor.white).resized(within: iconSize) else { return nil }  let texture = SKTexture(image: image) let sprite = SKSpriteNode(texture: texture, size: iconSize) return sprite }()  newIconShape?.position = CGPoint( x: self.view.bounds.midX, y: self.view.bounds.midY ) newIconShape?.alpha = 0  \/\/ \u0414\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u043c \u043d\u043e\u0432\u0443\u044e \u0438\u043a\u043e\u043d\u043a\u0443 if let newIconShape = newIconShape {<\/code><\/pre>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\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-336860","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/336860","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=336860"}],"version-history":[{"count":0,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/336860\/revisions"}],"wp:attachment":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=336860"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=336860"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=336860"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}