{"id":378456,"date":"2024-06-09T15:18:46","date_gmt":"2024-06-09T15:18:46","guid":{"rendered":"http:\/\/savepearlharbor.com\/?p=378456"},"modified":"-0001-11-30T00:00:00","modified_gmt":"2024-06-09T15:18:46","slug":"","status":"publish","type":"post","link":"https:\/\/savepearlharbor.com\/?p=378456","title":{"rendered":"<span>SwiftUI \u0438\u043d\u043f\u0443\u0442: \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u044b \u0438 \u0440\u0435\u0448\u0435\u043d\u0438\u044f<\/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<p>\u041f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u044c\u0442\u0435, \u0447\u0442\u043e \u0432\u0430\u043c \u043d\u0443\u0436\u043d\u043e \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u0442\u044c \u043a\u043e\u043c\u043f\u043b\u0435\u043a\u0441\u043d\u044b\u0439 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442 \u0438\u043d\u043f\u0443\u0442\u0430 \u043d\u0430 SwiftUI. \u0427\u0442\u043e \u043c\u043e\u0436\u0435\u0442 \u043f\u043e\u0439\u0442\u0438 \u043d\u0435 \u0442\u0430\u043a? \u041a\u0430\u043a\u0438\u0435 \u0441\u043b\u043e\u0436\u043d\u043e\u0441\u0442\u0438 \u043c\u043e\u0433\u0443\u0442 \u0432\u043e\u0437\u043d\u0438\u043a\u043d\u0443\u0442\u044c? \u041d\u0430 \u044d\u0442\u0438 \u0438 \u0434\u0440\u0443\u0433\u0438\u0435 \u0432\u043e\u043f\u0440\u043e\u0441\u044b \u043f\u043e\u0441\u0442\u0430\u0440\u0430\u044e\u0441\u044c \u043e\u0442\u0432\u0435\u0442\u0438\u0442\u044c \u0432 \u0441\u0442\u0430\u0442\u044c\u0435, \u0433\u0434\u0435 \u0440\u0435\u0430\u043b\u0438\u0437\u0443\u0435\u043c \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442 \u0441\u043e \u0441\u043a\u0440\u0438\u043d\u0430, \u0430 \u0442\u0430\u043a\u0436\u0435 \u0440\u0430\u0437\u0431\u0435\u0440\u0435\u043c \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u044b\u0435 \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u044b.<\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/4fe\/334\/18d\/4fe33418d5be0e91e390814c73f6854a.png\" width=\"1190\" height=\"462\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/4fe\/334\/18d\/4fe33418d5be0e91e390814c73f6854a.png\"\/><\/figure>\n<details class=\"spoiler\">\n<summary>\u0421\u043f\u043e\u0439\u043b\u0435\u0440<\/summary>\n<div class=\"spoiler__content\">\n<p>\u041e\u0434\u043d\u0438 \u0440\u0435\u0448\u0435\u043d\u0438\u044f, \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u043d\u044b\u0435 \u0432 \u0434\u0430\u043d\u043d\u043e\u0439 \u0441\u0442\u0430\u0442\u044c\u0435, \u0433\u0443\u0433\u043b\u044f\u0442\u0441\u044f \u0431\u0435\u0437 \u043e\u0441\u043e\u0431\u043e\u0433\u043e \u0442\u0440\u0443\u0434\u0430.<\/p>\n<p>\u0414\u0440\u0443\u0433\u0438\u0435 \u0436\u0435 \u0443\u043d\u0438\u043a\u0430\u043b\u044c\u043d\u044b \u0438 \u044f\u0432\u043b\u044f\u044e\u0442\u0441\u044f \u0441\u043e\u0431\u0441\u0442\u0432\u0435\u043d\u043d\u044b\u043c <s>\u0432\u0435\u043b\u043e\u0441\u0438\u043f\u0435\u0434\u043e\u043c<\/s> \u0438\u0437\u043e\u0431\u0440\u0435\u0442\u0435\u043d\u0438\u0435\u043c. \u0412\u043e\u0437\u043c\u043e\u0436\u043d\u043e \u043e\u043d\u0438 \u043d\u0435 \u0438\u0434\u0435\u0430\u043b\u044c\u043d\u044b, \u043d\u043e \u043e\u043d\u0438 \u0440\u0430\u0431\u043e\u0442\u0430\u044e\u0442. \u0415\u0441\u043b\u0438 \u0443 \u0432\u0430\u0441 \u0435\u0441\u0442\u044c \u0430\u043b\u044c\u0442\u0435\u0440\u043d\u0430\u0442\u0438\u0432\u043d\u044b\u0435 \u0440\u0435\u0448\u0435\u043d\u0438\u044f \u043f\u043e\u0434\u043e\u0431\u043d\u044b\u0445 \u043f\u0440\u043e\u0431\u043b\u0435\u043c \u2014 \u0432\u0435\u043b\u043a\u043e\u043c \u0432 \u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u0438 \ud83d\ude42<\/p>\n<\/div>\n<\/details>\n<h3>\u041f\u0435\u0440\u0432\u044b\u0435 \u0448\u0430\u0433\u0438<\/h3>\n<p>\u041d\u0430\u0431\u0440\u0430\u0441\u044b\u0432\u0430\u0435\u043c \u043a\u043e\u0434 \u0434\u043b\u044f \u0441\u0430\u043c\u043e\u0433\u043e \u0438\u043d\u043f\u0443\u0442\u0430:<\/p>\n<details class=\"spoiler\">\n<summary>XTextField.swift &#8212; \u043f\u0440\u043e\u043c\u0435\u0436\u0443\u0442\u043e\u0447\u043d\u044b\u0439 \u043a\u043e\u0434 #0<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"swift\">import SwiftUI  struct XTextField: View {          var text: String          var isEnabled: Bool = true     var hasError: Bool = false          var label: String = \"\"     var placeholder: String = \"\"          var trailingImage: UIImage? = nil          var captionText: String? = nil          var onTextChange: (String) -> Void          var onTrailingImageClick: () -> Void = { }          var body: some View {         VStack(alignment: .leading, spacing: 0) {             let shouldUseLabel = true \/\/ \u0418\u0437\u043c\u0435\u043d\u0438\u043c \u043f\u043e\u0441\u043b\u0435             Text(label)                 .font(labelFont)                 .foregroundColor(labelColor)                 .opacity(shouldUseLabel ? 1 : 0)                          Spacer().frame(height: 8)                          HStack(alignment: .center, spacing: 0) {                 ZStack(alignment: .leading) {                     Text(placeholder)                         .font(placeholderFont)                         .foregroundColor(placeholderColor)                         .opacity(shouldUseLabel ? 0 : 1)                     textField()                         .font(textFont)                         .foregroundColor(textColor)                         .accentColor(cursorColor)                 }                                  if let trailingImage = trailingImage {                     Spacer().frame(width: 16)                     Image(uiImage: trailingImage)                         .foregroundColor(trailingImageColor)                         .frame(width: 24, height: 24, alignment: .center)                         .onTapGesture { onTrailingImageClick() }                 }             }.frame(minHeight: 24)                          ZStack {                 Spacer()                 Rectangle()                     .fill(underlineColor)                     .frame(height: 1)             }.frame(height: 16, alignment: .bottom)                          if let captionText = captionText {                 Spacer().frame(height: 8)                 Text(captionText)                     .font(captionFont)                     .foregroundColor(captionColor)             }         }         .disabled(!isEnabled)         .opacity(isEnabled ? 1 : 0.4)     }          @ViewBuilder     private func textField() -> some View { \/\/ \u0432\u044b\u043d\u043e\u0441 \u0432 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u0443\u044e \u0444\u0443\u043d\u043a\u0446\u0438\u044e \u043f\u043e\u043c\u043e\u0436\u0435\u0442 \u0432 \u0434\u0430\u043b\u044c\u043d\u0435\u0439\u0448\u0435\u043c         TextField(\"\", text: Binding(             get: { text },             set: { onTextChange($0) }         ))     } }  \/\/ MARK: Colors private extension XTextField {          var labelColor: Color { Color.gray }          var placeholderColor: Color { Color.gray }          var textColor: Color { Color.black }          var cursorColor: Color {         if hasError {             Color.red         } else {             Color.green         }     }          var underlineColor: Color {         if hasError {             Color.red         } else {             Color.gray         }     }          var captionColor: Color {         if hasError {             Color.red         } else {             Color.gray         }     }          var trailingImageColor: Color { Color.gray } }  \/\/ MARK: Fonts private extension XTextField {          var labelFont: Font { Font.caption }          var placeholderFont: Font { Font.body }          var textFont: Font { Font.body }          var captionFont: Font { Font.caption } } <\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<p>\u0421\u0430\u043c\u0430 \u0432\u0435\u0440\u0441\u0442\u043a\u0430 \u043d\u0435 \u0442\u0430\u043a\u0430\u044f \u0443\u0436 \u0438 \u0441\u043b\u043e\u0436\u043d\u0430\u044f. \u041a\u043e\u043c\u043c\u0435\u043d\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0435\u0451 \u043d\u0435 \u0432\u0445\u043e\u0434\u0438\u043b\u043e \u0432 \u043f\u043b\u0430\u043d\u044b. \u042d\u0442\u043e\u0442 \u043a\u043e\u0434 \u043d\u0430\u043c \u043d\u0443\u0436\u0435\u043d \u043a\u0430\u043a \u043e\u0442\u043f\u0440\u0430\u0432\u043d\u0430\u044f \u0442\u043e\u0447\u043a\u0430 \u0434\u043b\u044f \u0440\u0435\u0448\u0435\u043d\u0438\u044f \u0434\u0430\u043b\u044c\u043d\u0435\u0439\u0448\u0438\u0445 \u043f\u0440\u043e\u0431\u043b\u0435\u043c. <\/p>\n<p>\u041d\u0430\u0431\u0440\u0430\u0441\u044b\u0432\u0430\u0435\u043c \u043a\u043e\u0434 \u0434\u043b\u044f \u0441\u043a\u0440\u0438\u043d\u0430, \u043d\u0430 \u043a\u043e\u0442\u043e\u0440\u043e\u043c \u043c\u044b \u0431\u0443\u0434\u0435\u043c \u0432\u0441\u0435 \u044d\u0442\u043e \u043e\u0431\u043a\u0430\u0442\u044b\u0432\u0430\u0442\u044c:<\/p>\n<details class=\"spoiler\">\n<summary>XScreen.swift &#8212; \u043f\u0440\u043e\u043c\u0435\u0436\u0443\u0442\u043e\u0447\u043d\u044b\u0439 \u043a\u043e\u0434 #0<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"swift\">import SwiftUI  struct XScreen: View {          @State private var text1 = \"TextField1\"     @State private var text2 = \"TextField2\"          var body: some View {         VStack {             XTextField(                 text: text1,                 hasError: false,                 label: \"TextField1\",                 placeholder: \"TextField1\",                 trailingImage: UIImage.add.withRenderingMode(.alwaysTemplate),                 captionText: \"TextField1\",                 onTextChange: { text in                     text1 = text                     print(\"TextField1: text changed to \\(text)\")                 }             ).padding(20)                          XTextField(                 text: text2,                 hasError: true,                 label: \"TextField2\",                 placeholder: \"TextField2\",                 trailingImage: UIImage.add.withRenderingMode(.alwaysTemplate),                 captionText: \"TextField2\",                 onTextChange: { text in                     text2 = text                     print(\"TextField2: text changed to \\(text)\")                 }             ).padding(20)         }     } } <\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<p> \u0417\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u043c, \u0441\u043c\u043e\u0442\u0440\u0438\u043c. \u0412\u0440\u043e\u0434\u0435 \u0434\u0430\u0436\u0435 \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442.<\/p>\n<figure class=\"\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/ae4\/4d7\/c86\/ae44d7c869562d59b9163eb6ae3a626a.gif\" alt=\"\u041d\u0430\u0447\u0430\u043b\u043e\" title=\"\u041d\u0430\u0447\u0430\u043b\u043e\" width=\"336\" height=\"440\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/ae4\/4d7\/c86\/ae44d7c869562d59b9163eb6ae3a626a.gif\"\/><\/p>\n<div><figcaption>\u041d\u0430\u0447\u0430\u043b\u043e<\/figcaption><\/div>\n<\/figure>\n<h3>\u041f\u0440\u043e\u0431\u043b\u0435\u043c\u0430 \u21161. \u0414\u0432\u043e\u0439\u043d\u043e\u0435 \u0441\u043e\u0431\u044b\u0442\u0438\u0435 \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u044f<\/h3>\n<p>\u0421\u043c\u043e\u0442\u0440\u0438\u043c \u0432 \u043b\u043e\u0433. \u041e\u0431\u043d\u0430\u0440\u0443\u0436\u0438\u0432\u0430\u0435\u043c, \u0447\u0442\u043e \u0441\u043e\u0431\u044b\u0442\u0438\u0435 \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u044f \u0442\u0435\u043a\u0441\u0442\u0430 (<code>onTextChange<\/code> ) \u0432\u044b\u0437\u044b\u0432\u0430\u0435\u0442\u0441\u044f \u0434\u0432\u0430\u0436\u0434\u044b:<\/p>\n<pre><code>TextField1: text changed to TextField1 TextField1: text changed to TextField1 TextField1: text changed to TextField1a TextField1: text changed to TextField1a TextField1: text changed to TextField1as TextField1: text changed to TextField1as TextField1: text changed to TextField1asd TextField1: text changed to TextField1asd TextField1: text changed to TextField1asda TextField1: text changed to TextField1asda<\/code><\/pre>\n<p>\u041e\u0447\u0435\u0432\u0438\u0434\u043d\u043e, \u0447\u0442\u043e \u0447\u0442\u043e-\u0442\u043e \u043f\u043e\u0448\u043b\u043e \u043d\u0435 \u0442\u0430\u043a&#8230; \u0413\u0443\u0433\u043b\u0438\u043c \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u0443, \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u043c <a href=\"https:\/\/stackoverflow.com\/questions\/70734379\/why-is-didset-called-twice-on-the-textfield-binding-in-swiftui\/74745555#74745555\" rel=\"noopener noreferrer nofollow\">\u0440\u0435\u0448\u0435\u043d\u0438\u0435<\/a>. \u0427\u0442\u043e \u0437\u0434\u0435\u0441\u044c \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0434\u0438\u0442? \u041f\u043e\u0447\u0435\u043c\u0443 \u0440\u0435\u0448\u0435\u043d\u0438\u0435 \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442? \u041d\u0430 \u044d\u0442\u0438 \u0432\u043e\u043f\u0440\u043e\u0441\u044b \u043e\u0442\u0432\u0435\u0442\u043e\u0432 \u0443 \u043c\u0435\u043d\u044f \u043d\u0435\u0442&#8230;<\/p>\n<p>\u0412\u0441\u0435, \u0447\u0442\u043e \u043d\u0443\u0436\u043d\u043e \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u2014 \u044d\u0442\u043e \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0432\u044b\u0437\u043e\u0432 <code>textFieldStyle(.plain)<\/code> \u0441\u0440\u0430\u0437\u0443 \u043f\u043e\u0441\u043b\u0435 <code>textField()<\/code>.<\/p>\n<p>\u041f\u043e\u0441\u043b\u0435 \u0437\u0430\u043f\u0443\u0441\u043a\u0430 \u0432\u0438\u0434\u0438\u043c, \u0447\u0442\u043e \u0432 \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u043d\u043e\u043c \u043b\u043e\u0433\u0435 \u0432\u0441\u0435 \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u043a\u0430\u043a \u0441\u043b\u0435\u0434\u0443\u0435\u0442:<\/p>\n<pre><code>TextField1: text changed to TextField1 TextField1: text changed to TextField1a TextField1: text changed to TextField1as TextField1: text changed to TextField1asd TextField1: text changed to TextField1asda<\/code><\/pre>\n<details class=\"spoiler\">\n<summary>XTextField.swift &#8212; \u043f\u0440\u043e\u043c\u0435\u0436\u0443\u0442\u043e\u0447\u043d\u044b\u0439 \u043a\u043e\u0434 #1<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"swift\">import SwiftUI   struct XTextField: View {          var text: String          var isEnabled: Bool = true     var hasError: Bool = false          var label: String = \"\"     var placeholder: String = \"\"          var trailingImage: UIImage? = nil          var captionText: String? = nil          var onTextChange: (String) -> Void          var onTrailingImageClick: () -> Void = { }          var body: some View {         VStack(alignment: .leading, spacing: 0) {             let shouldUseLabel = true \/\/ \u0418\u0437\u043c\u0435\u043d\u0438\u043c \u043f\u043e\u0441\u043b\u0435             Text(label)                 .font(labelFont)                 .foregroundColor(labelColor)                 .opacity(shouldUseLabel ? 1 : 0)                          Spacer().frame(height: 8)                          HStack(alignment: .center, spacing: 0) {                 ZStack(alignment: .leading) {                     Text(placeholder)                         .font(placeholderFont)                         .foregroundColor(placeholderColor)                         .opacity(shouldUseLabel ? 0 : 1)                     textField()                         .textFieldStyle(.plain) \/\/ https:\/\/stackoverflow.com\/a\/74745555                         .font(textFont)                         .foregroundColor(textColor)                         .accentColor(cursorColor)                 }                                  if let trailingImage = trailingImage {                     Spacer().frame(width: 16)                     Image(uiImage: trailingImage)                         .foregroundColor(trailingImageColor)                         .frame(width: 24, height: 24, alignment: .center)                         .onTapGesture { onTrailingImageClick() }                 }             }.frame(minHeight: 24)                          ZStack {                 Spacer()                 Rectangle()                     .fill(underlineColor)                     .frame(height: 1)             }.frame(height: 16, alignment: .bottom)                          if let captionText = captionText {                 Spacer().frame(height: 8)                 Text(captionText)                     .font(captionFont)                     .foregroundColor(captionColor)             }         }         .disabled(!isEnabled)         .opacity(isEnabled ? 1 : 0.4)     }          @ViewBuilder     private func textField() -> some View {         TextField(\"\", text: Binding(             get: { text },             set: { onTextChange($0) }         ))     } }  \/\/ MARK: Colors private extension XTextField {          var labelColor: Color { Color.gray }          var placeholderColor: Color { Color.gray }          var textColor: Color { Color.black }          var cursorColor: Color {         if hasError {             Color.red         } else {             Color.green         }     }          var underlineColor: Color {         if hasError {             Color.red         } else {             Color.gray         }     }          var captionColor: Color {         if hasError {             Color.red         } else {             Color.gray         }     }          var trailingImageColor: Color { Color.gray } }  \/\/ MARK: Fonts private extension XTextField {          var labelFont: Font { Font.caption }          var placeholderFont: Font { Font.body }          var textFont: Font { Font.body }          var captionFont: Font { Font.caption } } <\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<h3>\u041f\u0440\u043e\u0431\u043b\u0435\u043c\u0430 \u21162. \u041f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u044c<\/h3>\n<p>\u0412\u0440\u044f\u0434 \u043b\u0438 \u043d\u0430 \u0442\u0435\u043a\u0443\u0449\u0435\u043c \u044d\u0442\u0430\u043f\u0435 \u0432\u044b \u0437\u0430\u0434\u0443\u043c\u0430\u0435\u0442\u0435\u0441\u044c \u043e \u0442\u0430\u043a\u043e\u0439 <s>\u043d\u0435<\/s>\u0432\u0430\u0436\u043d\u043e\u0439 \u0448\u0442\u0443\u043a\u0435, \u043a\u0430\u043a \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u044c. \u0418 \u044f \u0442\u043e\u0436\u0435 \u043d\u0435 \u0437\u0430\u0434\u0443\u043c\u044b\u0432\u0430\u043b\u0441\u044f. \u041d\u043e, \u0432 \u0440\u0430\u043c\u043a\u0430\u0445 \u043f\u043e\u0432\u0435\u0441\u0442\u0432\u043e\u0432\u0430\u043d\u0438\u044f, \u043f\u0440\u0435\u0434\u043f\u043e\u043b\u043e\u0436\u0438\u043c, \u0447\u0442\u043e \u043c\u044b \u043e\u0431 \u044d\u0442\u043e\u043c \u0432\u0441\u0435 \u0436\u0435 \u0432\u0441\u043f\u043e\u043c\u043d\u0438\u043b\u0438 \u0441\u0435\u0439\u0447\u0430\u0441.<\/p>\n<p>\u0412\u043e\u043e\u0431\u0449\u0435, \u0432 \u0438\u043d\u0442\u0435\u0440\u043d\u0435\u0442\u0430\u0445 \u043c\u043d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043d\u0430\u0439\u0442\u0438 \u043c\u0430\u043b\u043e \u0447\u0435\u0433\u043e \u0440\u0435\u0430\u043b\u044c\u043d\u043e \u0433\u043e\u0434\u043d\u043e\u0433\u043e \u043d\u0430 \u0442\u0435\u043c\u0443 \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u0438 \u0438 <strong>SwiftUI<\/strong>. \u041d\u043e, \u043f\u0440\u043e\u0433\u0443\u043b\u044f\u0432\u0448\u0438\u0441\u044c \u043f\u043e <em>forums.developer.apple<\/em> \u0438 \u043f\u043e\u0441\u043c\u043e\u0442\u0440\u0435\u0432 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u043e\u0444\u0438\u0446\u0438\u0430\u043b\u044c\u043d\u044b\u0445 \u0432\u0438\u0434\u0435\u043e \u043f\u043e \u0434\u0430\u043d\u043d\u043e\u0439 \u0442\u0435\u043c\u0435, \u043c\u043e\u0436\u043d\u043e \u043f\u0440\u0438\u0439\u0442\u0438 \u043a \u0432\u044b\u0432\u043e\u0434\u0443, \u0447\u0442\u043e \u043c\u0435\u0442\u043e\u0434 <code>body<\/code> \u043d\u0435 \u0434\u043e\u043b\u0436\u0435\u043d \u0432\u044b\u0437\u044b\u0432\u0430\u0442\u044c\u0441\u044f \u043b\u0438\u0448\u043d\u0438\u0439 \u0440\u0430\u0437, \u0442\u0430\u043a \u043a\u0430\u043a \u044d\u0442\u043e \u043c\u043e\u0436\u0435\u0442 \u043e\u043a\u0430\u0437\u044b\u0432\u0430\u0442\u044c \u043d\u0435\u0433\u0430\u0442\u0438\u0432\u043d\u043e\u0435 \u0432\u043b\u0438\u044f\u043d\u0438\u0435 \u043d\u0430 \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u044c. \u0421\u0430\u043c \u0436\u0435 \u043f\u043e\u0432\u0442\u043e\u0440\u043d\u044b\u0439 \u0432\u044b\u0437\u043e\u0432 \u0434\u0430\u043d\u043d\u043e\u0433\u043e \u043c\u0435\u0442\u043e\u0434\u0430 \u043d\u0430\u0437\u044b\u0432\u0430\u044e\u0442 (\u043d\u043e \u043d\u0435 \u043e\u0431\u0449\u0435\u043f\u0440\u0438\u043d\u044f\u0442\u043e) <strong>body reinvoke<\/strong> \u0438\u043b\u0438 <strong>reevaluate<\/strong>. \u041c\u043d\u0435 \u043f\u0440\u0438\u0432\u044b\u0447\u043d\u0435\u0435 \u0432\u0442\u043e\u0440\u043e\u0439 \u0432\u0430\u0440\u0438\u0430\u043d\u0442, \u0435\u0433\u043e \u044f \u0438 \u0431\u0443\u0434\u0443 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0432 \u0434\u0430\u043b\u044c\u043d\u0435\u0439\u0448\u0435\u043c.<\/p>\n<details class=\"spoiler\">\n<summary>\u041f\u0430\u0441\u0445\u0430\u043b\u043a\u0430 \u0434\u043b\u044f \u0442\u0435\u0445, \u043a\u0442\u043e \u0437\u043d\u0430\u043a\u043e\u043c \u0441 Android<\/summary>\n<div class=\"spoiler__content\">\n<p>\u041d\u0430 \u0441\u0430\u043c\u043e\u043c \u0434\u0435\u043b\u0435 \u0435\u0449\u0435 \u043a\u0443\u0434\u0430 \u0431\u043e\u043b\u0435\u0435 \u043f\u0440\u0438\u0432\u044b\u0447\u043d\u043e \u043d\u0430\u0437\u044b\u0432\u0430\u0442\u044c \u044d\u0442\u043e <strong>\u0440\u0435\u043a\u043e\u043c\u043f\u043e\u0437\u0438\u0446\u0438\u0435\u0439<\/strong> \u043f\u043e \u0430\u043d\u0430\u043b\u043e\u0433\u0438\u0438 \u0441 <strong>Jetpack Compose<\/strong> \u0432 <strong>Android<\/strong>. \u041d\u043e \u043c\u044b \u043d\u0435 \u0432 <strong>Android<\/strong> \u0438 \u043d\u0435 \u0432 <strong>Jetpack Compose <\/strong>??<\/p>\n<\/div>\n<\/details>\n<p>\u041a\u0430\u043a \u0436\u0435 \u043d\u0430\u043c \u043e\u0442\u0441\u043b\u0435\u0434\u0438\u0442\u044c \u043f\u043e\u0432\u0442\u043e\u0440\u043d\u044b\u0439 \u0432\u044b\u0437\u043e\u0432 \u043c\u0435\u0442\u043e\u0434\u0430 <code>body<\/code>? \u041e\u0442\u0432\u0435\u0442 \u0435\u0441\u0442\u044c \u0432 <a href=\"https:\/\/www.hackingwithswift.com\/quick-start\/swiftui\/how-to-find-which-data-change-is-causing-a-swiftui-view-to-update\" rel=\"noopener noreferrer nofollow\">\u0434\u0430\u043d\u043d\u043e\u0439 \u0441\u0442\u0430\u0442\u044c\u0435<\/a>. \u041d\u043e, \u0435\u0441\u043b\u0438 \u043a\u043e\u0440\u043e\u0442\u043a\u043e, \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u044d\u0442\u043e \u043c\u043e\u0436\u043d\u043e \u0434\u0432\u0443\u043c\u044f \u0441\u043f\u043e\u0441\u043e\u0431\u0430\u043c\u0438:<\/p>\n<ul>\n<li>\n<p><code>let _ = Self._printChanges()<\/code><\/p>\n<\/li>\n<li>\n<p>\u0432\u044b\u0441\u0442\u0430\u0432\u043b\u044f\u044f \u0440\u0430\u043d\u0434\u043e\u043c\u043d\u044b\u0439 <code>background<\/code> \u043d\u0430\u0448\u0435\u0439 <strong>View<\/strong><\/p>\n<\/li>\n<\/ul>\n<p>\u0414\u043e\u0431\u0430\u0432\u0438\u043c \u0440\u0430\u043d\u0434\u043e\u043c\u043d\u044b\u0439 <code>background<\/code> \u043d\u0430\u0448\u0435\u043c\u0443 \u0438\u043d\u043f\u0443\u0442\u0443 \u0434\u043b\u044f \u043e\u0442\u0441\u043b\u0435\u0436\u0438\u0432\u0430\u043d\u0438\u044f \u0438 \u0437\u0430\u043f\u0443\u0441\u0442\u0438\u043c. <\/p>\n<pre><code class=\"swift\">struct XTextField: View {          ...          var body: some View {         VStack(alignment: .leading, spacing: 0) {             ...         }         .background(.random)     } }  extension ShapeStyle where Self == Color {     static var random: Color {         Color(             red: .random(in: 0...1),             green: .random(in: 0...1),             blue: .random(in: 0...1)         )     } }<\/code><\/pre>\n<figure class=\"\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/b25\/4a5\/a4d\/b254a5a4d1222fc5462d18135e8d32bc.gif\" width=\"336\" height=\"440\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/b25\/4a5\/a4d\/b254a5a4d1222fc5462d18135e8d32bc.gif\"\/><\/figure>\n<p>\u041c\u043e\u0436\u043d\u043e \u0437\u0430\u043c\u0435\u0442\u0438\u0442\u044c, \u0447\u0442\u043e \u043c\u0435\u043d\u044f\u044f \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435 \u043e\u0434\u043d\u043e\u0433\u043e \u0438\u043d\u043f\u0443\u0442\u0430, <strong>body reevaluate<\/strong> \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0434\u0438\u0442 \u0438 \u0443 \u0432\u0442\u043e\u0440\u043e\u0433\u043e \u0438\u043d\u043f\u0443\u0442\u0430 \u0442\u043e\u0436\u0435, \u0430 \u0442\u0430\u043a \u0431\u044b\u0442\u044c \u043d\u0435 \u0434\u043e\u043b\u0436\u043d\u043e. <\/p>\n<p>\u0414\u043e\u0431\u0430\u0432\u0438\u043c <code>_printChanges<\/code> \u0432 <code>XTextField.body<\/code>. \u041b\u043e\u0433 \u043d\u0435 \u043f\u043e\u043a\u0430\u0437\u044b\u0432\u0430\u0435\u0442 \u043d\u0438\u0447\u0435\u0433\u043e \u043f\u043e\u043b\u0435\u0437\u043d\u043e\u0433\u043e:<\/p>\n<pre><code>XTextField: @self changed. XTextField: @self changed.<\/code><\/pre>\n<p>\u0414\u043e\u043b\u0433\u0438\u043c\u0438 \u043f\u043e\u0438\u0441\u043a\u0430\u043c\u0438 \u043c\u043d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u0432\u044b\u044f\u0441\u043d\u0438\u0442\u044c, \u0447\u0442\u043e \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u0430 \u0437\u0430\u043a\u043b\u044e\u0447\u0430\u0435\u0442\u0441\u044f \u0432 \u0441\u0430\u043c\u043e\u043c \u0432\u044b\u0437\u043e\u0432\u0435 \u0438\u043d\u043f\u0443\u0442\u0430:<\/p>\n<pre><code class=\"swift\">            XTextField(                 text: text1,                 hasError: false,                 label: \"TextField1\",                 placeholder: \"TextField1\",                 trailingImage: UIImage.add.withRenderingMode(.alwaysTemplate),                 captionText: \"TextField1\",                 onTextChange: { text in                     text1 = text                     print(\"TextField1: text changed to \\(text)\")                 }             )<\/code><\/pre>\n<p>\u0410, \u0435\u0441\u043b\u0438 \u0431\u044b\u0442\u044c \u0442\u043e\u0447\u043d\u0435\u0435, \u0442\u043e \u0432 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0438 \u043a\u043b\u043e\u0436\u0443\u0440\u044b. \u0418 \u0441\u0435\u0439\u0447\u0430\u0441 \u044f \u043f\u043e\u0441\u0442\u0430\u0440\u0430\u044e\u0441\u044c \u043e\u0431\u044a\u044f\u0441\u043d\u0438\u0442\u044c, \u043f\u043e\u0447\u0435\u043c\u0443 \u044d\u0442\u043e \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0434\u0438\u0442.<\/p>\n<p>\u041d\u0430\u0448\u0438 \u0442\u0435\u043a\u0441\u0442\u0430 \u0445\u0440\u0430\u043d\u044f\u0442\u0441\u044f \u0432 <code>XScreen<\/code> \u0432 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 <code>@State<\/code> \u043f\u043e\u043b\u0435\u0439. \u041b\u044e\u0431\u043e\u0439 \u0438\u0437 <code>XTextField<\/code>&#8216;\u043e\u0432, \u043c\u0435\u043d\u044f\u044f \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435 \u0442\u0435\u043a\u0441\u0442\u043e\u0432\u043e\u0433\u043e \u043f\u043e\u043b\u044f, \u043e\u0431\u043d\u043e\u0432\u043b\u044f\u0435\u0442 \u0442\u0435\u043a\u0441\u0442 \u0432 <code>XScreen<\/code>. \u0418\u0437-\u0437\u0430 \u044d\u0442\u043e\u0433\u043e \u0432\u044b\u0437\u044b\u0432\u0430\u0435\u0442\u0441\u044f <code>XScreen.body<\/code>, \u0442\u0430\u043a \u043a\u0430\u043a \u0438\u0437\u043c\u0435\u043d\u0438\u043b\u043e\u0441\u044c \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435 \u043f\u043e\u043b\u044f, \u043f\u043e\u043c\u0435\u0447\u0435\u043d\u043d\u043e\u0433\u043e \u043a\u0430\u043a <code>@State<\/code>. \u0412 \u043c\u0435\u0442\u043e\u0434\u0435 <code>XScreen.body<\/code> \u043c\u044b \u0441\u0442\u0430\u043b\u043a\u0438\u0432\u0430\u0435\u043c\u0441\u044f \u0441 <code>XTextField<\/code>, <code>body<\/code> \u043a\u043e\u0442\u043e\u0440\u043e\u0433\u043e \u0431\u0443\u0434\u0435\u0442 \u0432\u044b\u0437\u0432\u0430\u043d \u0432 \u0442\u043e\u043c \u0441\u043b\u0443\u0447\u0430\u0435, \u0435\u0441\u043b\u0438 \u043a\u0430\u043a\u0438\u0435-\u0442\u043e <strong><em>\u0432\u0445\u043e\u0434\u043d\u044b\u0435 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b<\/em><\/strong> <strong><em>\u0438\u0437\u043c\u0435\u043d\u0438\u043b\u0438\u0441\u044c<\/em><\/strong>. \u0422\u0430\u043a\u0438\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c, \u043c\u0435\u043d\u044f\u044f \u0442\u0435\u043a\u0441\u0442 \u0432 \u043e\u0434\u043d\u043e\u043c \u0438\u0437 \u0438\u043d\u043f\u0443\u0442\u043e\u0432 <code>XScreen<\/code>, \u0432 \u0440\u0430\u043c\u043a\u0430\u0445 <strong>body reevaluate<\/strong>, \u043e\u043f\u0440\u043e\u0441\u0438\u0442 \u043e\u0431\u0430 \u0438\u043d\u043f\u0443\u0442\u0430, \u0430 \u043d\u0435 \u0445\u043e\u0442\u044f\u0442 \u043b\u0438 \u043e\u043d\u0438 \u0442\u043e\u0436\u0435 \u0432\u044b\u0437\u0432\u0430\u0442\u044c \u043f\u043e\u0432\u0442\u043e\u0440\u043d\u043e \u0441\u0432\u043e\u0439 <code>body<\/code>?<\/p>\n<blockquote>\n<p>\u0415\u0441\u0442\u044c \u0438 \u043c\u043d\u043e\u0436\u0435\u0441\u0442\u0432\u043e \u0434\u0440\u0443\u0433\u0438\u0445 \u043f\u0440\u0438\u0447\u0438\u043d, \u043f\u043e \u043a\u043e\u0442\u043e\u0440\u044b\u043c <code>body<\/code> \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u0432\u044b\u0437\u0432\u0430\u043d \u0441\u043d\u043e\u0432\u0430.  \u041e\u043d\u0438 \u043c\u043e\u0433\u0443\u0442 \u0431\u044b\u0442\u044c \u0441\u0432\u044f\u0437\u0430\u043d\u044b, \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440 \u0441  <code>@State<\/code> \/ <code>@ObservedObject<\/code> \/ <code>@StateObject<\/code>, \u043d\u043e \u043d\u0430 \u0434\u0430\u043d\u043d\u044b\u0439 \u043c\u043e\u043c\u0435\u043d\u0442 \u043d\u0430\u0448 \u0438\u043d\u043f\u0443\u0442 \u043b\u0438\u0448\u0435\u043d \u0432\u0441\u0435\u0433\u043e \u044d\u0442\u043e\u0433\u043e<\/p>\n<\/blockquote>\n<p>\u0414\u0430\u043b\u044c\u0448\u0435 \u043d\u0443\u0436\u043d\u043e \u043f\u043e\u043d\u044f\u0442\u044c, \u0447\u0442\u043e \u0437\u043d\u0430\u0447\u0438\u0442 &#171;<strong><em>\u0432\u0445\u043e\u0434\u043d\u044b\u0435 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b \u0438\u0437\u043c\u0435\u043d\u0438\u043b\u0438\u0441\u044c<\/em><\/strong>&#171;. \u0415\u0441\u043b\u0438 \u043a\u0440\u0430\u0442\u043a\u043e, \u0442\u043e \u0431\u0435\u0440\u0443\u0442\u0441\u044f \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b \u0434\u043e \u0438 \u043f\u043e\u0441\u043b\u0435 \u0438 \u0441\u0440\u0430\u0432\u043d\u0438\u0432\u0430\u044e\u0442\u0441\u044f (\u043f\u043e\u0434\u0440\u043e\u0431\u043d\u0435\u0435 \u043f\u043e\u0447\u0438\u0442\u0430\u0442\u044c \u043c\u043e\u0436\u043d\u043e \u0432 <a href=\"https:\/\/www.donnywals.com\/understanding-how-and-when-swiftui-decides-to-redraw-views\/\" rel=\"noopener noreferrer nofollow\">\u044d\u0442\u043e\u0439 \u0441\u0442\u0430\u0442\u044c\u0435<\/a>). \u0415\u0441\u043b\u0438 \u043a\u0430\u043a\u0438\u0435-\u0442\u043e \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b \u0440\u0435\u0430\u043b\u0438\u0437\u0443\u044e\u0442 \u043f\u0440\u043e\u0442\u043e\u043a\u043e\u043b <code>Equatable<\/code>, \u0442\u043e \u0441\u0440\u0430\u0432\u043d\u0438\u0432\u0430\u0442\u044c\u0441\u044f \u043e\u043d\u0438 \u0431\u0443\u0434\u0443\u0442 \u0438\u043c\u0435\u043d\u043d\u043e \u043f\u043e \u043d\u0435\u043c\u0443. \u0412 \u044d\u0442\u043e\u043c \u043b\u0435\u0433\u043a\u043e \u0443\u0431\u0435\u0434\u0438\u0442\u044c\u0441\u044f, \u0435\u0441\u043b\u0438:<\/p>\n<ul>\n<li>\n<p>\u0441\u043e\u0437\u0434\u0430\u0442\u044c \u043a\u043b\u0430\u0441\u0441<\/p>\n<\/li>\n<li>\n<p>\u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u0442\u044c <code>Equatable<\/code><\/p>\n<\/li>\n<li>\n<p>\u0432 \u043c\u0435\u0442\u043e\u0434 <code>==<\/code> \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c <code>print<\/code> <\/p>\n<\/li>\n<li>\n<p>\u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u044d\u0442\u043e\u0442 \u043a\u043b\u0430\u0441\u0441 \u043a\u0430\u043a \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440 \u0434\u043b\u044f <strong>View<\/strong><\/p>\n<\/li>\n<\/ul>\n<p>\u0418\u043d\u0442\u0435\u0440\u0435\u0441\u043d\u044b\u043c \u043c\u043e\u043c\u0435\u043d\u0442\u043e\u043c \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0435\u0449\u0435 \u0438 \u0442\u043e, \u0447\u0442\u043e <strong>SwiftUI<\/strong> \u043a\u0430\u043a-\u0442\u043e \u0443\u043c\u0435\u0435\u0442 \u0441\u0440\u0430\u0432\u043d\u0438\u0432\u0430\u0442\u044c \u0434\u0430\u0436\u0435 \u0442\u0435 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043d\u0435 \u044f\u0432\u043b\u044f\u044e\u0442\u0441\u044f <code>Equatable<\/code>. (\u043a\u0430\u043a-\u0442\u043e \u043f\u043e \u0441\u0441\u044b\u043b\u043a\u0435?)<\/p>\n<p>\u041c\u044b \u0436\u0435 \u0432 \u043a\u043e\u0434\u0435 \u0432\u044b\u0448\u0435, \u043d\u0430 \u0441\u0442\u0440\u043e\u043a\u0435 8, \u043a\u0430\u0436\u0434\u044b\u0439 \u0440\u0430\u0437 \u0441\u043e\u0437\u0434\u0430\u0435\u043c \u043d\u043e\u0432\u0443\u044e \u043a\u043b\u043e\u0436\u0443\u0440\u0443. \u041a\u043b\u043e\u0436\u0443\u0440\u044b, \u0432 \u0441\u0432\u043e\u044e \u043e\u0447\u0435\u0440\u0435\u0434\u044c, \u043d\u0435 \u044f\u0432\u043b\u044f\u044e\u0442\u0441\u044f <code>Equatable<\/code>. \u041d\u043e\u0432\u0430\u044f \u043a\u043b\u043e\u0436\u0443\u0440\u0430 \u0441 \u043d\u043e\u0432\u043e\u0439 \u0441\u0441\u044b\u043b\u043a\u043e\u0439 \u0438 \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u043f\u0440\u0438\u0447\u0438\u043d\u043e\u0439 \u043b\u0438\u0448\u043d\u0435\u0433\u043e <strong>body reevaluate<\/strong>.<\/p>\n<p>\u0427\u0442\u043e\u0431\u044b \u0443\u0431\u0435\u0434\u0438\u0442\u044c\u0441\u044f \u0432 \u044d\u0442\u043e\u043c, \u043c\u044b \u043f\u0440\u0438\u0434\u0435\u043c \u043a \u0440\u0435\u0448\u0435\u043d\u0438\u044e, \u0433\u0434\u0435 \u043a\u043b\u043e\u0436\u0443\u0440\u0430 \u0431\u0443\u0434\u0435\u0442 \u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0438\u0440\u043e\u0432\u0430\u0442\u044c\u0441\u044f  \u0435\u0434\u0438\u043d\u043e\u0436\u0434\u044b, \u043d\u0435 \u0441\u043e\u0437\u0434\u0430\u0432\u0430\u044f\u0441\u044c \u0432 \u0432\u044b\u0437\u043e\u0432\u0435 <code>XTextField<\/code>:<\/p>\n<pre><code class=\"swift\">            XTextField(                 ...                 onTextChange: onText1Change \/\/              onTextChange: { onText1Change($0) } \/\/ \u0432\u043e\u0442 \u0442\u0430\u043a \u0434\u0435\u043b\u0430\u0442\u044c \u043d\u0435 \u0441\u0442\u043e\u0438\u0442             )<\/code><\/pre>\n<details class=\"spoiler\">\n<summary>\u041f\u0430\u0441\u0445\u0430\u043b\u043a\u0430 \u0434\u043b\u044f \u0442\u0435\u0445, \u043a\u0442\u043e \u0437\u043d\u0430\u043a\u043e\u043c \u0441 Android<\/summary>\n<div class=\"spoiler__content\">\n<p>\u0418\u043d\u0442\u0435\u0440\u0435\u0441\u043d\u043e, \u0447\u0442\u043e \u0432 <strong>Jetpack Compose<\/strong> \u0435\u0441\u0442\u044c \u0440\u043e\u0432\u043d\u043e \u0441\u0445\u043e\u0436\u0430\u044f \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u0430 \u0441\u043e \u0441\u0442\u0430\u0431\u0438\u043b\u044c\u043d\u043e\u0441\u0442\u044c\u044e <strong>\u043b\u044f\u043c\u0431\u0434\u0430<\/strong> \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u043e\u0432. \u0420\u0435\u0448\u0430\u0435\u0442\u0441\u044f \u043e\u043d\u0430 &#171;\u0437\u0430\u043f\u043e\u043c\u0438\u043d\u0430\u043d\u0438\u0435\u043c&#187; \u043b\u044f\u043c\u0431\u0434\u044b. \u0417\u0434\u0435\u0441\u044c, \u0432 <strong>SwiftUI<\/strong>, \u043c\u044b \u0442\u043e\u0436\u0435, \u0441\u0432\u043e\u0435\u0433\u043e \u0440\u043e\u0434\u0430, \u0431\u0443\u0434\u0435\u043c \u0437\u0430\u043f\u043e\u043c\u0438\u043d\u0430\u0442\u044c <strong>\u043a\u043b\u043e\u0436\u0443\u0440\u0443<\/strong>, \u043e\u0431\u044a\u044f\u0432\u043b\u044f\u044f \u0435\u0435 \u0432 \u043a\u043e\u043d\u0441\u0442\u0440\u0443\u043a\u0442\u043e\u0440\u0435.<\/p>\n<\/div>\n<\/details>\n<p>\u0421\u0430\u043c\u044b\u0439 \u043f\u0440\u043e\u0441\u0442\u043e\u0439 \u0432\u0430\u0440\u0438\u0430\u043d\u0442 \u044d\u0442\u043e \u043f\u0440\u043e\u0432\u0435\u0440\u0438\u0442\u044c \u2014 \u043e\u0441\u0442\u0430\u0432\u0438\u0442\u044c \u043e\u0434\u043d\u0443 \u043a\u043b\u043e\u0436\u0443\u0440\u0443 \u043a\u0430\u043a \u0435\u0441\u0442\u044c (\u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u0434\u043b\u044f \u0432\u0442\u043e\u0440\u043e\u0433\u043e \u0442\u0435\u043a\u0441\u0442\u0430), \u0430 \u0434\u0440\u0443\u0433\u0443\u044e \u0440\u0430\u0437\u043e\u0432\u043e \u043f\u0440\u043e\u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0432 \u043a\u043e\u043d\u0441\u0442\u0440\u0443\u043a\u0442\u043e\u0440\u0435 <code>XScreen<\/code>:<\/p>\n<pre><code class=\"swift\">struct XScreen: View {          ...          private let onText1Change: (String) -> Void          init() {         self.onText1Change = { _ in }     }          var body: some View {         VStack {             XTextField(                 ...                 onTextChange: onText1Change             )                          ...         }     } } <\/code><\/pre>\n<p>\u0417\u0430\u043f\u0443\u0441\u0442\u0438\u0432, \u043c\u043e\u0436\u043d\u043e \u0443\u0431\u0435\u0434\u0438\u0442\u044c\u0441\u044f, \u0447\u0442\u043e \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0435 \u0432\u0442\u043e\u0440\u043e\u0433\u043e \u0438\u043d\u043f\u0443\u0442\u0430 \u0442\u0435\u043f\u0435\u0440\u044c \u043d\u0435 \u0432\u044b\u0437\u044b\u0432\u0430\u0435\u0442 <strong>body reevaluate<\/strong> \u043f\u0435\u0440\u0432\u043e\u0433\u043e \u0438\u043d\u043f\u0443\u0442\u0430.<\/p>\n<figure class=\"\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/cdb\/c4a\/baf\/cdbc4abafd15f0c026cedc17bc972453.gif\" width=\"336\" height=\"440\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/cdb\/c4a\/baf\/cdbc4abafd15f0c026cedc17bc972453.gif\"\/><\/figure>\n<p>\u0412\u0441\u0435 \u0431\u044b \u043d\u0438\u0447\u0435\u0433\u043e, \u0432\u043e\u0442 \u0442\u043e\u043b\u044c\u043a\u043e \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0435 \u043f\u0435\u0440\u0432\u043e\u0433\u043e \u0438\u043d\u043f\u0443\u0442\u0430 \u0432 \u0442\u0430\u043a\u043e\u043c \u0441\u043b\u0443\u0447\u0430\u0435 \u043d\u0438 \u043a \u0447\u0435\u043c\u0443 \u043d\u0435 \u043f\u0440\u0438\u0432\u043e\u0434\u0438\u0442, \u0442\u0430\u043a \u043a\u0430\u043a \u043a\u043b\u043e\u0436\u0443\u0440\u0430 \u043d\u0438\u0447\u0435\u0433\u043e \u043d\u0435 \u043e\u0431\u043d\u043e\u0432\u043b\u044f\u0435\u0442. <\/p>\n<p>\u0415\u0441\u043b\u0438 \u0436\u0435 \u043c\u044b \u0432\u043d\u0443\u0442\u0440\u044c \u0441\u043e\u0437\u0434\u0430\u0432\u0430\u0435\u043c\u043e\u0439 \u0432 \u043a\u043e\u043d\u0441\u0442\u0440\u0443\u043a\u0442\u043e\u0440\u0435 \u043a\u043b\u043e\u0436\u0443\u0440\u044b \u0434\u043e\u0431\u0430\u0432\u0438\u043c \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0435 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u044f (\u043a\u0430\u043a \u044d\u0442\u043e \u0438 \u0431\u044b\u043b\u043e \u0440\u0430\u043d\u044c\u0448\u0435), \u0442\u043e Xcode \u0432\u044b\u0434\u0430\u0441\u0442 \u043d\u0430\u043c \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0443\u044e \u043e\u0448\u0438\u0431\u043a\u0443: &#171;<em>Escaping closure captures mutating &#8216;self&#8217; parameter&#187;<\/em>.<\/p>\n<p>\u0420\u0435\u0448\u0435\u043d\u0438\u0435 \u0435\u0441\u0442\u044c. \u041e\u043d\u043e, \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e, \u0434\u0430\u043b\u0435\u043a\u043e \u043d\u0435 \u043b\u0443\u0447\u0448\u0435\u0435, \u043d\u043e \u0437\u0430\u0442\u043e \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442.<\/p>\n<p>\u0421\u043d\u0430\u0447\u0430\u043b\u0430 \u043c\u044b \u0437\u0430\u0432\u0435\u0434\u0435\u043c <code>ObservableObject<\/code>, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0445\u0440\u0430\u043d\u0438\u0442 \u0438\u0437\u043c\u0435\u043d\u044f\u0435\u043c\u043e\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435 \u0438 \u043a\u043b\u043e\u0436\u0443\u0440\u0443 \u043f\u043e \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044e \u044d\u0442\u043e\u0433\u043e \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f:<\/p>\n<pre><code class=\"swift\">class XObject&lt;T>: ObservableObject {     var value: T     lazy var update: (T) -> Void = {         { [weak self] newValue in             guard let self = self else { return }             self.value = newValue             self.objectWillChange.send()         }     }()          init(value: T) {         self.value = value     } }<\/code><\/pre>\n<p>\u0417\u0430\u0442\u0435\u043c \u0437\u0430\u043c\u0435\u043d\u0438\u043c \u044d\u0442\u043e\u0439 \u0448\u0442\u0443\u043a\u043e\u0439 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u044e\u0449\u0438\u0435 <code>@State<\/code>&#8216;\u044b:<\/p>\n<pre><code class=\"swift\">struct XScreen: View {          @StateObject private var text1Object: XObject&lt;String>     @StateObject private var text2Object: XObject&lt;String>          private var onText1Change: (String) -> Void     private var onText2Change: (String) -> Void          init() {         var text1Obj = XObject(value: \"TextField1\")         var text2Obj = XObject(value: \"TextField2\")                  self._text1Object = StateObject(wrappedValue: text1Obj)         self._text2Object = StateObject(wrappedValue: text2Obj)                  self.onText1Change = { text in             text1Obj.update(text)             print(\"TextField1: text changed to \\(text)\")         }         self.onText2Change = { text in             text2Obj.update(text)             print(\"TextField2: text changed to \\(text)\")         }     }          var body: some View {         VStack {             XTextField(                 text: text1Object.value,                 onTextChange: onText1Change             )                          XTextField(                 text: text2Object.value,                 ...                 onTextChange: onText2Change             )         }     } }<\/code><\/pre>\n<p>\u041f\u043e\u0441\u043b\u0435 \u0437\u0430\u043f\u0443\u0441\u043a\u0430 \u0432\u0438\u0434\u0438\u043c, \u0447\u0442\u043e \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0435 \u043e\u0434\u043d\u043e\u0433\u043e \u0438\u043d\u043f\u0443\u0442\u0430 \u0442\u0435\u043f\u0435\u0440\u044c \u043d\u0438\u043a\u0430\u043a \u043d\u0435 \u0432\u043b\u0438\u044f\u0435\u0442 \u043d\u0430 <strong>body reevaluate<\/strong> \u0432\u0442\u043e\u0440\u043e\u0433\u043e.<\/p>\n<figure class=\"\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/19a\/67a\/e68\/19a67ae68a72a8662a9292727fab5792.gif\" width=\"336\" height=\"440\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/19a\/67a\/e68\/19a67ae68a72a8662a9292727fab5792.gif\"\/><\/figure>\n<p><code>XObject<\/code>, \u043d\u0430 \u0441\u0430\u043c\u043e\u043c \u0434\u0435\u043b\u0435, \u0432\u0440\u044f\u0434 \u043b\u0438 \u043f\u0440\u0438\u0433\u043e\u0434\u0438\u0442\u0441\u044f \u043d\u0430 \u043f\u0440\u0430\u043a\u0442\u0438\u043a\u0435. \u0421\u043a\u043e\u0440\u0435\u0435 \u0432\u0441\u0435\u0433\u043e, \u0432\u044b \u043d\u0435 \u0431\u0443\u0434\u0435\u0442\u0435 \u0445\u0440\u0430\u043d\u0438\u0442\u044c \u0442\u0435\u043a\u0441\u0442 (\u0430 \u0431\u043e\u043b\u0435\u0435 \u0433\u043b\u043e\u0431\u0430\u043b\u044c\u043d\u043e \u2014 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435) \u0432 \u043a\u0430\u043a\u043e\u0439-\u0442\u043e <strong>View<\/strong> \u043f\u043e \u0430\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u043d\u044b\u043c \u043f\u0440\u0438\u0447\u0438\u043d\u0430\u043c.<\/p>\n<p>\u041d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u043c\u044b \u043c\u043e\u0436\u0435\u043c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c <strong>MVI<\/strong> \u0430\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u0443, \u0433\u0434\u0435 <strong>View<\/strong> \u043e\u0431\u0449\u0430\u0435\u0442\u0441\u044f \u0441 <strong>ViewModel<\/strong>, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u0447\u0435\u0440\u0435\u0437 <code>AnyPublisher<\/code> \u043e\u0442\u0434\u0430\u0435\u0442 <strong>ViewState<\/strong>, \u0445\u0440\u0430\u043d\u044f\u0449\u0438\u0439 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f \u0434\u043b\u044f \u0438\u043d\u043f\u0443\u0442\u043e\u0432. \u0412\u0441\u0435 \u044d\u0442\u043e \u043c\u043e\u0436\u0435\u0442 \u0432\u044b\u0433\u043b\u044f\u0434\u0435\u0442\u044c \u043f\u0440\u0438\u043c\u0435\u0440\u043d\u043e \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c:<\/p>\n<pre><code class=\"swift\">struct XScreen: View {          @State     private var viewState: XViewState          private let onTextChange: (String) -> Void          init(viewModel: XViewModel) {         self.viewState = viewModel.currentViewState                  self.onTextChange = { newText in             viewModel.obtainEvent(                 viewEvent: XEvent.OnTextChanged(text: newText)             )         }     }          var body: some View {         VStack(spacing: 0) {             XTextField(                 text: viewState.text,                 onTextChange: onTextChange             )         }.onReceive(viewModel.viewStatePublisher) { (newViewState: XViewState) in             viewState = newViewState         }     } }<\/code><\/pre>\n<details class=\"spoiler\">\n<summary>XTextField.swift &#8212; \u043f\u0440\u043e\u043c\u0435\u0436\u0443\u0442\u043e\u0447\u043d\u044b\u0439 \u043a\u043e\u0434 #2<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"swift\">import SwiftUI  struct XTextField: View {          var text: String          var isEnabled: Bool = true     var hasError: Bool = false          var label: String = \"\"     var placeholder: String = \"\"          var trailingImage: UIImage? = nil          var captionText: String? = nil          var onTextChange: (String) -> Void          var onTrailingImageClick: () -> Void = { }          var body: some View {         let _ = Self._printChanges()         VStack(alignment: .leading, spacing: 0) {             let shouldUseLabel = true \/\/ \u0418\u0437\u043c\u0435\u043d\u0438\u043c \u043f\u043e\u0441\u043b\u0435             Text(label)                 .font(labelFont)                 .foregroundColor(labelColor)                 .opacity(shouldUseLabel ? 1 : 0)                          Spacer().frame(height: 8)                          HStack(alignment: .center, spacing: 0) {                 ZStack(alignment: .leading) {                     Text(placeholder)                         .font(placeholderFont)                         .foregroundColor(placeholderColor)                         .opacity(shouldUseLabel ? 0 : 1)                     textField()                         .textFieldStyle(.plain) \/\/ https:\/\/stackoverflow.com\/a\/74745555                         .font(textFont)                         .foregroundColor(textColor)                         .accentColor(cursorColor)                 }                                  if let trailingImage = trailingImage {                     Spacer().frame(width: 16)                     Image(uiImage: trailingImage)                         .foregroundColor(trailingImageColor)                         .frame(width: 24, height: 24, alignment: .center)                         .onTapGesture { onTrailingImageClick() }                 }             }.frame(minHeight: 24)                          ZStack {                 Spacer()                 Rectangle()                     .fill(underlineColor)                     .frame(height: 1)             }.frame(height: 16, alignment: .bottom)                          if let captionText = captionText {                 Spacer().frame(height: 8)                 Text(captionText)                     .font(captionFont)                     .foregroundColor(captionColor)             }         }         .disabled(!isEnabled)         .opacity(isEnabled ? 1 : 0.4)         .background(.random)     }          @ViewBuilder     private func textField() -> some View {         TextField(\"\", text: Binding(             get: { text },             set: { onTextChange($0) }         ))     } }  \/\/ MARK: Colors private extension XTextField {          var labelColor: Color { Color.gray }          var placeholderColor: Color { Color.gray }          var textColor: Color { Color.black }          var cursorColor: Color {         if hasError {             Color.red         } else {             Color.green         }     }          var underlineColor: Color {         if hasError {             Color.red         } else {             Color.gray         }     }          var captionColor: Color {         if hasError {             Color.red         } else {             Color.gray         }     }          var trailingImageColor: Color { Color.gray } }  \/\/ MARK: Fonts private extension XTextField {          var labelFont: Font { Font.caption }          var placeholderFont: Font { Font.body }          var textFont: Font { Font.body }          var captionFont: Font { Font.caption } }  private extension ShapeStyle where Self == Color {     static var random: Color {         Color(             red: .random(in: 0...1),             green: .random(in: 0...1),             blue: .random(in: 0...1)         )     } } <\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<details class=\"spoiler\">\n<summary>XScreen.swift &#8212; \u043f\u0440\u043e\u043c\u0435\u0436\u0443\u0442\u043e\u0447\u043d\u044b\u0439 \u043a\u043e\u0434 #2<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"swift\">import SwiftUI  class XObject&lt;T>: ObservableObject {     var value: T     lazy var update: (T) -> Void = {         { [weak self] newValue in             guard let self = self else { return }             self.value = newValue             self.objectWillChange.send()         }     }()          init(value: T) {         self.value = value     } }  struct XScreen: View {          @StateObject private var text1Object: XObject&lt;String>     @StateObject private var text2Object: XObject&lt;String>          private var onText1Change: (String) -> Void     private var onText2Change: (String) -> Void          init() {         var text1Obj = XObject(value: \"TextField1\")         var text2Obj = XObject(value: \"TextField2\")                  self._text1Object = StateObject(wrappedValue: text1Obj)         self._text2Object = StateObject(wrappedValue: text2Obj)                  self.onText1Change = { text in             text1Obj.update(text)             print(\"TextField1: text changed to \\(text)\")         }         self.onText2Change = { text in             text2Obj.update(text)             print(\"TextField2: text changed to \\(text)\")         }     }          var body: some View {         VStack {             XTextField(                 text: text1Object.value,                 hasError: false,                 label: \"TextField1\",                 placeholder: \"TextField1\",                 trailingImage: UIImage.add.withRenderingMode(.alwaysTemplate),                 captionText: \"TextField1\",                 onTextChange: onText1Change             ).padding(20)                          XTextField(                 text: text2Object.value,                 hasError: true,                 label: \"TextField2\",                 placeholder: \"TextField2\",                 trailingImage: UIImage.add.withRenderingMode(.alwaysTemplate),                 captionText: \"TextField2\",                 onTextChange: onText2Change             ).padding(20)         }     } } <\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<h3>\u041f\u0440\u043e\u0431\u043b\u0435\u043c\u0430 \u21163. \u041e\u0431\u043b\u0430\u0441\u0442\u044c \u043d\u0430\u0436\u0430\u0442\u0438\u044f<\/h3>\n<p>\u041f\u0435\u0440\u0435\u0439\u0434\u0435\u043c \u043a \u0447\u0435\u043c\u0443-\u0442\u043e \u043c\u0435\u043d\u0435\u0435 \u043d\u0443\u0434\u043d\u043e\u043c\u0443. \u041d\u0435\u043c\u043d\u043e\u0433\u043e \u043f\u043e\u0438\u0433\u0440\u0430\u0432\u0448\u0438\u0441\u044c, \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0438\u0432\u0430\u0435\u043c, \u0447\u0442\u043e \u043e\u0431\u043b\u0430\u0441\u0442\u044c \u043d\u0430\u0436\u0430\u0442\u0438\u044f \u0432\u043e\u0432\u0441\u0435 \u043d\u0435 \u0442\u0430, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u0434\u043e\u043b\u0436\u043d\u0430 \u0431\u044b\u0442\u044c. <\/p>\n<figure class=\"\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/0df\/e8d\/16c\/0dfe8d16ce6974f4c974a0c4320a0291.gif\" width=\"336\" height=\"440\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/0df\/e8d\/16c\/0dfe8d16ce6974f4c974a0c4320a0291.gif\"\/><\/figure>\n<p>\u0420\u0435\u0448\u0435\u043d\u0438\u0435\u043c \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0440\u0430\u0431\u043e\u0442\u0430 \u0441 \u0444\u043e\u043a\u0443\u0441\u043e\u043c: \u0437\u0430\u0434\u0430\u0432\u0430\u0442\u044c \u043a\u0430\u0441\u0442\u043e\u043c\u043d\u044b\u0439 <code>onTapGesture { }<\/code> \u0438 \u043f\u043e \u043a\u043b\u0438\u043a\u0443 \u0437\u0430\u0445\u0432\u0430\u0442\u044b\u0432\u0430\u0442\u044c \u0444\u043e\u043a\u0443\u0441 \u0434\u043b\u044f \u043d\u0430\u0448\u0435\u0433\u043e <code>TextField<\/code>. \u0414\u043b\u044f \u043a\u043e\u0433\u043e-\u0442\u043e \u0434\u0430\u043d\u043d\u044b\u0439 \u043c\u043e\u043c\u0435\u043d\u0442 \u043c\u043e\u0436\u0435\u0442 \u0441\u0442\u0430\u0442\u044c \u043a\u0440\u0438\u0442\u0438\u0447\u043d\u044b\u043c, \u0442\u0430\u043a \u043a\u0430\u043a \u0444\u043e\u043a\u0443\u0441 \u043f\u043e\u0434\u0432\u0435\u0437\u043b\u0438 \u0442\u043e\u043b\u044c\u043a\u043e \u0432 <strong>iOS 15<\/strong>. \u0412\u043e\u0437\u043c\u043e\u0436\u043d\u043e, \u0434\u043b\u044f \u0431\u043e\u043b\u0435\u0435 \u0440\u0430\u043d\u043d\u0438\u0445 \u0432\u0435\u0440\u0441\u0438\u0439 \u0435\u0441\u0442\u044c \u0438 \u0430\u043b\u044c\u0442\u0435\u0440\u043d\u0430\u0442\u0438\u0432\u043d\u044b\u0435 \u0441\u043f\u043e\u0441\u043e\u0431\u044b \u0440\u0435\u0448\u0435\u043d\u0438\u044f \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u044b, \u043e\u0434\u043d\u0430\u043a\u043e \u0432\u0441\u0435 \u043e\u043d\u0438 (\u0447\u0442\u043e \u044f \u0432\u0438\u0434\u0435\u043b) \u0432\u044b\u0433\u043b\u044f\u0434\u044f\u0442 \u043a\u0430\u043a \u043e\u0434\u0438\u043d \u0431\u043e\u043b\u044c\u0448\u043e\u0439 \u043a\u043e\u0441\u0442\u044b\u043b\u044c.<\/p>\n<p>\u0418\u0442\u0430\u043a, \u0434\u043e\u0431\u0430\u0432\u0438\u043c \u0437\u0430\u0445\u0432\u0430\u0442 \u0444\u043e\u043a\u0443\u0441\u0430 \u043f\u043e \u0442\u0430\u043f\u0443:<\/p>\n<pre><code class=\"swift\">struct XTextField: View {      ...        @FocusState     var isFocused: Bool          var body: some View {         VStack(alignment: .leading, spacing: 0) {             ...         }         .onTapGesture {             isFocused = true         }     }          @ViewBuilder     private func textField() -> some View {         TextField(\"\", text: Binding(             get: { text },             set: { onTextChange($0) }         ))             .focused($isFocused)     } }<\/code><\/pre>\n<p>\u041f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u043c. \u0420\u0430\u0431\u043e\u0442\u0430\u0435\u0442!<\/p>\n<figure class=\"\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/b70\/db5\/f29\/b70db5f291c5cae906ad158ecfba84f0.gif\" width=\"336\" height=\"440\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/b70\/db5\/f29\/b70db5f291c5cae906ad158ecfba84f0.gif\"\/><\/figure>\n<p>\u0415\u0441\u0442\u044c \u0443 \u043d\u0430\u0441 \u0442\u0430\u043a\u0430\u044f \u043c\u0430\u0433\u0438\u0447\u0435\u0441\u043a\u0430\u044f \u0441\u0442\u0440\u043e\u043a\u0430:<\/p>\n<pre><code class=\"swift\">VStack(alignment: .leading, spacing: 0) {<\/code><\/pre>\n<p>\u0417\u0434\u0435\u0441\u044c \u043c\u044b \u0432\u044b\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u043c \u043e\u0442\u0441\u0442\u0443\u043f \u0432 0. \u041a\u043e\u0433\u0434\u0430 \u043c\u043d\u0435 \u0434\u043e\u0432\u0435\u043b\u043e\u0441\u044c \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u044b\u0432\u0430\u0442\u044c \u0434\u0430\u043d\u043d\u044b\u0439 \u0438\u043d\u043f\u0443\u0442, \u043d\u0443\u043b\u0435\u0432\u043e\u0439 \u0438\u043d\u043f\u0443\u0442 \u044f \u0432\u044b\u0441\u0442\u0430\u0432\u0438\u043b \u0443\u0436\u0435 \u043a\u0443\u0434\u0430 \u043f\u043e\u0437\u0436\u0435, \u0441\u0432\u0435\u0440\u044f\u044f \u0434\u0438\u0437\u0430\u0439\u043d \u0441 \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0435\u0439 (pixel perfect, \u0432\u0441\u0435 \u0434\u0435\u043b\u0430). \u0414\u043e\u043f\u0443\u0441\u0442\u0438\u043c, \u0443 \u043d\u0430\u0441 \u0442\u043e\u0436\u0435 \u043d\u0435 \u0431\u044b\u043b \u0432\u044b\u0441\u0442\u0430\u0432\u043b\u0435\u043d \u043d\u0443\u043b\u0435\u0432\u043e\u0439 \u043e\u0442\u0441\u0442\u0443\u043f (\u043f\u0440\u043e\u0441\u0442\u043e \u043d\u0435 \u043f\u0435\u0440\u0435\u0434\u0430\u0435\u043c \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440 <code>spacing<\/code>). \u0417\u0430\u043f\u0443\u0441\u0442\u0438\u043c. <\/p>\n<figure class=\"\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/582\/ea4\/b28\/582ea4b28462b223df39e3015eaaed9e.gif\" width=\"336\" height=\"440\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/582\/ea4\/b28\/582ea4b28462b223df39e3015eaaed9e.gif\"\/><\/figure>\n<p>\u0418\u043d\u043f\u0443\u0442 \u0441\u0442\u0430\u043b \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0431\u043e\u043b\u044c\u0448\u0435. \u041d\u043e \u0441\u0443\u0442\u044c \u043d\u0435 \u0432 \u044d\u0442\u043e\u043c. \u0422\u0435\u043f\u0435\u0440\u044c \u0437\u0430\u0445\u0432\u0430\u0442 \u0444\u043e\u043a\u0443\u0441\u0430 \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u043d\u0435 \u0432\u0441\u0435\u0433\u0434\u0430. \u0415\u0441\u043b\u0438 \u043c\u044b \u043f\u043e\u043f\u0430\u0434\u0435\u043c \u0432 \u0442\u043e\u0442 \u0441\u0430\u043c\u044b\u0439 \u043e\u0442\u0441\u0442\u0443\u043f, \u0442\u043e <code>onTapGesture { }<\/code> \u043d\u0435 \u043e\u0442\u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442. <a href=\"https:\/\/www.hackingwithswift.com\/quick-start\/swiftui\/how-to-control-the-tappable-area-of-a-view-using-contentshape\" rel=\"noopener noreferrer nofollow\">\u0420\u0435\u0448\u0435\u043d\u0438\u0435\u043c<\/a> \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u044b \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0432\u044b\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u0438\u0435 <code>contentShape(Rectangle())<\/code> \u043d\u0430\u0434 \u043d\u0438\u043c:<\/p>\n<pre><code class=\"swift\">    var body: some View {         VStack(alignment: .leading, spacing: 0) {         }         .contentShape(Rectangle())         .onTapGesture {             isFocused = true         }     }<\/code><\/pre>\n<p>\u0422\u0430\u043a\u0436\u0435 \u043e\u0431\u0432\u0435\u0448\u0430\u0435\u043c \u043d\u0430\u0448 \u043a\u043e\u0434 \u0447\u0442\u0435\u043d\u0438\u0435\u043c \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u044f \u0444\u043e\u043a\u0443\u0441\u0430, \u0447\u0442\u043e\u0431\u044b \u0431\u044b\u043b\u043e \u043a\u0430\u043a \u043f\u043e \u0434\u0438\u0437\u0430\u0439\u043d\u0443 (\u043f\u043e\u0434\u0434\u0435\u0440\u0436\u043a\u0430 \u043f\u043b\u0435\u0439\u0441\u0445\u043e\u043b\u0434\u0435\u0440\u0430 \u043f\u0440\u0438 \u043f\u0443\u0441\u0442\u043e\u0439 \u0441\u0442\u0440\u043e\u043a\u0435, \u0446\u0432\u0435\u0442 \u0438 \u0432\u044b\u0441\u043e\u0442\u0430 \u043f\u043e\u043b\u043e\u0441\u043a\u0438 \u0432 \u0444\u043e\u043a\u0443\u0441\u0435):<\/p>\n<figure class=\"\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/0e9\/ac7\/ba8\/0e9ac7ba839d2b0af1710f054829b06f.gif\" width=\"336\" height=\"440\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/0e9\/ac7\/ba8\/0e9ac7ba839d2b0af1710f054829b06f.gif\"\/><\/figure>\n<details class=\"spoiler\">\n<summary>XTextField.swift &#8212; \u043f\u0440\u043e\u043c\u0435\u0436\u0443\u0442\u043e\u0447\u043d\u044b\u0439 \u043a\u043e\u0434 #3<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"swift\">import SwiftUI  struct XTextField: View {          var text: String          var isEnabled: Bool = true     var hasError: Bool = false          var label: String = \"\"     var placeholder: String = \"\"          var trailingImage: UIImage? = nil          var captionText: String? = nil          var onTextChange: (String) -> Void          var onTrailingImageClick: () -> Void = { }          @FocusState     var isFocused: Bool          var body: some View {         let _ = Self._printChanges()         VStack(alignment: .leading, spacing: 0) {             let shouldUseLabel = isFocused || !text.isEmpty             Text(label)                 .font(labelFont)                 .foregroundColor(labelColor)                 .opacity(shouldUseLabel ? 1 : 0)                          Spacer().frame(height: 8)                          HStack(alignment: .center, spacing: 0) {                 ZStack(alignment: .leading) {                     Text(placeholder)                         .font(placeholderFont)                         .foregroundColor(placeholderColor)                         .opacity(shouldUseLabel ? 0 : 1)                     textField()                         .textFieldStyle(.plain) \/\/ https:\/\/stackoverflow.com\/a\/74745555                         .font(textFont)                         .foregroundColor(textColor)                         .accentColor(cursorColor)                 }                                  if let trailingImage = trailingImage {                     Spacer().frame(width: 16)                     Image(uiImage: trailingImage)                         .foregroundColor(trailingImageColor)                         .frame(width: 24, height: 24, alignment: .center)                         .onTapGesture { onTrailingImageClick() }                 }             }.frame(minHeight: 24)                          ZStack {                 Spacer()                 Rectangle()                     .fill(underlineColor)                     .frame(height: isFocused ? 2 : 1)             }.frame(height: 16, alignment: .bottom)                          if let captionText = captionText {                 Spacer().frame(height: 8)                 Text(captionText)                     .font(captionFont)                     .foregroundColor(captionColor)             }         }         .contentShape(Rectangle())         .onTapGesture {             isFocused = true         }         .disabled(!isEnabled)         .opacity(isEnabled ? 1 : 0.4)         .background(.random)     }          @ViewBuilder     private func textField() -> some View {         TextField(\"\", text: Binding(             get: { text },             set: { onTextChange($0) }         ))             .focused($isFocused)     } }  \/\/ MARK: Colors private extension XTextField {          var labelColor: Color { Color.gray }          var placeholderColor: Color { Color.gray }          var textColor: Color { Color.black }          var cursorColor: Color {         if hasError {             Color.red         } else {             Color.green         }     }          var underlineColor: Color {         if hasError {             Color.red         } else {             if isFocused {                 Color.black             } else {                 Color.gray             }         }     }          var captionColor: Color {         if hasError {             Color.red         } else {             Color.gray         }     }          var trailingImageColor: Color { Color.gray } }  \/\/ MARK: Fonts private extension XTextField {          var labelFont: Font { Font.caption }          var placeholderFont: Font { Font.body }          var textFont: Font { Font.body }          var captionFont: Font { Font.caption } }  private extension ShapeStyle where Self == Color {     static var random: Color {         Color(             red: .random(in: 0...1),             green: .random(in: 0...1),             blue: .random(in: 0...1)         )     } } <\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<h3>\u041f\u0440\u043e\u0431\u043b\u0435\u043c\u0430 #4. \u0417\u0432\u0435\u0437\u0434\u043e\u0447\u043a\u0438 \u0432\u043c\u0435\u0441\u0442\u043e \u043f\u0430\u0440\u043e\u043b\u044f<\/h3>\n<p>\u0412 <strong>UIKit<\/strong> \u0443 <code>UITextField<\/code> \u0435\u0441\u0442\u044c \u043f\u043e\u043b\u0435, \u043f\u0440\u0438 \u043f\u043e\u043c\u043e\u0449\u0438 \u043a\u043e\u0442\u043e\u0440\u043e\u0433\u043e \u043c\u043e\u0436\u043d\u043e \u043f\u0435\u0440\u0435\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f \u043d\u0430 \u0431\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u044b\u0439 \u0432\u0432\u043e\u0434. \u042d\u0442\u043e \u043f\u043e\u043b\u0435 \u2014 <code>isSecureTextEntry<\/code>. \u041f\u0435\u0440\u0435\u043a\u043b\u044e\u0447\u0430\u044f \u0435\u0433\u043e \u0432 <strong>true<\/strong>, \u0432\u0432\u043e\u0434\u0438\u043c\u044b\u0439 \u0442\u0435\u043a\u0441\u0442 \u0437\u0430\u043c\u0435\u043d\u044f\u0435\u0442\u0441\u044f \u0443\u0441\u043b\u043e\u0432\u043d\u044b\u043c\u0438 \u0437\u0432\u0435\u0437\u0434\u043e\u0447\u043a\u0430\u043c\u0438. \u041a\u0440\u043e\u043c\u0435 \u044d\u0442\u043e\u0433\u043e, \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435 \u0438\u043d\u043f\u0443\u0442\u0430 \u043f\u0440\u0438 \u0442\u0430\u043a\u0438\u0445 \u0443\u0441\u043b\u043e\u0432\u0438\u044f\u0445 \u043d\u0435\u043b\u044c\u0437\u044f \u043a\u043e\u043f\u0438\u0440\u043e\u0432\u0430\u0442\u044c.<\/p>\n<p>\u0412 <strong>SwiftUI<\/strong> \u0440\u043e\u0432\u043d\u043e \u044d\u0442\u043e\u0442 \u0436\u0435 \u0444\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b \u043f\u043e\u0447\u0435\u043c\u0443-\u0442\u043e \u043d\u0435 \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0447\u0430\u0441\u0442\u044c\u044e <code>TextField<\/code>, \u0430 \u0432\u044b\u043d\u0435\u0441\u0435\u043d \u0432 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u0443\u044e <strong>View <\/strong>\u2014 <code>SecureField<\/code>.<\/p>\n<p>\u0417\u0430\u0447\u0435\u043c \u0436\u0435 \u043c\u044b \u0440\u0430\u043d\u0435\u0435 \u0432\u044b\u043d\u043e\u0441\u0438\u043b\u0438 <code>TextField<\/code> \u0432 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u0443\u044e <code>@ViewBuilder<\/code> \u0444\u0443\u043d\u043a\u0446\u0438\u044e? \u041a\u0430\u043a \u0440\u0430\u0437 \u0434\u043b\u044f \u0442\u043e\u0433\u043e, \u0447\u0442\u043e\u0431\u044b \u043f\u0430\u0440\u0430\u043b\u043b\u0435\u043b\u044c\u043d\u043e \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0442\u0443\u0434\u0430 \u0436\u0435 <code>SecureField<\/code> \u0438 \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u0442\u044c \u043f\u0435\u0440\u0435\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u043d\u0430 \u044d\u0442\u043e\u0442 \u0441\u0430\u043c\u044b\u0439 \u0431\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u044b\u0439 \u0440\u0435\u0436\u0438\u043c \u0432\u0432\u043e\u0434\u0430.<\/p>\n<p>\u041e\u0431\u043d\u043e\u0432\u0438\u043c \u043d\u0430\u0448 <code>XTextField<\/code>:<\/p>\n<pre><code class=\"swift\">struct XTextField: View {          var isSecureTextEntry: Bool = false          ...          @ViewBuilder     private func textField() -> some View {         ZStack {             TextField(\"\", text: Binding(                 get: { text },                 set: { onTextChange($0) }             ))                 .opacity(isSecureTextEntry ? 0 : 1)                          SecureField(\"\", text: Binding(                 get: { text },                 set: { onTextChange($0) }             ))                 .opacity(isSecureTextEntry ? 1 : 0)         }      } }<\/code><\/pre>\n<p>\u0427\u0442\u043e\u0431\u044b \u043f\u043e\u0441\u043c\u043e\u0442\u0440\u0435\u0442\u044c \u043d\u043e\u0432\u044b\u0439 \u0444\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b \u0432 \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0438, \u0442\u0430\u043a\u0436\u0435 \u0432\u0438\u0434\u043e\u0438\u0437\u043c\u0435\u043d\u0438\u043c \u043a\u043e\u0434 \u0432 <code>XScreen<\/code>, \u0434\u043e\u043f\u0438\u0441\u0430\u0432 \u043f\u0435\u0440\u0435\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 <code>isSecureTextEntry<\/code> \u043f\u043e \u043a\u043b\u0438\u043a\u0443 \u043d\u0430 \u0438\u043a\u043e\u043d\u043a\u0443 \u0432 \u043f\u0435\u0440\u0432\u043e\u043c \u0438\u043d\u043f\u0443\u0442\u0435:<\/p>\n<pre><code class=\"kotlin\">struct XScreen: View {      ...          @StateObject private var text1SecureObject: XObject&lt;Bool>          private var onTrailing1IconClick: () -> Void          init() {         ...                  var text1SecureObj = XObject(value: false)                  self._text1SecureObject = StateObject(wrappedValue: text1SecureObj)                  self.onTrailing1IconClick = {             text1SecureObj.update(!text1SecureObj.value)         }     }          var body: some View {         VStack {             XTextField(                 text: text1Object.value,                 hasError: false,                 isSecureTextEntry: text1SecureObject.value,                 label: \"TextField1\",                 placeholder: \"TextField1\",                 trailingImage: UIImage.add.withRenderingMode(.alwaysTemplate),                 captionText: \"TextField1\",                 onTextChange: onText1Change,                 onTrailingImageClick: onTrailing1IconClick             )                          ...         }     } } <\/code><\/pre>\n<figure class=\"\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/db8\/e73\/721\/db8e737210ff7acd24ee77d43bee51da.gif\" width=\"336\" height=\"440\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/db8\/e73\/721\/db8e737210ff7acd24ee77d43bee51da.gif\"\/><\/figure>\n<p>\u041a\u0430\u043a \u0432\u0438\u0434\u0438\u043c, \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442. \u041e\u0434\u043d\u0430\u043a\u043e \u0435\u0441\u0442\u044c \u0434\u0432\u0430 \u043a\u0440\u0438\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u0445 \u043c\u0438\u043d\u0443\u0441\u0430:<\/p>\n<ul>\n<li>\n<p>\u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f \u0441 \u0444\u043e\u043a\u0443\u0441\u043e\u043c, \u0441\u0434\u0435\u043b\u0430\u043d\u043d\u044b\u0435 \u043d\u0430 \u043f\u0440\u043e\u0448\u043b\u043e\u043c \u044d\u0442\u0430\u043f\u0435, \u043d\u0435 \u0440\u0430\u0431\u043e\u0442\u0430\u044e\u0442<\/p>\n<\/li>\n<li>\n<p>\u043f\u0430\u0440\u043e\u043b\u044c \u0437\u0430\u0442\u0438\u0440\u0430\u0435\u0442\u0441\u044f<\/p>\n<\/li>\n<\/ul>\n<p>\u041d\u0430\u0447\u043d\u0435\u043c \u0441 \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u044b \u0444\u043e\u043a\u0443\u0441\u0430. \u0420\u0435\u0448\u0435\u043d\u0438\u0435 \u0431\u0443\u0434\u0435\u0442 \u0437\u0430\u043a\u043b\u044e\u0447\u0430\u0442\u044c\u0441\u044f \u0432 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u043c:<\/p>\n<ul>\n<li>\n<p>\u043c\u0435\u043d\u044f\u0435\u043c \u0442\u0438\u043f \u0434\u043b\u044f <code>@FocusState<\/code> \u0441 <code>Bool<\/code> \u043d\u0430 <code>FocusField?<\/code>, \u0433\u0434\u0435 \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0435\u0435 \u2014 \u044d\u0442\u043e <strong>enum<\/strong>, \u043e\u0442\u0440\u0430\u0436\u0430\u044e\u0449\u0438\u0439 \u0442\u043e, \u0447\u0442\u043e \u0441\u0435\u0439\u0447\u0430\u0441 \u0432 \u0444\u043e\u043a\u0443\u0441\u0435 (<code>TextField<\/code> \u0438\u043b\u0438 <code>SecureField<\/code>) <\/p>\n<\/li>\n<li>\n<p>\u043f\u043e \u0442\u0430\u043f\u0443 \u0432\u044b\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u043c \u0444\u043e\u043a\u0443\u0441 \u0432 \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0438\u0438 \u0441 \u0442\u0435\u043c, \u0447\u0442\u043e \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0430\u0435\u0442\u0441\u044f<\/p>\n<\/li>\n<li>\n<p>\u043f\u043e \u0441\u043c\u0435\u043d\u0435 <code>isSecureTextEntry<\/code> \u043e\u0431\u043d\u043e\u0432\u043b\u044f\u0435\u043c \u0438 \u0444\u043e\u043a\u0443\u0441, \u0442\u0430\u043a \u043a\u0430\u043a \u0432 \u0438\u043d\u043e\u043c \u0441\u043b\u0443\u0447\u0430\u0435, \u043f\u0440\u0438 \u043d\u0430\u043b\u0438\u0447\u0438\u0438 \u0444\u043e\u043a\u0443\u0441\u0430, \u0441\u043c\u0435\u043d\u0430 \u0434\u0430\u043d\u043d\u043e\u0433\u043e \u043f\u043e\u043b\u044f \u043d\u0435 \u0437\u0430\u043a\u0440\u043e\u0435\u0442 \u043a\u043b\u0430\u0432\u0438\u0430\u0442\u0443\u0440\u0443, \u043d\u043e \u0441\u043a\u0440\u043e\u0435\u0442 \u043a\u0443\u0440\u0441\u043e\u0440<\/p>\n<\/li>\n<\/ul>\n<pre><code class=\"swift\">struct XTextField: View {      ...        @FocusState     \/\/ (1)     \/\/ var isFocused: Bool \/\/ \u043c\u0435\u043d\u044f\u0435\u043c \u043d\u0430:     var focusField: FocusField?          var body: some View {         VStack(alignment: .leading, spacing: 0) {             ...         }         .onTapGesture {             \/\/ (2)             focusField = isSecureTextEntry ? .secureField : .textField         }         .onChange(of: isSecureTextEntry) { newIsSecureTextEntry in             if focusField != nil {                 \/\/ (3)                 \/\/ \u043e\u0431\u043d\u043e\u0432\u043b\u044f\u0435\u043c \u043f\u043e\u043b\u0435 \u0434\u043b\u044f \u0444\u043e\u043a\u0443\u0441\u0430, \u0435\u0441\u043b\u0438 \u043f\u043e\u043b\u0435 \u0431\u044b\u043b\u043e \u0432 \u0444\u043e\u043a\u0443\u0441\u0435, \u043d\u043e \u0438\u0437\u043c\u0435\u043d\u0438\u043b\u0441\u044f isSecureTextEntry                 focusField = newIsSecureTextEntry ? .secureField : .textField             }         }     }          @ViewBuilder     private func textField() -> some View {         ZStack {             TextField(\"\", text: Binding(                 get: { text },                 set: { onTextChange($0) }             ))                 \/\/ (4)                 .focused($focusField, equals: .textField)                 .opacity(isSecureTextEntry ? 0 : 1)                          SecureField(\"\", text: Binding(                 get: { text },                 set: { onTextChange($0) }             ))                 \/\/ (4)                 .focused($focusField, equals: .secureField)                 .opacity(isSecureTextEntry ? 1 : 0)         }     }      \/\/ (5)     var isFocused: Bool {         focusField != nil     } }  extension XTextField {          enum FocusField {         case textField, secureField     } }<\/code><\/pre>\n<p>\u0421 \u0437\u0430\u0442\u0438\u0440\u0430\u043d\u0438\u0435\u043c \u043f\u0430\u0440\u043e\u043b\u044f \u0434\u0435\u043b\u0430 \u043e\u0431\u0441\u0442\u043e\u044f\u0442 \u043a\u0443\u0434\u0430 \u0441\u043b\u043e\u0436\u043d\u0435\u0435.<\/p>\n<p>\u0412 \u0441\u0445\u043e\u0436\u0435\u043c \u0438\u043d\u043f\u0443\u0442\u0435 \u043d\u0430 <strong>UIKit<\/strong> \u043c\u044b \u043f\u0435\u0440\u0435\u043e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0435\u043c \u0444\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f \u0444\u043e\u043a\u0443\u0441\u0430 \u0442\u0430\u043a\u0438\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c, \u0447\u0442\u043e \u0435\u0441\u043b\u0438 \u0432\u044b\u0441\u0442\u0430\u0432\u043b\u0435\u043d \u0431\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u044b\u0439 \u0432\u0432\u043e\u0434, \u0442\u043e \u0441\u0431\u0440\u0430\u0441\u044b\u0432\u0430\u0435\u043c \u0442\u0435\u043a\u0441\u0442 \u0438 \u0442\u0443\u0442 \u0436\u0435 \u0432\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u043c \u0435\u0433\u043e \u0447\u0435\u0440\u0435\u0437 <code>insertText<\/code> (\u043f\u043e\u0447\u0438\u0442\u0430\u0442\u044c \u043c\u043e\u0436\u043d\u043e <a href=\"https:\/\/stackoverflow.com\/a\/48115361\" rel=\"noopener noreferrer nofollow\">\u0437\u0434\u0435\u0441\u044c<\/a>).<\/p>\n<p>\u0421\u043e <strong>SwiftUI<\/strong> \u0441\u0445\u043e\u0434\u0443 \u0442\u0430\u043a\u043e\u0439 \u0444\u043e\u043a\u0443\u0441 \u043d\u0435 \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u0441\u044f. \u041b\u044e\u0431\u044b\u0435 \u043f\u043e\u043f\u044b\u0442\u043a\u0438 \u043f\u043e\u0434\u043e\u0431\u043d\u043e\u0439 \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u043f\u0440\u0438\u0432\u043e\u0434\u044f\u0442 \u043a \u0442\u043e\u043c\u0443, \u0447\u0442\u043e \u043d\u0438\u0447\u0435\u0433\u043e \u043d\u0435 \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442.<\/p>\n<p>\u041c\u0435\u0442\u043e\u0434\u043e\u043c \u0434\u043e\u043b\u0433\u0438\u0445 \u043f\u0440\u043e\u0431 \u0438 \u043e\u0448\u0438\u0431\u043e\u043a \u043c\u043d\u0435 \u043f\u043e\u0432\u0441\u0442\u0440\u0435\u0447\u0430\u043b\u0441\u044f <a href=\"https:\/\/github.com\/siteline\/swiftui-introspect\" rel=\"noopener noreferrer nofollow\">SwiftUI Introspect<\/a>. \u041f\u0440\u0438 \u043f\u043e\u043c\u043e\u0449\u0438 \u043d\u0435\u0433\u043e \u043c\u043e\u0436\u043d\u043e \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c \u0434\u043e\u0441\u0442\u0443\u043f \u043a \u0431\u0430\u0437\u043e\u0432\u043e\u043c\u0443 \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u0443 \u0438\u0437 <strong>UIKit<\/strong>, \u043a\u043e\u0442\u043e\u0440\u044b\u043c \u0432 \u043d\u0430\u0448\u0435\u043c \u0441\u043b\u0443\u0447\u0430\u0435 \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f <code>UITextField<\/code>. <\/p>\n<p>\u0418 \u0432\u0440\u043e\u0434\u0435 \u0431\u044b \u0440\u0435\u0448\u0435\u043d\u0438\u0435 \u043d\u0430 <strong>SwiftUI Introspect<\/strong> \u0434\u0430\u0436\u0435 \u043c\u043e\u0436\u043d\u043e \u043d\u0430\u043f\u0438\u0441\u0430\u0442\u044c. \u0418 \u043e\u043d\u043e \u0434\u0430\u0436\u0435 \u0431\u0443\u0434\u0435\u0442 \u0440\u0430\u0431\u043e\u0442\u0430\u0442\u044c. \u041d\u043e \u043c\u043e\u0439 \u0432\u044b\u0431\u043e\u0440 \u0432\u0441\u0435 \u0436\u0435 \u043f\u0430\u043b \u0432 \u0441\u0442\u043e\u0440\u043e\u043d\u0443 \u0441\u0430\u043c\u043e\u0434\u0435\u043b\u044c\u043d\u043e\u0433\u043e \u043a\u043e\u0441\u0442\u044b\u043b\u044f (\u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0441\u0435\u0439\u0447\u0430\u0441 \u043c\u043d\u0435 \u0434\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u043e \u0441\u043b\u043e\u0436\u043d\u043e \u043e\u0431\u043e\u0441\u043d\u043e\u0432\u0430\u0442\u044c). \u0417\u0430\u043a\u043b\u044e\u0447\u0430\u0435\u0442\u0441\u044f \u043e\u043d \u0432 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u043c:<\/p>\n<ul>\n<li>\n<p>&#171;\u043f\u0440\u043e\u0441\u043b\u0443\u0448\u0438\u0432\u0430\u0435\u043c&#187; <code>UITextField.textDidBeginEditingNotification<\/code><\/p>\n<\/li>\n<li>\n<p>\u0435\u0441\u043b\u0438 \u0434\u043b\u044f <code>UITextField<\/code> \u043f\u043e\u0434\u0440\u0430\u0437\u0443\u043c\u0435\u0432\u0430\u0435\u0442\u0441\u044f \u0431\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u044b\u0439 \u0432\u0432\u043e\u0434, \u0442\u043e \u0437\u0430\u0442\u0438\u0440\u0430\u0435\u043c \u0442\u0435\u043a\u0441\u0442 \u0438 \u0432\u044b\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u043c \u0435\u0433\u043e \u0436\u0435 \u0447\u0435\u0440\u0435\u0437 <code>insertText<\/code> <\/p>\n<\/li>\n<li>\n<p>\u0432\u0441\u044e \u044d\u0442\u0443 \u043b\u043e\u0433\u0438\u043a\u0443 \u0435\u0434\u0438\u043d\u043e\u0436\u0434\u044b \u0431\u0443\u0434\u0435\u043c \u0432\u0435\u0441\u0438\u0442\u044c \u043d\u0430 \u0440\u0443\u0442\u043e\u0432\u0443\u044e <strong>View<\/strong><\/p>\n<\/li>\n<\/ul>\n<pre><code class=\"swift\">extension View {          @ViewBuilder     public func preventPasswordReset() -> some View {         onReceive(             NotificationCenter.default.publisher(                 for: UITextField.textDidBeginEditingNotification             )         ) { obj in             if let textField = obj.object as? UITextField {                 if textField.isSecureTextEntry {                     let currentText = textField.text ?? \"\"                     textField.text = \"\"                     textField.insertText(currentText)                 }             }         }     } }  struct XScreen: View {      ...          var body: some View {         VStack {             ...         }         .preventPasswordReset()     } } <\/code><\/pre>\n<figure class=\"\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/f5c\/1e6\/09a\/f5c1e609a78b4e151c7a612341ca96ea.gif\" width=\"336\" height=\"440\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/f5c\/1e6\/09a\/f5c1e609a78b4e151c7a612341ca96ea.gif\"\/><\/figure>\n<details class=\"spoiler\">\n<summary>XTextField.swift &#8212; \u043f\u0440\u043e\u043c\u0435\u0436\u0443\u0442\u043e\u0447\u043d\u044b\u0439 \u043a\u043e\u0434 #4<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"swift\">import SwiftUI  struct XTextField: View {          var text: String          var isEnabled: Bool = true     var hasError: Bool = false     var isSecureTextEntry: Bool = false          var label: String = \"\"     var placeholder: String = \"\"          var trailingImage: UIImage? = nil          var captionText: String? = nil          var onTextChange: (String) -> Void          var onTrailingImageClick: () -> Void = { }          @FocusState     var focusField: FocusField?          var body: some View {         let _ = Self._printChanges()         VStack(alignment: .leading, spacing: 0) {             let shouldUseLabel = isFocused || !text.isEmpty             Text(label)                 .font(labelFont)                 .foregroundColor(labelColor)                 .opacity(shouldUseLabel ? 1 : 0)                          Spacer().frame(height: 8)                          HStack(alignment: .center, spacing: 0) {                 ZStack(alignment: .leading) {                     Text(placeholder)                         .font(placeholderFont)                         .foregroundColor(placeholderColor)                         .opacity(shouldUseLabel ? 0 : 1)                     textField()                         .textFieldStyle(.plain) \/\/ https:\/\/stackoverflow.com\/a\/74745555                         .font(textFont)                         .foregroundColor(textColor)                         .accentColor(cursorColor)                 }                                  if let trailingImage = trailingImage {                     Spacer().frame(width: 16)                     Image(uiImage: trailingImage)                         .foregroundColor(trailingImageColor)                         .frame(width: 24, height: 24, alignment: .center)                         .onTapGesture { onTrailingImageClick() }                 }             }.frame(minHeight: 24)                          ZStack {                 Spacer()                 Rectangle()                     .fill(underlineColor)                     .frame(height: isFocused ? 2 : 1)             }.frame(height: 16, alignment: .bottom)                          if let captionText = captionText {                 Spacer().frame(height: 8)                 Text(captionText)                     .font(captionFont)                     .foregroundColor(captionColor)             }         }         .contentShape(Rectangle())         .onTapGesture {             focusField = isSecureTextEntry ? .secureField : .textField         }         .disabled(!isEnabled)         .opacity(isEnabled ? 1 : 0.4)         .onChange(of: isSecureTextEntry) { newIsSecureTextEntry in             if focusField != nil {                 focusField = newIsSecureTextEntry ? .secureField : .textField             }         }         .background(.random)     }          @ViewBuilder     private func textField() -> some View {         ZStack {             TextField(\"\", text: Binding(                 get: { text },                 set: { onTextChange($0) }             ))                 .focused($focusField, equals: .textField)                 .opacity(isSecureTextEntry ? 0 : 1)                          SecureField(\"\", text: Binding(                 get: { text },                 set: { onTextChange($0) }             ))                 .focused($focusField, equals: .secureField)                 .opacity(isSecureTextEntry ? 1 : 0)         }     }          var isFocused: Bool {         focusField != nil     } }  \/\/ MARK: Colors private extension XTextField {          var labelColor: Color { Color.gray }          var placeholderColor: Color { Color.gray }          var textColor: Color { Color.black }          var cursorColor: Color {         if hasError {             Color.red         } else {             Color.green         }     }          var underlineColor: Color {         if hasError {             Color.red         } else {             if isFocused {                 Color.black             } else {                 Color.gray             }         }     }          var captionColor: Color {         if hasError {             Color.red         } else {             Color.gray         }     }          var trailingImageColor: Color { Color.gray } }  \/\/ MARK: Fonts private extension XTextField {          var labelFont: Font { Font.caption }          var placeholderFont: Font { Font.body }          var textFont: Font { Font.body }          var captionFont: Font { Font.caption } }  extension XTextField {          enum FocusField {         case textField, secureField     } }  extension View {          @ViewBuilder     public func preventPasswordReset() -> some View {         onReceive(             NotificationCenter.default.publisher(                 for: UITextField.textDidBeginEditingNotification             )         ) { obj in             if let textField = obj.object as? UITextField {                 if textField.isSecureTextEntry {                     let currentText = textField.text ?? \"\"                     textField.text = \"\"                     textField.insertText(currentText)                 }             }         }     } }  private extension ShapeStyle where Self == Color {     static var random: Color {         Color(             red: .random(in: 0...1),             green: .random(in: 0...1),             blue: .random(in: 0...1)         )     } } <\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<details class=\"spoiler\">\n<summary>XScreen.swift &#8212; \u043f\u0440\u043e\u043c\u0435\u0436\u0443\u0442\u043e\u0447\u043d\u044b\u0439 \u043a\u043e\u0434 #4<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"swift\">import SwiftUI  class XObject&lt;T>: ObservableObject {     var value: T     lazy var update: (T) -> Void = {         { [weak self] newValue in             guard let self = self else { return }             self.value = newValue             self.objectWillChange.send()         }     }()          init(value: T) {         self.value = value     } }  struct XScreen: View {          @StateObject private var text1Object: XObject&lt;String>     @StateObject private var text2Object: XObject&lt;String>     @StateObject private var text1SecureObject: XObject&lt;Bool>          private var onText1Change: (String) -> Void     private var onText2Change: (String) -> Void          private var onTrailing1IconClick: () -> Void          init() {         var text1Obj = XObject(value: \"TextField1\")         var text2Obj = XObject(value: \"TextField2\")         var text1SecureObj = XObject(value: false)                  self._text1Object = StateObject(wrappedValue: text1Obj)         self._text2Object = StateObject(wrappedValue: text2Obj)         self._text1SecureObject = StateObject(wrappedValue: text1SecureObj)                  self.onText1Change = { text in             text1Obj.update(text)             print(\"TextField1: text changed to \\(text)\")         }         self.onText2Change = { text in             text2Obj.update(text)             print(\"TextField2: text changed to \\(text)\")         }         self.onTrailing1IconClick = {             text1SecureObj.update(!text1SecureObj.value)         }     }          var body: some View {         VStack {             XTextField(                 text: text1Object.value,                 hasError: false,                 isSecureTextEntry: text1SecureObject.value,                 label: \"TextField1\",                 placeholder: \"TextField1\",                 trailingImage: UIImage.add.withRenderingMode(.alwaysTemplate),                 captionText: \"TextField1\",                 onTextChange: onText1Change,                 onTrailingImageClick: onTrailing1IconClick             ).padding(20)                          XTextField(                 text: text2Object.value,                 hasError: true,                 label: \"TextField2\",                 placeholder: \"TextField2\",                 trailingImage: UIImage.add.withRenderingMode(.alwaysTemplate),                 captionText: \"TextField2\",                 onTextChange: onText2Change             ).padding(20)         }         .preventPasswordReset()     } } <\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<h3>\u041f\u0440\u043e\u0431\u043b\u0435\u043c\u0430 \u21165. \u0424\u043e\u0440\u043c\u0430\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435<\/h3>\n<p>\u0427\u0442\u043e \u0435\u0441\u043b\u0438 \u043d\u0430\u043c \u043d\u0443\u0436\u043d\u043e \u0438\u0441\u043a\u0430\u0437\u0438\u0442\u044c \u0438 \u043e\u0442\u0444\u043e\u0440\u043c\u0430\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0442\u0435\u043a\u0441\u0442, \u0432\u0432\u0435\u0434\u0435\u043d\u043d\u044b\u0439 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435\u043c? \u041d\u0430\u043f\u0440\u0438\u043c\u0435\u0440:<\/p>\n<ul>\n<li>\n<p>\u043e\u0433\u0440\u0430\u043d\u0438\u0447\u0438\u0442\u044c \u043c\u0430\u043a\u0441\u0438\u043c\u0430\u043b\u044c\u043d\u043e\u0435 \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u0432\u0432\u043e\u0434\u0438\u043c\u044b\u0445 \u0441\u0438\u043c\u0432\u043e\u043b\u043e\u0432<\/p>\n<\/li>\n<li>\n<p>\u043f\u043e\u0437\u0432\u043e\u043b\u0438\u0442\u044c \u0432\u0432\u043e\u0434\u0438\u0442\u044c \u0442\u043e\u043b\u044c\u043a\u043e \u0446\u0438\u0444\u0440\u044b<\/p>\n<\/li>\n<li>\n<p>\u0432\u044b\u0432\u043e\u0434\u0438\u0442\u044c \u043d\u043e\u043c\u0435\u0440 \u0442\u0435\u043b\u0435\u0444\u043e\u043d\u0430 \u0432 \u0444\u043e\u0440\u043c\u0430\u0442\u0435 <strong>+7-999-999-99-99<\/strong><\/p>\n<\/li>\n<\/ul>\n<p>\u041e\u0447\u0435\u0432\u0438\u0434\u043d\u043e, \u0447\u0442\u043e \u043d\u0430\u043c \u043d\u0443\u0436\u043d\u043e \u043e\u0442\u0440\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043a\u043b\u043e\u0436\u0443\u0440\u0443 \u043f\u043e \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u044e \u0442\u0435\u043a\u0441\u0442\u0430 \u0442\u0430\u043a\u0438\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c, \u0447\u0442\u043e\u0431\u044b \u0442\u0435\u043a\u0441\u0442 \u0432\u044b\u0441\u0442\u0430\u0432\u043b\u044f\u043b\u0441\u044f \u0432 \u043d\u0443\u0436\u043d\u043e\u043c \u043d\u0430\u043c \u0444\u043e\u0440\u043c\u0430\u0442\u0435. \u041f\u043e\u044d\u043a\u0441\u043f\u0435\u0440\u0438\u043c\u0435\u043d\u0442\u0438\u0440\u0443\u0435\u043c \u0441 \u043d\u0430\u0448\u0438\u043c \u0432\u0442\u043e\u0440\u044b\u043c \u0438\u043d\u043f\u0443\u0442\u043e\u043c:<\/p>\n<ul>\n<li>\n<p>\u043f\u043e\u0437\u0432\u043e\u043b\u0438\u043c \u0432\u0432\u043e\u0434\u0438\u0442\u044c \u0442\u043e\u043b\u044c\u043a\u043e \u0446\u0438\u0444\u0440\u044b<\/p>\n<\/li>\n<li>\n<p>\u0440\u0430\u0437\u0440\u0435\u0448\u0438\u043c \u043c\u0430\u043a\u0441\u0438\u043c\u0443\u043c \u0447\u0435\u0442\u044b\u0440\u0435 \u0446\u0438\u0444\u0440\u044b<\/p>\n<\/li>\n<li>\n<p>\u0446\u0438\u0444\u0440\u044b \u0431\u0443\u0434\u0435\u043c \u0440\u0430\u0437\u0434\u0435\u043b\u044f\u0442\u044c \u0434\u0435\u0444\u0438\u0441\u043e\u043c<\/p>\n<\/li>\n<li>\n<p>\u0431\u0443\u0434\u0435\u043c \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0430\u0442\u044c \u0442\u0435\u043a\u0441\u0442 \u0432 \u0444\u043e\u0440\u043c\u0430\u0442\u0435: <strong>1<\/strong>, <strong>1-2<\/strong>, <strong>1-2-3<\/strong>, <strong>1-2-3-4<\/strong><\/p>\n<\/li>\n<\/ul>\n<pre><code class=\"swift\">struct XScreen: View {          ...          init() {         self.onText2Change = { text in             let numbers = text                 .filter { $0.isNumber }                 .prefix(4)             let charArray = Array(numbers)             let formattedText = charArray                 .map { String($0) }                 .joined(separator: \"-\")             text2Obj.update(formattedText)             print(\"TextField2: text changed to \\(formattedText)\")         }     } } <\/code><\/pre>\n<figure class=\"\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/7f4\/4d6\/1c0\/7f44d61c030a4a7e26fb5bb72b036243.gif\" width=\"336\" height=\"440\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/7f4\/4d6\/1c0\/7f44d61c030a4a7e26fb5bb72b036243.gif\"\/><\/figure>\n<p>\u041d\u0443, \u043e\u043d\u043e \u043d\u0435 \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442. \u0422\u043e \u0435\u0441\u0442\u044c \u043a\u0430\u043a. \u041e\u043d\u043e \u0447\u0442\u043e-\u0442\u043e \u0444\u043e\u0440\u043c\u0430\u0442\u0438\u0440\u0443\u0435\u0442. \u041d\u043e \u043d\u0435 \u0432\u0441\u0435. \u0418 \u043d\u0438\u0447\u0435\u0433\u043e \u043d\u0435 \u043e\u0433\u0440\u0430\u043d\u0438\u0447\u0438\u0432\u0430\u0435\u0442. \u0422\u0430\u043a\u043e\u0435 \u0440\u0435\u0448\u0435\u043d\u0438\u0435 \u043d\u0438\u043a\u0443\u0434\u0430 \u043d\u0435 \u0433\u043e\u0434\u0438\u0442\u0441\u044f.<\/p>\n<p>\u0412 \u0447\u0435\u043c \u0441\u0443\u0442\u044c, \u043f\u043e\u0447\u0435\u043c\u0443 \u0442\u0430\u043a \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0434\u0438\u0442? \u041f\u0440\u043e\u0432\u0435\u0434\u044f \u043d\u0435\u043a\u043e\u0442\u043e\u0440\u043e\u0435 \u0438\u0441\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u043d\u0438\u0435, \u043c\u043d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u0432\u044b\u044f\u0441\u043d\u0438\u0442\u044c, \u0433\u0434\u0435 \u043d\u0430\u0445\u043e\u0434\u0438\u0442\u0441\u044f \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u0430:<\/p>\n<pre><code class=\"swift\">            TextField(\"\", text: Binding(                 get: { text },                 set: { onTextChange($0) }             ))<\/code><\/pre>\n<p>\u041a\u043e\u0433\u0434\u0430 \u0443 \u043d\u0430\u0441 \u043d\u0435\u0442 \u0444\u043e\u0440\u043c\u0430\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f, \u0442\u043e \u043a\u043b\u043e\u0436\u0443\u0440\u0430 <strong>onTextChange<\/strong> \u0432\u044b\u0437\u044b\u0432\u0430\u0435\u0442 \u043d\u0435\u043f\u043e\u0441\u0440\u0435\u0434\u0441\u0442\u0432\u0435\u043d\u043d\u043e\u0435 \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0435 <strong>text<\/strong>, \u0447\u0442\u043e \u043f\u0440\u0438\u0432\u043e\u0434\u0438\u0442 \u043a <strong>body reevaluate<\/strong> \u0432\u0441\u0435\u0433\u043e \u0438\u043d\u043f\u0443\u0442\u0430 \u0438 \u0432\u044b\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u0438\u044e \u043d\u043e\u0432\u043e\u0433\u043e \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f. \u041a\u043e\u0433\u0434\u0430 \u0435\u0441\u0442\u044c \u0444\u043e\u0440\u043c\u0430\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435, \u0442\u043e <strong>text<\/strong> \u043c\u0435\u043d\u044f\u0435\u0442\u0441\u044f \u043d\u0435 \u0432\u0441\u0435\u0433\u0434\u0430, \u0438\u0437-\u0437\u0430 \u0447\u0435\u0433\u043e <strong>body reevaluate <\/strong>\u043d\u0435 \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0434\u0438\u0442, \u0442\u0430\u043a \u043a\u0430\u043a \u0432\u0445\u043e\u0434\u043d\u044b\u0435 \u0434\u0430\u043d\u043d\u044b\u0435 \u043d\u0435 \u0438\u0437\u043c\u0435\u043d\u0438\u043b\u0438\u0441\u044c. \u041f\u0440\u0438 \u044d\u0442\u043e\u043c \u0438\u043d\u0442\u0435\u0440\u0435\u0441\u043d\u044b\u043c \u043c\u043e\u043c\u0435\u043d\u0442\u043e\u043c \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0442\u043e, \u0447\u0442\u043e \u0432\u044b\u0437\u043e\u0432 <code>get:<\/code> \u0432 \u0431\u0438\u043d\u0434\u0438\u043d\u0433\u0435 \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0434\u0438\u0442 \u0441\u043e \u0441\u0442\u0430\u0440\u044b\u043c \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435\u043c, \u043e\u0434\u043d\u0430\u043a\u043e, \u044d\u0442\u043e \u043d\u0438\u043a\u0430\u043a \u043d\u0435 \u0432\u043b\u0438\u044f\u0435\u0442 \u043d\u0430 \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0430\u0435\u043c\u044b\u0439 \u0442\u0435\u043a\u0441\u0442.<\/p>\n<p>\u0420\u0435\u0448\u0435\u043d\u0438\u0435 \u0434\u0430\u043d\u043d\u043e\u0439 \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u044b \u0437\u0430\u043a\u043b\u044e\u0447\u0430\u0435\u0442\u0441\u044f \u0432 \u043e\u0442\u043a\u0430\u0437\u0435 \u043e\u0442 \u0440\u0443\u0447\u043d\u043e\u0433\u043e \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f \u0431\u0438\u043d\u0434\u0438\u0433\u0430 \u0438, \u0432\u043c\u0435\u0441\u0442\u043e \u044d\u0442\u043e\u0433\u043e, \u043f\u0435\u0440\u0435\u0435\u0437\u0434\u0435 \u043d\u0430 <code>State<\/code> \/ <code>ObservableObject<\/code>. <\/p>\n<p>\u041f\u0435\u0440\u0432\u0430\u044f \u043f\u043e\u043f\u044b\u0442\u043a\u0430 \u0431\u0430\u0437\u0438\u0440\u043e\u0432\u0430\u043b\u0430\u0441\u044c \u043d\u0430 <code>ObservableObject<\/code>. \u041e\u043d\u0430 \u043f\u043e\u043b\u0443\u0447\u0438\u043b\u0430\u0441\u044c \u0433\u0440\u043e\u043c\u043e\u0437\u0434\u043a\u043e\u0439 \u0438 \u0441\u043b\u043e\u0436\u043d\u043e\u0439, \u0430 \u0442\u0430\u043a\u0436\u0435 \u0437\u0430\u043d\u044f\u043b\u0430 \u0443\u0439\u043c\u0443 \u0432\u0440\u0435\u043c\u0435\u043d\u0438. \u041e\u0431\u0438\u0434\u043d\u0435\u0435 \u0432\u0441\u0435\u0433\u043e \u0431\u044b\u043b\u043e \u0442\u043e, \u0447\u0442\u043e \u043d\u0430 \u044d\u0442\u0430\u043f\u0435 \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u043f\u043e\u043b\u0443\u0447\u0438\u043b\u0438 \u0440\u0430\u0437\u043d\u043e\u0435 \u043f\u043e\u0432\u0435\u0434\u0435\u043d\u0438\u0435 \u043d\u0430 \u0440\u0430\u0437\u043d\u044b\u0445 \u0432\u0435\u0440\u0441\u0438\u044f\u0445 <strong>iOS<\/strong> (\u043a\u043e\u0442\u043e\u0440\u043e\u0435 \u0433\u0434\u0435-\u0442\u043e \u0438 \u0432\u043e\u0432\u0441\u0435 \u043d\u0435 \u0440\u0430\u0431\u043e\u0442\u0430\u043b\u043e).<\/p>\n<p>\u0424\u0438\u043d\u0430\u043b\u044c\u043d\u043e\u0435 \u0436\u0435 \u0440\u0435\u0448\u0435\u043d\u0438\u0435 \u0441\u0442\u0440\u043e\u0438\u043b\u0430\u0441\u044c \u043d\u0430 <code>State<\/code>. \u041e\u043d\u043e \u043f\u043e\u043b\u0443\u0447\u0438\u043b\u043e\u0441\u044c \u043a\u0443\u0434\u0430 \u0431\u043e\u043b\u0435\u0435 \u043f\u0440\u043e\u0441\u0442\u044b\u043c, \u043d\u043e \u043b\u043e\u0433\u0438\u043a\u0430 \u043e\u0441\u0442\u0430\u043b\u0430\u0441\u044c \u043f\u0440\u0438\u043c\u0435\u0440\u043d\u043e \u043f\u0440\u0435\u0436\u043d\u0435\u0439. <\/p>\n<pre><code class=\"swift\">struct XTextField: View {          var text: String     var currentText: (() -> String)? = nil          ...          @State     private var internalText: String          init(         text: String,         currentText: (() -> String)? = nil,         ...     ) {         ...         self.text = text         self.currentText = currentText         self._internalText = State(wrappedValue: text)     }          var body: some View {         VStack(alignment: .leading, spacing: 0) {             ...         }         .onChange(of: internalText) { newText in             \/\/ \u0422\u0435\u043a\u0441\u0442 \u0438\u0437\u043c\u0435\u043d\u0438\u043b\u0441\u044f \u0438\u0437-\u0437\u0430 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u0441\u043a\u043e\u0433\u043e \u0432\u0432\u043e\u0434\u0430             if newText != text {                 \/\/ \u0423\u0432\u0435\u0434\u043e\u043c\u043b\u044f\u0435\u043c \u043e \u0432\u0432\u043e\u0434\u0435                 onTextChange(newText)                 if let currentText = currentText {                     \/\/ \u0421\u0440\u0430\u0437\u0443 \u043f\u043e\u0441\u043b\u0435 \u0443\u0432\u0435\u0434\u043e\u043c\u043b\u0435\u043d\u0438\u044f \u0437\u0430\u043f\u0440\u0430\u0448\u0438\u0432\u0430\u0435\u043c \u0430\u043a\u0442\u0443\u0430\u043b\u044c\u043d\u044b\u0439 \u0442\u0435\u043a\u0441\u0442 \u0432 \u043c\u043e\u043c\u0435\u043d\u0442\u0435                     internalText = currentText()                 }             }         }         .onChange(of: text) { newText in             \/\/ \u041f\u0440\u0438\u0448\u0435\u043b \u043d\u043e\u0432\u044b\u0439 \u0442\u0435\u043a\u0441\u0442 \u0447\u0435\u0440\u0435\u0437 \u043a\u043e\u043d\u0441\u0442\u0440\u0443\u043a\u0442\u043e\u0440             internalText = newText         }     }          @ViewBuilder     private func textField() -> some View {         ZStack {             \/\/ \u041f\u043e\u043b\u0443\u0447\u0430\u0435\u043c \u0431\u0438\u043d\u0434\u0438\u043d\u0433 \u0438\u0437 State             TextField(\"\", text: $internalText)             SecureField(\"\", text: $internalText)         }     }<\/code><\/pre>\n<p>\u0417\u0434\u0435\u0441\u044c \u043c\u044b \u043e\u043f\u0435\u0440\u0438\u0440\u0443\u0435\u043c \u0442\u0440\u0435\u043c\u044f \u0432\u0435\u0449\u0430\u043c\u0438:<\/p>\n<ul>\n<li>\n<p>\u0432\u043d\u0435\u0448\u043d\u0438\u0439 \u0442\u0435\u043a\u0441\u0442 (<strong>text<\/strong>)<\/p>\n<\/li>\n<li>\n<p>\u0432\u043d\u0443\u0442\u0440\u0435\u043d\u043d\u0438\u0439 \u0442\u0435\u043a\u0441\u0442 (<strong>internalText<\/strong>)<\/p>\n<\/li>\n<li>\n<p>\u0442\u0435\u043a\u0441\u0442 \u0432 \u043c\u043e\u043c\u0435\u043d\u0442\u0435 (\u043a\u043b\u043e\u0436\u0443\u0440\u0430 <strong>currentText<\/strong>). <\/p>\n<\/li>\n<\/ul>\n<p>\u0412\u043d\u0435\u0448\u043d\u0438\u0439 \u0442\u0435\u043a\u0441\u0442 \u043f\u0440\u0438\u043b\u0435\u0442\u0430\u0435\u0442 \u0432 \u043a\u043e\u043d\u0441\u0442\u0440\u0443\u043a\u0442\u043e\u0440\u0435 \u0438 \u043e\u0431\u043d\u043e\u0432\u043b\u044f\u0435\u0442 \u0432\u043d\u0443\u0442\u0440\u0435\u043d\u043d\u0438\u0439 \u0442\u0435\u043a\u0441\u0442. \u0421\u0430\u043c \u0436\u0435 \u043e\u043d \u043e\u0431\u043d\u043e\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u043f\u0440\u0438\u043c\u0435\u0440\u043d\u043e \u0432\u043e \u0432\u0441\u0435\u0445 \u0441\u043b\u0443\u0447\u0430\u044f\u0445, \u043a\u043e\u0433\u0434\u0430 \u043d\u0435\u0442 \u0444\u043e\u0440\u043c\u0430\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f, \u0433\u0434\u0435 <strong>onTextChange<\/strong> \u043f\u043e\u0431\u043e\u0447\u043d\u043e \u0432\u044b\u0437\u044b\u0432\u0430\u0435\u0442 \u0441\u043c\u0435\u043d\u0443 \u043f\u0435\u0440\u0435\u0434\u0430\u0432\u0430\u0435\u043c\u043e\u0433\u043e \u0432 \u043a\u043e\u043d\u0441\u0442\u0440\u0443\u043a\u0442\u043e\u0440 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0430 <strong>text<\/strong>.<\/p>\n<p>\u0412\u043d\u0443\u0442\u0440\u0435\u043d\u043d\u0438\u0439 \u0442\u0435\u043a\u0441\u0442 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c \u0434\u043b\u044f \u043e\u0431\u0449\u0435\u043d\u0438\u044f \u0441 <code>TextField<\/code> \/ <code>SecureField<\/code>, \u0434\u043b\u044f \u043e\u0442\u0441\u043b\u0435\u0436\u0438\u0432\u0430\u043d\u0438\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u0441\u043a\u043e\u0433\u043e \u0432\u0432\u043e\u0434\u0430. \u0418\u043c\u0435\u043d\u043d\u043e \u0435\u0433\u043e \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435 \u0438 \u0431\u0443\u0434\u0435\u0442 \u043e\u0442\u0440\u0438\u0441\u043e\u0432\u044b\u0432\u0430\u0442\u044c\u0441\u044f.<\/p>\n<p>\u0427\u0442\u043e \u0436\u0435 \u0442\u0430\u043a\u043e\u0435 &#171;\u0442\u0435\u043a\u0441\u0442 \u0432 \u043c\u043e\u043c\u0435\u043d\u0442\u0435&#187;? \u042d\u0442\u043e \u043a\u043b\u043e\u0436\u0443\u0440\u0430, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u0441\u043f\u043e\u0441\u043e\u0431\u043d\u0430 \u043e\u0442\u0434\u0430\u0432\u0430\u0442\u044c \u0430\u043a\u0442\u0443\u0430\u043b\u044c\u043d\u043e\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e\u0433\u043e \u0442\u0435\u043a\u0441\u0442\u0430 \u0432 \u043b\u044e\u0431\u043e\u0439 \u043c\u043e\u043c\u0435\u043d\u0442 \u0432\u0440\u0435\u043c\u0435\u043d\u0438. \u041f\u0440\u0438\u043c\u0435\u043d\u0438\u043c\u0430 \u043e\u043d\u0430 \u0442\u043e\u043b\u044c\u043a\u043e \u0434\u043b\u044f \u0441\u043b\u0443\u0447\u0430\u0435\u0432 \u0441 \u0444\u043e\u0440\u043c\u0430\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435\u043c, \u043a\u043e\u0433\u0434\u0430 \u0432\u0432\u043e\u0434\u0438\u043c\u044b\u0439 \u0442\u0435\u043a\u0441\u0442 \u043c\u043e\u0436\u0435\u0442 \u043e\u0442\u043b\u0438\u0447\u0430\u0442\u044c\u0441\u044f \u043e\u0442 \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0430\u0435\u043c\u043e\u0433\u043e.<\/p>\n<p>\u0420\u0430\u0441\u0441\u043c\u043e\u0442\u0440\u0438\u043c \u043f\u0440\u0438\u043c\u0435\u0440 \u0441 \u043d\u0430\u0448\u0438\u043c \u0444\u043e\u0440\u043c\u0430\u0442\u043e\u043c <strong>1-2-3-4<\/strong>:<\/p>\n<ul>\n<li>\n<p>\u0432\u0432\u0435\u0434\u0435\u043d\u043e <strong>1-2-3-4 <\/strong>(\u0438 \u044d\u0442\u043e\u043c\u0443 \u0436\u0435 \u0440\u0430\u0432\u0435\u043d \u0432\u043d\u0435\u0448\u043d\u0438\u0439 \u0442\u0435\u043a\u0441\u0442)<\/p>\n<\/li>\n<li>\n<p>\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u0432\u0432\u043e\u0434\u0438\u0442 \u0431\u0443\u043a\u0432\u0443 <strong>a<\/strong><\/p>\n<\/li>\n<li>\n<p>\u0432\u043d\u0443\u0442\u0440\u0435\u043d\u043d\u0438\u0439 \u0442\u0435\u043a\u0441\u0442 \u043c\u0435\u043d\u044f\u0435\u0442\u0441\u044f \u043d\u0430 <strong>1-2-3-4a <\/strong>\u0438 \u0431\u043e\u043b\u044c\u0448\u0435 \u043d\u0435 \u0440\u0430\u0432\u0435\u043d \u0432\u043d\u0435\u0448\u043d\u0435\u043c\u0443 \u0442\u0435\u043a\u0441\u0442\u0443<\/p>\n<\/li>\n<li>\n<p><strong>1-2-3-4a <\/strong>\u043e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0432 <strong>onTextChange<\/strong><\/p>\n<ul>\n<li>\n<p>\u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f <strong>onTextChange <\/strong>\u0444\u043e\u0440\u043c\u0430\u0442\u0438\u0440\u0443\u0435\u0442 <strong>1-2-3-4a<\/strong> \u0432 <strong>1-2-3-4 <\/strong>\u0438 \u043f\u043e\u0441\u044b\u043b\u0430\u0435\u0442 \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0435\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435 \u0432 <code>XObject<\/code> (\u0438\u043b\u0438 \u0432 \u043b\u044e\u0431\u0443\u044e \u0434\u0440\u0443\u0433\u0443\u044e \u0441\u0443\u0449\u043d\u043e\u0441\u0442\u044c, \u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0435\u043d\u043d\u0443\u044e \u0437\u0430 \u0445\u0440\u0430\u043d\u0435\u043d\u0438\u0435 \u0430\u043a\u0442\u0443\u0430\u043b\u044c\u043d\u043e\u0433\u043e \u0442\u0435\u043a\u0441\u0442\u0430)<\/p>\n<\/li>\n<li>\n<p>\u0432\u044b\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u043d\u043e\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435 (<strong>1-2-3-4<\/strong>) \u043f\u0435\u0440\u0435\u0434\u0430\u0435\u0442\u0441\u044f \u0432 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u0432\u043d\u0435\u0448\u043d\u0435\u0433\u043e \u0442\u0435\u043a\u0441\u0442\u0430 \u0432 \u043a\u043e\u043d\u0441\u0442\u0440\u0443\u043a\u0442\u043e\u0440\u0435 \u043d\u0430\u0448\u0435\u0433\u043e \u0438\u043d\u043f\u0443\u0442\u0430<\/p>\n<\/li>\n<li>\n<p>\u0432\u043d\u0435\u0448\u043d\u0438\u0439 \u0442\u0435\u043a\u0441\u0442 \u043d\u0435 \u0438\u0437\u043c\u0435\u043d\u0438\u043b\u0441\u044f, \u043d\u0438\u0447\u0435\u0433\u043e \u043d\u0435 \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0434\u0438\u0442 (\u0432 \u043f\u043b\u0430\u043d\u0435 <strong>body reevaluate<\/strong>)<\/p>\n<\/li>\n<\/ul>\n<\/li>\n<li>\n<p>\u043f\u043e\u043b\u0443\u0447\u0430\u0435\u043c \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435 \u0442\u0435\u043a\u0441\u0442\u0430 \u0432 \u043c\u043e\u043c\u0435\u043d\u0442\u0435. \u041e\u043d\u043e \u0440\u0430\u0432\u043d\u043e \u0442\u043e\u043c\u0443, \u0447\u0442\u043e \u0441\u0435\u0439\u0447\u0430\u0441 \u043d\u0430\u0445\u043e\u0434\u0438\u0442\u0441\u044f \u0432 \u043d\u0430\u0448\u0435\u043c <code>XObject<\/code>. \u0412 \u043d\u0430\u0448\u0435\u043c \u0441\u043b\u0443\u0447\u0430\u0435 \u044d\u0442\u043e <strong>1-2-3-4<\/strong><\/p>\n<\/li>\n<li>\n<p>\u0432\u043d\u0443\u0442\u0440\u0435\u043d\u043d\u0438\u0439 \u0442\u0435\u043a\u0441\u0442 \u043c\u0435\u043d\u044f\u0435\u0442\u0441\u044f \u043d\u0430 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435 \u0442\u0435\u043a\u0441\u0442\u0430 \u0432 \u043c\u043e\u043c\u0435\u043d\u0442\u0435, \u0430 \u0438\u043c\u0435\u043d\u043d\u043e \u043d\u0430 <strong>1-2-3-4<\/strong><\/p>\n<\/li>\n<\/ul>\n<figure class=\"\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/7b3\/b6d\/243\/7b3b6d2436b4b573fa100354d80111b2.gif\" width=\"336\" height=\"440\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/7b3\/b6d\/243\/7b3b6d2436b4b573fa100354d80111b2.gif\"\/><\/figure>\n<details class=\"spoiler\">\n<summary>XTextField.swift &#8212; \u043f\u0440\u043e\u043c\u0435\u0436\u0443\u0442\u043e\u0447\u043d\u044b\u0439 \u043a\u043e\u0434 #5<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"swift\">import SwiftUI  struct XTextField: View {          var text: String     var currentText: (() -> String)? = nil          var isEnabled: Bool = true     var hasError: Bool = false     var isSecureTextEntry: Bool = false          var label: String = \"\"     var placeholder: String = \"\"          var trailingImage: UIImage? = nil          var captionText: String? = nil          var onTextChange: (String) -> Void          var onTrailingImageClick: () -> Void = { }          @FocusState     var focusField: FocusField?     @State     private var internalText: String          init(         text: String,         currentText: (() -> String)? = nil,         isEnabled: Bool = true,         hasError: Bool = false,         isSecureTextEntry: Bool = false,         label: String = \"\",         placeholder: String = \"\",         trailingImage: UIImage? = nil,         captionText: String? = nil,         onTextChange: @escaping (String) -> Void,         onTrailingImageClick: @escaping () -> Void = { }     ) {         self.text = text         self.currentText = currentText         self.isEnabled = isEnabled         self.hasError = hasError         self.isSecureTextEntry = isSecureTextEntry         self.label = label         self.placeholder = placeholder         self.trailingImage = trailingImage         self.captionText = captionText         self.onTextChange = onTextChange         self.onTrailingImageClick = onTrailingImageClick         self._internalText = State(wrappedValue: text)     }          var body: some View {         let _ = Self._printChanges()         VStack(alignment: .leading, spacing: 0) {             let shouldUseLabel = isFocused || !internalText.isEmpty             Text(label)                 .font(labelFont)                 .foregroundColor(labelColor)                 .opacity(shouldUseLabel ? 1 : 0)                          Spacer().frame(height: 8)                          HStack(alignment: .center, spacing: 0) {                 ZStack(alignment: .leading) {                     Text(placeholder)                         .font(placeholderFont)                         .foregroundColor(placeholderColor)                         .opacity(shouldUseLabel ? 0 : 1)                     textField()                         .textFieldStyle(.plain) \/\/ https:\/\/stackoverflow.com\/a\/74745555                         .font(textFont)                         .foregroundColor(textColor)                         .accentColor(cursorColor)                 }                                  if let trailingImage = trailingImage {                     Spacer().frame(width: 16)                     Image(uiImage: trailingImage)                         .foregroundColor(trailingImageColor)                         .frame(width: 24, height: 24, alignment: .center)                         .onTapGesture { onTrailingImageClick() }                 }             }.frame(minHeight: 24)                          ZStack {                 Spacer()                 Rectangle()                     .fill(underlineColor)                     .frame(height: isFocused ? 2 : 1)             }.frame(height: 16, alignment: .bottom)                          if let captionText = captionText {                 Spacer().frame(height: 8)                 Text(captionText)                     .font(captionFont)                     .foregroundColor(captionColor)             }         }         .contentShape(Rectangle())         .onTapGesture {             focusField = isSecureTextEntry ? .secureField : .textField         }         .disabled(!isEnabled)         .opacity(isEnabled ? 1 : 0.4)         .onChange(of: isSecureTextEntry) { newIsSecureTextEntry in             if focusField != nil {                 focusField = newIsSecureTextEntry ? .secureField : .textField             }         }         .onChange(of: internalText) { newText in             if newText != text {                 onTextChange(newText)                 if let currentText = currentText {                     internalText = currentText()                 }             }         }         .onChange(of: text) { newText in             internalText = newText         }         .background(.random)     }          @ViewBuilder     private func textField() -> some View {         ZStack {             TextField(\"\", text: $internalText)                 .focused($focusField, equals: .textField)                 .opacity(isSecureTextEntry ? 0 : 1)                          SecureField(\"\", text: $internalText)                 .focused($focusField, equals: .secureField)                 .opacity(isSecureTextEntry ? 1 : 0)         }     }          var isFocused: Bool {         focusField != nil     } }  \/\/ MARK: Colors private extension XTextField {          var labelColor: Color { Color.gray }          var placeholderColor: Color { Color.gray }          var textColor: Color { Color.black }          var cursorColor: Color {         if hasError {             Color.red         } else {             Color.green         }     }          var underlineColor: Color {         if hasError {             Color.red         } else {             if isFocused {                 Color.black             } else {                 Color.gray             }         }     }          var captionColor: Color {         if hasError {             Color.red         } else {             Color.gray         }     }          var trailingImageColor: Color { Color.gray } }  \/\/ MARK: Fonts private extension XTextField {          var labelFont: Font { Font.caption }          var placeholderFont: Font { Font.body }          var textFont: Font { Font.body }          var captionFont: Font { Font.caption } }  extension XTextField {          enum FocusField {         case textField, secureField     } }  extension View {          @ViewBuilder     public func preventPasswordReset() -> some View {         onReceive(             NotificationCenter.default.publisher(                 for: UITextField.textDidBeginEditingNotification             )         ) { obj in             if let textField = obj.object as? UITextField {                 if textField.isSecureTextEntry {                     let currentText = textField.text ?? \"\"                     textField.text = \"\"                     textField.insertText(currentText)                 }             }         }     } }  private extension ShapeStyle where Self == Color {     static var random: Color {         Color(             red: .random(in: 0...1),             green: .random(in: 0...1),             blue: .random(in: 0...1)         )     } } <\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<details class=\"spoiler\">\n<summary>XScreen.swift &#8212; \u043f\u0440\u043e\u043c\u0435\u0436\u0443\u0442\u043e\u0447\u043d\u044b\u0439 \u043a\u043e\u0434 #5<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"swift\">import SwiftUI  class XObject&lt;T>: ObservableObject {     var value: T     lazy var update: (T) -> Void = {         { [weak self] newValue in             guard let self = self else { return }             self.value = newValue             self.objectWillChange.send()         }     }()          init(value: T) {         self.value = value     } }  struct XScreen: View {          @StateObject private var text1Object: XObject&lt;String>     @StateObject private var text2Object: XObject&lt;String>     @StateObject private var text1SecureObject: XObject&lt;Bool>          private var onText1Change: (String) -> Void     private var onText2Change: (String) -> Void          private var onTrailing1IconClick: () -> Void          private var text2CurrentText: () -> String          init() {         var text1Obj = XObject(value: \"TextField1\")         var text2Obj = XObject(value: \"TextField2\")         var text1SecureObj = XObject(value: false)                  self._text1Object = StateObject(wrappedValue: text1Obj)         self._text2Object = StateObject(wrappedValue: text2Obj)         self._text1SecureObject = StateObject(wrappedValue: text1SecureObj)                  self.onText1Change = { text in             text1Obj.update(text)             print(\"TextField1: text changed to \\(text)\")         }         self.onText2Change = { text in             let numbers = text                 .filter { $0.isNumber }                 .prefix(4)             let charArray = Array(numbers)             let formattedText = charArray                 .map { String($0) }                 .joined(separator: \"-\")             text2Obj.update(formattedText)             print(\"TextField2: text changed to \\(formattedText)\")         }         self.onTrailing1IconClick = {             text1SecureObj.update(!text1SecureObj.value)         }                  self.text2CurrentText = { text2Obj.value }     }          var body: some View {         VStack {             XTextField(                 text: text1Object.value,                 hasError: false,                 isSecureTextEntry: text1SecureObject.value,                 label: \"TextField1\",                 placeholder: \"TextField1\",                 trailingImage: UIImage.add.withRenderingMode(.alwaysTemplate),                 captionText: \"TextField1\",                 onTextChange: onText1Change,                 onTrailingImageClick: onTrailing1IconClick             ).padding(20)                          XTextField(                 text: text2Object.value,                 currentText: text2CurrentText,                 hasError: true,                 label: \"TextField2\",                 placeholder: \"TextField2\",                 trailingImage: UIImage.add.withRenderingMode(.alwaysTemplate),                 captionText: \"TextField2\",                 onTextChange: onText2Change             ).padding(20)         }         .preventPasswordReset()     } } <\/code><\/pre>\n<\/p>\n<\/p>\n<\/div>\n<\/details>\n<h3>\u041f\u0440\u043e\u0431\u043b\u0435\u043c\u0430 \u21166. \u041f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u044c<\/h3>\n<p>\u0412\u043e\u0437\u043c\u043e\u0436\u043d\u043e \u044d\u0442\u043e\u0439 \u0447\u0430\u0441\u0442\u0438 \u043d\u0438\u043a\u043e\u0433\u0434\u0430 \u0438 \u043d\u0435 \u0431\u044b\u043b\u043e \u0431\u044b, \u0435\u0441\u043b\u0438 \u0431\u044b \u043d\u0435 \u043e\u0434\u043d\u043e \u041d\u041e.<\/p>\n<p>\u041f\u043e \u043c\u0435\u0440\u0435 \u043d\u0430\u043f\u0438\u0441\u0430\u043d\u0438\u044f \u043f\u0440\u043e\u0434\u0430\u043a\u0448\u043d \u043a\u043e\u0434\u0430 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u043e\u0432\u0430\u043b\u0430 \u0435\u0449\u0435 \u043e\u0434\u043d\u0430 \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u0430 \u0441 <strong>body reevaluate<\/strong>, \u043e \u043a\u043e\u0442\u043e\u0440\u043e\u0439 \u0434\u0430\u043b\u044c\u0448\u0435 \u0438 \u043f\u043e\u0439\u0434\u0435\u0442 \u0440\u0435\u0447\u044c. \u041d\u043e \u0443 \u043d\u0430\u0441 \u0435\u0435 \u0441\u0435\u0439\u0447\u0430\u0441 \u043d\u0435\u0442. \u0422\u0430\u043a \u043f\u0440\u043e\u0438\u0437\u043e\u0448\u043b\u043e \u0438\u0437-\u0437\u0430 \u0442\u043e\u0433\u043e, \u0447\u0442\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043b\u0438\u0441\u044c \u0440\u0430\u0437\u043d\u044b\u0435 \u0432\u0435\u0440\u0441\u0438\u0438 \u044d\u043c\u0443\u043b\u044f\u0442\u043e\u0440\u043e\u0432 (<strong>iOS 16.4<\/strong> vs <strong>iOS 17.2<\/strong>).<\/p>\n<p>\u0418\u0442\u0430\u043a, <strong>\u0432\u0441\u0435<\/strong> gif \u0430\u043d\u0438\u043c\u0430\u0446\u0438\u0438 \u0432 \u0441\u0442\u0430\u0442\u044c\u0435 \u0432\u044b\u0448\u0435 \u0431\u044b\u043b\u0438 \u0437\u0430\u043f\u0438\u0441\u0430\u043d\u044b \u0441 \u044d\u043c\u0443\u043b\u044f\u0442\u043e\u0440\u0430 <strong>iOS 17.2<\/strong>. \u0414\u0430\u0432\u0430\u0439\u0442\u0435 \u0437\u0430\u043f\u0443\u0441\u0442\u0438\u043c \u0442\u043e \u0436\u0435 \u0441\u0430\u043c\u043e\u0435 \u043d\u0430 <strong>iOS 16.4<\/strong> \u0438 \u043f\u043e\u0441\u043c\u043e\u0442\u0440\u0438\u043c, \u0432 \u0447\u0435\u043c \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u0430.<\/p>\n<figure class=\"\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/7d4\/cac\/028\/7d4cac028dffa8d6f89bbb2e653ab3da.gif\" width=\"322\" height=\"440\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/7d4\/cac\/028\/7d4cac028dffa8d6f89bbb2e653ab3da.gif\"\/><\/figure>\n<p>\u041c\u044b \u043d\u0438\u043a\u0430\u043a \u043d\u0435 \u0432\u043e\u0437\u0434\u0435\u0439\u0441\u0442\u0432\u0443\u0435\u043c \u043d\u0430 \u0432\u0442\u043e\u0440\u043e\u0439 \u0438\u043d\u043f\u0443\u0442. \u041d\u043e \u0443 \u043d\u0435\u0433\u043e \u0432\u044b\u0437\u044b\u0432\u0430\u0435\u0442\u0441\u044f <strong>body reevaluate<\/strong>. \u041f\u0440\u0438\u0447\u0435\u043c \u0432\u044b\u0437\u044b\u0432\u0430\u0435\u0442\u0441\u044f \u0432 \u0434\u0432\u0443\u0445 \u0441\u043b\u0443\u0447\u0430\u044f\u0445:<\/p>\n<ul>\n<li>\n<p>\u0435\u0441\u043b\u0438 \u043c\u0435\u043d\u044f\u0435\u043c \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435 \u0431\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u043e\u0433\u043e \u0432\u0432\u043e\u0434\u0430 \u043f\u0435\u0440\u0432\u043e\u0433\u043e \u0438\u043d\u043f\u0443\u0442\u0430<\/p>\n<\/li>\n<li>\n<p>\u0435\u0441\u043b\u0438 \u0434\u0435\u043b\u0430\u0435\u043c \u0441\u0432\u0430\u0439\u043f \u044d\u043a\u0440\u0430\u043d\u0430 \u0432\u043d\u0438\u0437<\/p>\n<\/li>\n<\/ul>\n<p>\u0411\u0430\u0433 \u044d\u0442\u043e \u0438\u043b\u0438 \u043e\u0436\u0438\u0434\u0430\u0435\u043c\u043e\u0435 \u043f\u043e\u0432\u0435\u0434\u0435\u043d\u0438\u0435 \u2014 \u043d\u0435\u044f\u0441\u043d\u043e. \u041d\u043e, \u043a\u0430\u043a \u0443\u0436\u0435 \u043f\u0438\u0441\u0430\u043b \u0440\u0430\u043d\u0435\u0435, \u043d\u0430 <strong>iOS 17.2<\/strong> \u0442\u0430\u043a\u043e\u0433\u043e \u043f\u043e\u0432\u0435\u0434\u0435\u043d\u0438\u044f \u043d\u0435 \u043d\u0430\u0431\u043b\u044e\u0434\u0430\u0435\u0442\u0441\u044f.<\/p>\n<p>\u041f\u0440\u043e\u0431\u043b\u0435\u043c\u0430 \u043a\u0440\u043e\u0435\u0442\u0441\u044f \u0432 <code>@FocusState<\/code>, \u043a\u043e\u0442\u043e\u0440\u0430\u044f, \u0435\u0441\u043b\u0438 \u0447\u0435\u0441\u0442\u043d\u043e, \u0432\u0441\u0435 \u0436\u0435 \u0431\u043e\u043b\u044c\u0448\u0435 \u043f\u043e\u0445\u043e\u0436\u0430 \u043d\u0430 \u0431\u0430\u0433. \u041b\u044e\u0431\u044b\u0435 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f \u0444\u043e\u043a\u0443\u0441\u0430 \u043f\u0440\u0438\u0432\u043e\u0434\u044f\u0442 \u043a \u0442\u043e\u043c\u0443, \u0447\u0442\u043e \u0432\u0441\u0435 <strong>View<\/strong>, \u0432 \u043a\u043e\u0442\u043e\u0440\u044b\u0445 \u0435\u0441\u0442\u044c <strong>\u0447\u0442\u0435\u043d\u0438\u0435 <\/strong>\u0438\u0437 <code>@FocusState<\/code> \u043f\u043e\u043b\u044f, \u043f\u043e\u0434\u0432\u0435\u0440\u0433\u0430\u044e\u0442\u0441\u044f <strong>body reevaluate<\/strong>.<\/p>\n<p>\u0418\u0437\u0431\u0430\u0432\u0438\u0442\u044c\u0441\u044f \u043e\u0442 \u0434\u0430\u043d\u043d\u043e\u0439 \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u044b \u043f\u043e\u043b\u043d\u043e\u0441\u0442\u044c\u044e \u043d\u0435 \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u0441\u044f. \u041e\u0434\u043d\u0430\u043a\u043e, \u043c\u043e\u0436\u043d\u043e \u043c\u0438\u043d\u0438\u043c\u0438\u0437\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0435\u0435 \u0443\u0449\u0435\u0440\u0431 (\u0432\u0430\u0436\u043d\u043e \u043e\u0442\u043c\u0435\u0442\u0438\u0442\u044c, \u0447\u0442\u043e \u044d\u0442\u043e \u043d\u0435 \u0444\u0430\u043a\u0442, \u0430 \u043f\u0440\u0435\u0434\u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u0435-\u0434\u043e\u0433\u0430\u0434\u043a\u0430, \u0438, \u0435\u0441\u043b\u0438 \u044d\u0442\u043e \u043d\u0435 \u0442\u0430\u043a, \u0442\u043e \u043f\u043e\u0434\u043e\u0431\u043d\u043e\u0435 \u0440\u0435\u0448\u0435\u043d\u0438\u0435 \u0438\u0437\u043b\u0438\u0448\u043d\u0435)<\/p>\n<p>\u0427\u0442\u043e \u043c\u044b \u0441\u0434\u0435\u043b\u0430\u0435\u043c?<\/p>\n<ul>\n<li>\n<p>\u0441\u043e\u0437\u0434\u0430\u0434\u0438\u043c <code>FocusHolderView<\/code>, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u0431\u0443\u0434\u0435\u0442 \u0445\u0440\u0430\u043d\u0438\u0442\u044c <code>@FocusState<\/code> (\u0438 \u0437\u0430\u0431\u0438\u0440\u0430\u0442\u044c \u0432\u0435\u0441\u044c \u0443\u0434\u0430\u0440 \u043e\u0442 <strong>body reevaluate<\/strong> \u043d\u0430 \u0441\u0435\u0431\u044f)<\/p>\n<\/li>\n<li>\n<p>\u0432 \u043d\u0430\u0448\u0435\u043c <code>XTextField<\/code> \u043f\u0435\u0440\u0435\u0435\u0434\u0435\u043c \u0441 <code>@FocusState<\/code> \u043d\u0430 <code>FocusState.Binding<\/code>, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0431\u0443\u0434\u0435\u043c \u043f\u043e\u043b\u0443\u0447\u0430\u0442\u044c \u0438\u0437 <code>FocusHolderView<\/code><\/p>\n<\/li>\n<li>\n<p><code>FocusHolderView<\/code> \u0431\u0443\u0434\u0435\u0442 \u043f\u0440\u0438\u043d\u0438\u043c\u0430\u0442\u044c \u0432 \u043a\u043e\u043d\u0441\u0442\u0440\u0443\u043a\u0442\u043e\u0440\u0435 \u043a\u043b\u043e\u0436\u0443\u0440\u0443 <code>FocusState.Binding -> View<\/code><\/p>\n<\/li>\n<li>\n<p><code>XTextField<\/code> \u043f\u0435\u0440\u0435\u0438\u043c\u0435\u043d\u0443\u0435\u043c \u0432 <code>XTextField_Base<\/code> (\u0442\u0430\u043a \u043a\u0430\u043a \u0434\u0430\u043d\u043d\u0430\u044f <strong>View<\/strong> \u0431\u043e\u043b\u044c\u0448\u0435 \u043d\u0435 \u0431\u0443\u0434\u0435\u0442 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c\u0441\u044f \u043a\u043e\u043d\u0435\u0447\u043d\u044b\u043c \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435\u043c)<\/p>\n<\/li>\n<li>\n<p>\u0432\u044b\u043d\u0435\u0441\u0435\u043c \u0432\u044b\u0437\u043e\u0432 <code>FocusHolderView<\/code> + <code>XTextField_Base<\/code> \u0432 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u0443\u044e \u043d\u043e\u0432\u0443\u044e <strong>View<\/strong> \u2014 <code>XTextField<\/code>, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u0443\u0436\u0435, \u043a\u0430\u043a \u0440\u0430\u0437-\u0442\u0430\u043a\u0438, \u0431\u0443\u0434\u0435\u0442 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c\u0441\u044f \u043f\u0443\u0431\u043b\u0438\u0447\u043d\u043e<\/p>\n<\/li>\n<\/ul>\n<pre><code class=\"swift\">struct FocusHolderView&lt;Content: View, FSValue: Hashable>: View {          @FocusState     var focusValue: FSValue?          var content: (FocusState&lt;FSValue?>.Binding) -> Content          var body: some View {         content($focusValue)     } }<\/code><\/pre>\n<pre><code class=\"swift\">struct XTextField_Base: View {          ...      \/\/    @FocusState \/\/    var focusField: FocusField?     var focusField: FocusState&lt;FocusField?>.Binding          init(         ...         focusField: FocusState&lt;FocusField?>.Binding     ) {         ...         self.focusField = focusField     }          var body: some View {         VStack(alignment: .leading, spacing: 0) {             ...         }         .onTapGesture { \/\/            focusField = isSecureTextEntry ? .secureField : .textField             focusField.wrappedValue = isSecureTextEntry ? .secureField : .textField         }         .onChange(of: isSecureTextEntry) { newIsSecureTextEntry in \/\/            if focusField.wrappedValue != nil { \/\/                focusField.wrappedValue = newIsSecureTextEntry ? .secureField : .textField \/\/            }             if focusField.wrappedValue != nil {                 focusField.wrappedValue = newIsSecureTextEntry ? .secureField : .textField             }         }     }          @ViewBuilder     private func textField() -> some View {         ZStack {             TextField(\"\", text: $internalText) \/\/                .focused($focusField, equals: .textField)                 .focused(focusField, equals: .textField)                          SecureField(\"\", text: $internalText) \/\/                .focused($focusField, equals: .secureField)                 .focused(focusField, equals: .secureField)         }     }          var isFocused: Bool { \/\/        focusField != nil         focusField.wrappedValue != nil     } }<\/code><\/pre>\n<pre><code class=\"swift\">struct XTextField: View {          ... \/\/ \u0432\u0441\u0435 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b \u0434\u043b\u044f \u043a\u043e\u043d\u0441\u0442\u0440\u0443\u043a\u0442\u043e\u0440\u0430 \u0438\u0437 XTextField_Base + init \u0431\u043b\u043e\u043a          var body: some View {         FocusHolderView { focusBinding in             XTextField_Base(                 ...                 focusField: focusBinding             )         }     } }<\/code><\/pre>\n<figure class=\"\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/d83\/355\/7da\/d833557dae93ee83e9b4e70dc26f51e6.gif\" width=\"322\" height=\"440\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/d83\/355\/7da\/d833557dae93ee83e9b4e70dc26f51e6.gif\"\/><\/figure>\n<p> \u0412\u0438\u0434\u0438\u043c, \u0447\u0442\u043e \u043c\u044b \u0438\u0437\u0431\u0430\u0432\u0438\u043b\u0438\u0441\u044c \u043e\u0442 \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u044b \u0441 \u0438\u0437\u043b\u0438\u0448\u043d\u0435\u043c <strong>body reevaluate<\/strong>.<\/p>\n<blockquote>\n<p>\u041d\u0430 \u0441\u0430\u043c\u043e\u043c \u0434\u0435\u043b\u0435 \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u0430 \u043e\u0441\u0442\u0430\u043b\u0430\u0441\u044c. \u041f\u0440\u043e\u0441\u0442\u043e \u043e\u043d\u0430 \u043f\u0435\u0440\u0435\u0435\u0445\u0430\u043b\u0430 \u0432 <code>FocusHolderView<\/code>. <code>FocusHolderView.body<\/code> \u043a\u0443\u0434\u0430 \u043c\u0435\u043d\u044c\u0448\u0435, \u0447\u0435\u043c <code>body<\/code> \u043d\u0430\u0448\u0435\u0433\u043e \u0438\u043d\u043f\u0443\u0442\u0430 \u0438, \u0438\u0441\u0445\u043e\u0434\u044f \u0438\u0437 \u044d\u0442\u043e\u0433\u043e, \u0431\u044b\u043b\u043e \u0441\u0434\u0435\u043b\u0430\u043d\u043e \u043f\u0440\u0435\u0434\u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u0435, \u0447\u0442\u043e \u0442\u0430\u043a\u0438\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c \u043c\u043e\u0436\u043d\u043e \u043c\u0438\u043d\u0438\u043c\u0438\u0437\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0443\u0449\u0435\u0440\u0431 \u043e\u0442 \u0434\u0430\u043d\u043d\u043e\u0439 \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u044b.<\/p>\n<\/blockquote>\n<h2>\u0417\u0430\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435<\/h2>\n<p>\u041c\u044b \u043f\u0440\u043e\u0434\u0435\u043b\u0430\u043b\u0438 \u0431\u043e\u043b\u044c\u0448\u043e\u0439 \u043f\u0443\u0442\u044c, \u043f\u043e\u043f\u0443\u0442\u043d\u043e \u043f\u0440\u0438\u043c\u0435\u043d\u044f\u044f \u043e\u0447\u0435\u0432\u0438\u0434\u043d\u044b\u0435 \u0440\u0435\u0448\u0435\u043d\u0438\u044f \u0438 \u043d\u0435 \u043e\u0447\u0435\u043d\u044c. \u0412\u044b\u0441\u0442\u0440\u043e\u0438\u043b\u0438 \u0438\u043d\u043f\u0443\u0442, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043e\u0442\u0432\u0435\u0447\u0430\u0435\u0442 \u0431\u043e\u043b\u044c\u0448\u043e\u043c\u0443 \u0447\u0438\u0441\u043b\u0443 \u0442\u0440\u0435\u0431\u043e\u0432\u0430\u043d\u0438\u0439: \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442 \u0444\u043e\u0440\u043c\u0430\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435, \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u0441 \u043f\u0430\u0440\u043e\u043b\u044f\u043c\u0438, <s>\u0441\u0430\u043c \u0443\u0431\u0438\u0440\u0430\u0435\u0442\u0441\u044f \u0438 \u043f\u044b\u043b\u0435\u0441\u043e\u0441\u0438\u0442<\/s> \u0438 \u043c\u043d\u043e\u0433\u043e\u0435 \u0434\u0440\u0443\u0433\u043e\u0435. \u0411\u043e\u043b\u0435\u0435 \u0442\u043e\u0433\u043e, \u043d\u0435 \u0437\u0430\u0431\u044b\u043b\u0438 \u043f\u0440\u043e \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u0438 \u0438 \u0440\u0430\u0437\u043e\u0431\u0440\u0430\u043b\u0438 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442 \u0438 \u0441 \u044d\u0442\u043e\u0433\u043e \u0430\u0441\u043f\u0435\u043a\u0442\u0430 \u0442\u043e\u0436\u0435.<\/p>\n<p>\u041a\u0441\u0442\u0430\u0442\u0438, \u0444\u0438\u043d\u0430\u043b\u044c\u043d\u044b\u0439 \u043a\u043e\u0434, \u0430 \u0442\u0430\u043a\u0436\u0435 \u0438\u0441\u0442\u043e\u0440\u0438\u044e \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0439 \u0432 \u0432\u0438\u0434\u0435 \u043a\u043e\u043c\u043c\u0438\u0442\u043e\u0432 \u043c\u043e\u0436\u043d\u043e \u043d\u0430\u0439\u0442\u0438 <a href=\"https:\/\/github.com\/C5FR7Q\/x-text-field\" rel=\"noopener noreferrer nofollow\">\u043f\u043e \u044d\u0442\u043e\u0439 \u0441\u0441\u044b\u043b\u043a\u0435<\/a>.  <\/p>\n<p>\u0411\u044b\u043b \u0440\u0430\u0434 \u043f\u043e\u0434\u0435\u043b\u0438\u0442\u044c\u0441\u044f \u043e\u043f\u044b\u0442\u043e\u043c. \u0410 \u0441 \u043a\u0430\u043a\u0438\u043c\u0438 \u0441\u043b\u043e\u0436\u043d\u043e\u0441\u0442\u044f\u043c\u0438 \u0432\u0430\u043c \u043f\u0440\u0438\u0445\u043e\u0434\u0438\u043b\u043e\u0441\u044c \u0441\u0442\u0430\u043b\u043a\u0438\u0432\u0430\u0442\u044c\u0441\u044f \u0432 <strong>SwiftUI<\/strong>?<\/p>\n<\/p>\n<\/div>\n<\/div>\n<\/div>\n<p><!----><!----><\/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\/articles\/815685\/\"> https:\/\/habr.com\/ru\/articles\/815685\/<\/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<p>\u041f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u044c\u0442\u0435, \u0447\u0442\u043e \u0432\u0430\u043c \u043d\u0443\u0436\u043d\u043e \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u0442\u044c \u043a\u043e\u043c\u043f\u043b\u0435\u043a\u0441\u043d\u044b\u0439 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442 \u0438\u043d\u043f\u0443\u0442\u0430 \u043d\u0430 SwiftUI. \u0427\u0442\u043e \u043c\u043e\u0436\u0435\u0442 \u043f\u043e\u0439\u0442\u0438 \u043d\u0435 \u0442\u0430\u043a? \u041a\u0430\u043a\u0438\u0435 \u0441\u043b\u043e\u0436\u043d\u043e\u0441\u0442\u0438 \u043c\u043e\u0433\u0443\u0442 \u0432\u043e\u0437\u043d\u0438\u043a\u043d\u0443\u0442\u044c? \u041d\u0430 \u044d\u0442\u0438 \u0438 \u0434\u0440\u0443\u0433\u0438\u0435 \u0432\u043e\u043f\u0440\u043e\u0441\u044b \u043f\u043e\u0441\u0442\u0430\u0440\u0430\u044e\u0441\u044c \u043e\u0442\u0432\u0435\u0442\u0438\u0442\u044c \u0432 \u0441\u0442\u0430\u0442\u044c\u0435, \u0433\u0434\u0435 \u0440\u0435\u0430\u043b\u0438\u0437\u0443\u0435\u043c \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442 \u0441\u043e \u0441\u043a\u0440\u0438\u043d\u0430, \u0430 \u0442\u0430\u043a\u0436\u0435 \u0440\u0430\u0437\u0431\u0435\u0440\u0435\u043c \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u044b\u0435 \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u044b.<\/p>\n<figure class=\"full-width\"><\/figure>\n<details class=\"spoiler\">\n<summary>\u0421\u043f\u043e\u0439\u043b\u0435\u0440<\/summary>\n<div class=\"spoiler__content\">\n<p>\u041e\u0434\u043d\u0438 \u0440\u0435\u0448\u0435\u043d\u0438\u044f, \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u043d\u044b\u0435 \u0432 \u0434\u0430\u043d\u043d\u043e\u0439 \u0441\u0442\u0430\u0442\u044c\u0435, \u0433\u0443\u0433\u043b\u044f\u0442\u0441\u044f \u0431\u0435\u0437 \u043e\u0441\u043e\u0431\u043e\u0433\u043e \u0442\u0440\u0443\u0434\u0430.<\/p>\n<p>\u0414\u0440\u0443\u0433\u0438\u0435 \u0436\u0435 \u0443\u043d\u0438\u043a\u0430\u043b\u044c\u043d\u044b \u0438 \u044f\u0432\u043b\u044f\u044e\u0442\u0441\u044f \u0441\u043e\u0431\u0441\u0442\u0432\u0435\u043d\u043d\u044b\u043c <s>\u0432\u0435\u043b\u043e\u0441\u0438\u043f\u0435\u0434\u043e\u043c<\/s> \u0438\u0437\u043e\u0431\u0440\u0435\u0442\u0435\u043d\u0438\u0435\u043c. \u0412\u043e\u0437\u043c\u043e\u0436\u043d\u043e \u043e\u043d\u0438 \u043d\u0435 \u0438\u0434\u0435\u0430\u043b\u044c\u043d\u044b, \u043d\u043e \u043e\u043d\u0438 \u0440\u0430\u0431\u043e\u0442\u0430\u044e\u0442. \u0415\u0441\u043b\u0438 \u0443 \u0432\u0430\u0441 \u0435\u0441\u0442\u044c \u0430\u043b\u044c\u0442\u0435\u0440\u043d\u0430\u0442\u0438\u0432\u043d\u044b\u0435 \u0440\u0435\u0448\u0435\u043d\u0438\u044f \u043f\u043e\u0434\u043e\u0431\u043d\u044b\u0445 \u043f\u0440\u043e\u0431\u043b\u0435\u043c \u2014 \u0432\u0435\u043b\u043a\u043e\u043c \u0432 \u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u0438 \ud83d\ude42<\/p>\n<\/div>\n<\/details>\n<h3>\u041f\u0435\u0440\u0432\u044b\u0435 \u0448\u0430\u0433\u0438<\/h3>\n<p>\u041d\u0430\u0431\u0440\u0430\u0441\u044b\u0432\u0430\u0435\u043c \u043a\u043e\u0434 \u0434\u043b\u044f \u0441\u0430\u043c\u043e\u0433\u043e \u0438\u043d\u043f\u0443\u0442\u0430:<\/p>\n<details class=\"spoiler\">\n<summary>XTextField.swift &#8212; \u043f\u0440\u043e\u043c\u0435\u0436\u0443\u0442\u043e\u0447\u043d\u044b\u0439 \u043a\u043e\u0434 #0<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"swift\">import SwiftUI  struct XTextField: View {          var text: String          var isEnabled: Bool = true     var hasError: Bool = false          var label: String = \"\"     var placeholder: String = \"\"          var trailingImage: UIImage? = nil          var captionText: String? = nil          var onTextChange: (String) -> Void          var onTrailingImageClick: () -> Void = { }          var body: some View {         VStack(alignment: .leading, spacing: 0) {             let shouldUseLabel = true \/\/ \u0418\u0437\u043c\u0435\u043d\u0438\u043c \u043f\u043e\u0441\u043b\u0435             Text(label)                 .font(labelFont)                 .foregroundColor(labelColor)                 .opacity(shouldUseLabel ? 1 : 0)                          Spacer().frame(height: 8)                          HStack(alignment: .center, spacing: 0) {                 ZStack(alignment: .leading) {                     Text(placeholder)                         .font(placeholderFont)                         .foregroundColor(placeholderColor)                         .opacity(shouldUseLabel ? 0 : 1)                     textField()                         .font(textFont)                         .foregroundColor(textColor)                         .accentColor(cursorColor)                 }                                  if let trailingImage = trailingImage {                     Spacer().frame(width: 16)                     Image(uiImage: trailingImage)                         .foregroundColor(trailingImageColor)                         .frame(width: 24, height: 24, alignment: .center)                         .onTapGesture { onTrailingImageClick() }                 }             }.frame(minHeight: 24)                          ZStack {                 Spacer()                 Rectangle()                     .fill(underlineColor)                     .frame(height: 1)             }.frame(height: 16, alignment: .bottom)                          if let captionText = captionText {                 Spacer().frame(height: 8)                 Text(captionText)                     .font(captionFont)                     .foregroundColor(captionColor)             }         }         .disabled(!isEnabled)         .opacity(isEnabled ? 1 : 0.4)     }          @ViewBuilder     private func textField() -> some View { \/\/ \u0432\u044b\u043d\u043e\u0441 \u0432 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u0443\u044e \u0444\u0443\u043d\u043a\u0446\u0438\u044e \u043f\u043e\u043c\u043e\u0436\u0435\u0442 \u0432 \u0434\u0430\u043b\u044c\u043d\u0435\u0439\u0448\u0435\u043c         TextField(\"\", text: Binding(             get: { text },             set: { onTextChange($0) }         ))     } }  \/\/ MARK: Colors private extension XTextField {          var labelColor: Color { Color.gray }          var placeholderColor: Color { Color.gray }          var textColor: Color { Color.black }          var cursorColor: Color {         if hasError {             Color.red         } else {             Color.green         }     }          var underlineColor: Color {         if hasError {             Color.red         } else {             Color.gray         }     }          var captionColor: Color {         if hasError {             Color.red         } else {             Color.gray         }     }          var trailingImageColor: Color { Color.gray } }  \/\/ MARK: Fonts private extension XTextField {          var labelFont: Font { Font.caption }          var placeholderFont: Font { Font.body }          var textFont: Font { Font.body }          var captionFont: Font { Font.caption } } <\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<p>\u0421\u0430\u043c\u0430 \u0432\u0435\u0440\u0441\u0442\u043a\u0430 \u043d\u0435 \u0442\u0430\u043a\u0430\u044f \u0443\u0436 \u0438 \u0441\u043b\u043e\u0436\u043d\u0430\u044f. \u041a\u043e\u043c\u043c\u0435\u043d\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0435\u0451 \u043d\u0435 \u0432\u0445\u043e\u0434\u0438\u043b\u043e \u0432 \u043f\u043b\u0430\u043d\u044b. \u042d\u0442\u043e\u0442 \u043a\u043e\u0434 \u043d\u0430\u043c \u043d\u0443\u0436\u0435\u043d \u043a\u0430\u043a \u043e\u0442\u043f\u0440\u0430\u0432\u043d\u0430\u044f \u0442\u043e\u0447\u043a\u0430 \u0434\u043b\u044f \u0440\u0435\u0448\u0435\u043d\u0438\u044f \u0434\u0430\u043b\u044c\u043d\u0435\u0439\u0448\u0438\u0445 \u043f\u0440\u043e\u0431\u043b\u0435\u043c. <\/p>\n<p>\u041d\u0430\u0431\u0440\u0430\u0441\u044b\u0432\u0430\u0435\u043c \u043a\u043e\u0434 \u0434\u043b\u044f \u0441\u043a\u0440\u0438\u043d\u0430, \u043d\u0430 \u043a\u043e\u0442\u043e\u0440\u043e\u043c \u043c\u044b \u0431\u0443\u0434\u0435\u043c \u0432\u0441\u0435 \u044d\u0442\u043e \u043e\u0431\u043a\u0430\u0442\u044b\u0432\u0430\u0442\u044c:<\/p>\n<details class=\"spoiler\">\n<summary>XScreen.swift &#8212; \u043f\u0440\u043e\u043c\u0435\u0436\u0443\u0442\u043e\u0447\u043d\u044b\u0439 \u043a\u043e\u0434 #0<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"swift\">import SwiftUI  struct XScreen: View {          @State private var text1 = \"TextField1\"     @State private var text2 = \"TextField2\"          var body: some View {         VStack {             XTextField(                 text: text1,                 hasError: false,                 label: \"TextField1\",                 placeholder: \"TextField1\",                 trailingImage: UIImage.add.withRenderingMode(.alwaysTemplate),                 captionText: \"TextField1\",                 onTextChange: { text in                     text1 = text                     print(\"TextField1: text changed to \\(text)\")                 }             ).padding(20)                          XTextField(                 text: text2,                 hasError: true,                 label: \"TextField2\",                 placeholder: \"TextField2\",                 trailingImage: UIImage.add.withRenderingMode(.alwaysTemplate),                 captionText: \"TextField2\",                 onTextChange: { text in                     text2 = text                     print(\"TextField2: text changed to \\(text)\")                 }             ).padding(20)         }     } } <\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<p> \u0417\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u043c, \u0441\u043c\u043e\u0442\u0440\u0438\u043c. \u0412\u0440\u043e\u0434\u0435 \u0434\u0430\u0436\u0435 \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442.<\/p>\n<figure class=\"\">\n<div><figcaption>\u041d\u0430\u0447\u0430\u043b\u043e<\/figcaption><\/div>\n<\/figure>\n<h3>\u041f\u0440\u043e\u0431\u043b\u0435\u043c\u0430 \u21161. \u0414\u0432\u043e\u0439\u043d\u043e\u0435 \u0441\u043e\u0431\u044b\u0442\u0438\u0435 \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u044f<\/h3>\n<p>\u0421\u043c\u043e\u0442\u0440\u0438\u043c \u0432 \u043b\u043e\u0433. \u041e\u0431\u043d\u0430\u0440\u0443\u0436\u0438\u0432\u0430\u0435\u043c, \u0447\u0442\u043e \u0441\u043e\u0431\u044b\u0442\u0438\u0435 \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u044f \u0442\u0435\u043a\u0441\u0442\u0430 (<code>onTextChange<\/code> ) \u0432\u044b\u0437\u044b\u0432\u0430\u0435\u0442\u0441\u044f \u0434\u0432\u0430\u0436\u0434\u044b:<\/p>\n<pre><code>TextField1: text changed to TextField1 TextField1: text changed to TextField1 TextField1: text changed to TextField1a TextField1: text changed to TextField1a TextField1: text changed to TextField1as TextField1: text changed to TextField1as TextField1: text changed to TextField1asd TextField1: text changed to TextField1asd TextField1: text changed to TextField1asda TextField1: text changed to TextField1asda<\/code><\/pre>\n<p>\u041e\u0447\u0435\u0432\u0438\u0434\u043d\u043e, \u0447\u0442\u043e \u0447\u0442\u043e-\u0442\u043e \u043f\u043e\u0448\u043b\u043e \u043d\u0435 \u0442\u0430\u043a&#8230; \u0413\u0443\u0433\u043b\u0438\u043c \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u0443, \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u043c <a href=\"https:\/\/stackoverflow.com\/questions\/70734379\/why-is-didset-called-twice-on-the-textfield-binding-in-swiftui\/74745555#74745555\" rel=\"noopener noreferrer nofollow\">\u0440\u0435\u0448\u0435\u043d\u0438\u0435<\/a>. \u0427\u0442\u043e \u0437\u0434\u0435\u0441\u044c \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0434\u0438\u0442? \u041f\u043e\u0447\u0435\u043c\u0443 \u0440\u0435\u0448\u0435\u043d\u0438\u0435 \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442? \u041d\u0430 \u044d\u0442\u0438 \u0432\u043e\u043f\u0440\u043e\u0441\u044b \u043e\u0442\u0432\u0435\u0442\u043e\u0432 \u0443 \u043c\u0435\u043d\u044f \u043d\u0435\u0442&#8230;<\/p>\n<p>\u0412\u0441\u0435, \u0447\u0442\u043e \u043d\u0443\u0436\u043d\u043e \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u2014 \u044d\u0442\u043e \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0432\u044b\u0437\u043e\u0432 <code>textFieldStyle(.plain)<\/code> \u0441\u0440\u0430\u0437\u0443 \u043f\u043e\u0441\u043b\u0435 <code>textField()<\/code>.<\/p>\n<p>\u041f\u043e\u0441\u043b\u0435 \u0437\u0430\u043f\u0443\u0441\u043a\u0430 \u0432\u0438\u0434\u0438\u043c, \u0447\u0442\u043e \u0432 \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u043d\u043e\u043c \u043b\u043e\u0433\u0435 \u0432\u0441\u0435 \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u043a\u0430\u043a \u0441\u043b\u0435\u0434\u0443\u0435\u0442:<\/p>\n<pre><code>TextField1: text changed to TextField1 TextField1: text changed to TextField1a TextField1: text changed to TextField1as TextField1: text changed to TextField1asd TextField1: text changed to TextField1asda<\/code><\/pre>\n<details class=\"spoiler\">\n<summary>XTextField.swift &#8212; \u043f\u0440\u043e\u043c\u0435\u0436\u0443\u0442\u043e\u0447\u043d\u044b\u0439 \u043a\u043e\u0434 #1<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"swift\">import SwiftUI   struct XTextField: View {          var text: String          var isEnabled: Bool = true     var hasError: Bool = false          var label: String = \"\"     var placeholder: String = \"\"          var trailingImage: UIImage? = nil          var captionText: String? = nil          var onTextChange: (String) -> Void          var onTrailingImageClick: () -> Void = { }          var body: some View {         VStack(alignment: .leading, spacing: 0) {             let shouldUseLabel = true \/\/ \u0418\u0437\u043c\u0435\u043d\u0438\u043c \u043f\u043e\u0441\u043b\u0435             Text(label)                 .font(labelFont)                 .foregroundColor(labelColor)                 .opacity(shouldUseLabel ? 1 : 0)                          Spacer().frame(height: 8)                          HStack(alignment: .center, spacing: 0) {                 ZStack(alignment: .leading) {                     Text(placeholder)                         .font(placeholderFont)                         .foregroundColor(placeholderColor)                         .opacity(shouldUseLabel ? 0 : 1)                     textField()                         .textFieldStyle(.plain) \/\/ https:\/\/stackoverflow.com\/a\/74745555                         .font(textFont)                         .foregroundColor(textColor)                         .accentColor(cursorColor)                 }                                  if let trailingImage = trailingImage {                     Spacer().frame(width: 16)                     Image(uiImage: trailingImage)                         .foregroundColor(trailingImageColor)                         .frame(width: 24, height: 24, alignment: .center)                         .onTapGesture { onTrailingImageClick() }                 }             }.frame(minHeight: 24)                          ZStack {                 Spacer()                 Rectangle()                     .fill(underlineColor)                     .frame(height: 1)             }.frame(height: 16, alignment: .bottom)                          if let captionText = captionText {                 Spacer().frame(height: 8)                 Text(captionText)                     .font(captionFont)                     .foregroundColor(captionColor)             }         }         .disabled(!isEnabled)         .opacity(isEnabled ? 1 : 0.4)     }          @ViewBuilder     private func textField() -> some View {         TextField(\"\", text: Binding(             get: { text },             set: { onTextChange($0) }         ))     } }  \/\/ MARK: Colors private extension XTextField {          var labelColor: Color { Color.gray }          var placeholderColor: Color { Color.gray }          var textColor: Color { Color.black }          var cursorColor: Color {         if hasError {             Color.red         } else {             Color.green         }     }          var underlineColor: Color {         if hasError {             Color.red         } else {             Color.gray         }     }          var captionColor: Color {         if hasError {             Color.red         } else {             Color.gray         }     }          var trailingImageColor: Color { Color.gray } }  \/\/ MARK: Fonts private extension XTextField {          var labelFont: Font { Font.caption }          var placeholderFont: Font { Font.body }          var textFont: Font { Font.body }          var captionFont: Font { Font.caption } } <\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<h3>\u041f\u0440\u043e\u0431\u043b\u0435\u043c\u0430 \u21162. \u041f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u044c<\/h3>\n<p>\u0412\u0440\u044f\u0434 \u043b\u0438 \u043d\u0430 \u0442\u0435\u043a\u0443\u0449\u0435\u043c \u044d\u0442\u0430\u043f\u0435 \u0432\u044b \u0437\u0430\u0434\u0443\u043c\u0430\u0435\u0442\u0435\u0441\u044c \u043e \u0442\u0430\u043a\u043e\u0439 <s>\u043d\u0435<\/s>\u0432\u0430\u0436\u043d\u043e\u0439 \u0448\u0442\u0443\u043a\u0435, \u043a\u0430\u043a \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u044c. \u0418 \u044f <\/p>\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-378456","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/378456","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=378456"}],"version-history":[{"count":0,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/378456\/revisions"}],"wp:attachment":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=378456"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=378456"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=378456"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}