{"id":938,"date":"2024-08-28T23:04:48","date_gmt":"2024-08-28T21:04:48","guid":{"rendered":"https:\/\/mic.st\/blog\/?p=938"},"modified":"2025-09-02T15:55:15","modified_gmt":"2025-09-02T13:55:15","slug":"labeled-textfield-in-swiftui","status":"publish","type":"post","link":"https:\/\/mic.st\/blog\/labeled-textfield-in-swiftui\/","title":{"rendered":"Create a labeled TextField in SwiftUI"},"content":{"rendered":"\n<p>Today, I wanted to create a labeled <code>TextField<\/code> in SwiftUI. More specifically the label should appear as a small text above the <code>TextField<\/code>. My first quick hack was to just add a label above a <code>TextField<\/code>. Obviously, we can do better. Let me show you how.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">LabeledContent<\/h2>\n\n\n\n<p>As a major thing in SwiftUI is its declarative syntax, it is not a big surprise that there is already an official element which does exactly what we want, i.e. labeling content. And it is also named like that.<\/p>\n\n\n\n<p>According to the official docs, it is &#8220;A container for attaching a label to a value-bearing view.&#8221; (see <a href=\"https:\/\/developer.apple.com\/documentation\/swiftui\/labeledcontent\" target=\"_blank\" rel=\"noopener\">https:\/\/developer.apple.com\/documentation\/swiftui\/labeledcontent<\/a>). It is really exactly what we want. I mean almost.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Default behavior<\/h2>\n\n\n\n<p>The following snippet shows the bare minimum (with some styling of the textfield):<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-1\" data-shcb-language-name=\"Swift\" data-shcb-language-slug=\"swift\"><span><code class=\"hljs language-swift shcb-code-table shcb-line-numbers\"><span class='shcb-loc'><span><span class=\"hljs-type\">LabeledContent<\/span> {\n<\/span><\/span><span class='shcb-loc'><span>\t<span class=\"hljs-type\">TextField<\/span>(<span class=\"hljs-string\">\"\"<\/span>, text: $text)\n<\/span><\/span><span class='shcb-loc'><span>\t\t.textFieldStyle(.roundedBorder)\n<\/span><\/span><span class='shcb-loc'><span>\t\t.frame(width: <span class=\"hljs-number\">200<\/span>)\n<\/span><\/span><span class='shcb-loc'><span>} label: {\n<\/span><\/span><span class='shcb-loc'><span>\t<span class=\"hljs-type\">Text<\/span>(<span class=\"hljs-string\">\"Name\"<\/span>)\n<\/span><\/span><span class='shcb-loc'><span>}\n<\/span><\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-1\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">Swift<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">swift<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>It is a labeled Textfield but it is not exactly looking like what we wanted. It will result in something like this:<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"254\" height=\"46\" src=\"https:\/\/mic.st\/blog\/wp-content\/uploads\/2024\/08\/grafik.png\" alt=\"\" class=\"wp-image-939\"\/><figcaption class=\"wp-element-caption\">LabeledContent wrapped around a TextField in SwiftUI<\/figcaption><\/figure>\n<\/div>\n\n\n<p>As we see, the default layout is horizontal and not our desired vertical layout. Also there is a huge margin we do not want. Can we modify this? Yes, of course.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Custom LabeledContentStyle<\/h2>\n\n\n\n<p>A lot of SwiftUI elements can be modified by using an associated style configuration. Luckily, this is also possible for <code>LabeledContent<\/code>. By doing this instead of building a custom <code>View<\/code>, we still get all the nice SwiftUI sugar and accessibility features for free.<\/p>\n\n\n\n<p>There is the following modifier we can use for that:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-2\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml\">func labeledContentStyle<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">S<\/span>&gt;<\/span>(_ style: S) -&gt; some View where S : LabeledContentStyle<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-2\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>As we see, this modifier needs a style that conforms to <code>LabeledContentStyle<\/code> passed into it. Based on the passed style, the <code>LabeledContent<\/code> is styled however we want.<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>We want to have a label above the textfield (we need a <code>VStack<\/code>)<\/li>\n\n\n\n<li>The label should be a little smaller than the <code>TextField<\/code>&#8216;s input (we modify the label with some smaller font)<\/li>\n\n\n\n<li>Label and textfield should be aligned to the leading edge (set alignment of the <code>VStack<\/code> to <code>.leading<\/code>).<\/li>\n<\/ol>\n\n\n\n<p>With these requirements, we can build a really simple struct conforming to <code>LabeledContentStyle<\/code>:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-3\" data-shcb-language-name=\"Swift\" data-shcb-language-slug=\"swift\"><span><code class=\"hljs language-swift shcb-code-table shcb-line-numbers\"><span class='shcb-loc'><span><span class=\"hljs-class\"><span class=\"hljs-keyword\">struct<\/span> <span class=\"hljs-title\">TopLabeledStyleConfig<\/span>: <span class=\"hljs-title\">LabeledContentStyle<\/span> <\/span>{\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>\t<span class=\"hljs-function\"><span class=\"hljs-keyword\">func<\/span> <span class=\"hljs-title\">makeBody<\/span><span class=\"hljs-params\">(configuration: Configuration)<\/span><\/span> -&gt; some <span class=\"hljs-type\">View<\/span> {\n<\/span><\/span><span class='shcb-loc'><span>\t\t<span class=\"hljs-type\">VStack<\/span>(alignment: .leading) {\n<\/span><\/span><span class='shcb-loc'><span>\t\t\tconfiguration.label\n<\/span><\/span><span class='shcb-loc'><span>\t\t\t\t.font(.caption)\n<\/span><\/span><span class='shcb-loc'><span>\t\t\tconfiguration.content\n<\/span><\/span><span class='shcb-loc'><span>\t\t}\n<\/span><\/span><span class='shcb-loc'><span>\t}\n<\/span><\/span><span class='shcb-loc'><span>}\n<\/span><\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-3\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">Swift<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">swift<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>After that, we can append our newly created style via the <code>labeledContentStyle<\/code> modifier:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-4\" data-shcb-language-name=\"Swift\" data-shcb-language-slug=\"swift\"><span><code class=\"hljs language-swift shcb-code-table shcb-line-numbers\"><span class='shcb-loc'><span><span class=\"hljs-type\">LabeledContent<\/span> {\n<\/span><\/span><span class='shcb-loc'><span>\t<span class=\"hljs-type\">TextField<\/span>(<span class=\"hljs-string\">\"\"<\/span>, text: $text)\n<\/span><\/span><span class='shcb-loc'><span>\t\t.textFieldStyle(.roundedBorder)\n<\/span><\/span><span class='shcb-loc'><span>\t\t.frame(width: <span class=\"hljs-number\">200<\/span>)\n<\/span><\/span><span class='shcb-loc'><span>} label: {\n<\/span><\/span><span class='shcb-loc'><span>\t<span class=\"hljs-type\">Text<\/span>(<span class=\"hljs-string\">\"Name\"<\/span>)\n<\/span><\/span><span class='shcb-loc'><span>}\n<\/span><\/span><span class='shcb-loc'><span>.labeledContentStyle(<span class=\"hljs-type\">TopLabeledStyleConfig<\/span>())\n<\/span><\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-4\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">Swift<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">swift<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Which will result in this:<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"167\" height=\"58\" src=\"https:\/\/mic.st\/blog\/wp-content\/uploads\/2024\/08\/grafik-1.png\" alt=\"\" class=\"wp-image-940\"\/><figcaption class=\"wp-element-caption\">Textfield wrapped with LabeledContent using custom style<\/figcaption><\/figure>\n<\/div>\n\n\n<p>Much better right?<\/p>\n\n\n\n<p>Of course now you can fine tune it, change the margin, font type, etc. however you like. You could also think about adding small animations when focussing the textfield and so on.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">TopLabeledTextField<\/h2>\n\n\n\n<p>If you want to reuse this labeled and styled <code>TextField<\/code> in your SwiftUI views more often, it makes sense to create a reusable component. By that you do not have to think about adding your modifier manually.<\/p>\n\n\n\n<p>Just put everything in one file and you have your own <code>TopLabeledTextField<\/code>:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-5\" data-shcb-language-name=\"Swift\" data-shcb-language-slug=\"swift\"><span><code class=\"hljs language-swift shcb-code-table shcb-line-numbers\"><span class='shcb-loc'><span><span class=\"hljs-class\"><span class=\"hljs-keyword\">struct<\/span> <span class=\"hljs-title\">TopLabeledStyleConfig<\/span>: <span class=\"hljs-title\">LabeledContentStyle<\/span> <\/span>{\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>\t<span class=\"hljs-function\"><span class=\"hljs-keyword\">func<\/span> <span class=\"hljs-title\">makeBody<\/span><span class=\"hljs-params\">(configuration: Configuration)<\/span><\/span> -&gt; some <span class=\"hljs-type\">View<\/span> {\n<\/span><\/span><span class='shcb-loc'><span>\t\t<span class=\"hljs-type\">VStack<\/span>(alignment: .leading) {\n<\/span><\/span><span class='shcb-loc'><span>\t\t\tconfiguration.label\n<\/span><\/span><span class='shcb-loc'><span>\t\t\t\t.font(.caption)\n<\/span><\/span><span class='shcb-loc'><span>\t\t\tconfiguration.content\n<\/span><\/span><span class='shcb-loc'><span>\t\t}\n<\/span><\/span><span class='shcb-loc'><span>\t}\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>}\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-class\"><span class=\"hljs-keyword\">struct<\/span> <span class=\"hljs-title\">TopLabeledTextField<\/span>: <span class=\"hljs-title\">View<\/span> <\/span>{\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>\t@<span class=\"hljs-type\">Binding<\/span> <span class=\"hljs-keyword\">var<\/span> text: <span class=\"hljs-type\">String<\/span>\n<\/span><\/span><span class='shcb-loc'><span>\t<span class=\"hljs-keyword\">var<\/span> placeholderText: <span class=\"hljs-type\">String<\/span>\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-keyword\">var<\/span> body: some <span class=\"hljs-type\">View<\/span> {\n<\/span><\/span><span class='shcb-loc'><span>\t\t<span class=\"hljs-type\">LabeledContent<\/span> {\n<\/span><\/span><span class='shcb-loc'><span>\t\t\t<span class=\"hljs-type\">TextField<\/span>(<span class=\"hljs-string\">\"\"<\/span>, text: $text)\n<\/span><\/span><span class='shcb-loc'><span>\t\t\t\t.textFieldStyle(.roundedBorder)\n<\/span><\/span><span class='shcb-loc'><span>\t\t\t\t.frame(width: <span class=\"hljs-number\">200<\/span>)\n<\/span><\/span><span class='shcb-loc'><span>\t\t} label: {\n<\/span><\/span><span class='shcb-loc'><span>\t\t\t<span class=\"hljs-type\">Text<\/span>(placeholderText)\n<\/span><\/span><span class='shcb-loc'><span>\t\t}\n<\/span><\/span><span class='shcb-loc'><span>\t\t.labeledContentStyle(<span class=\"hljs-type\">TopLabeledStyleConfig<\/span>())\n<\/span><\/span><span class='shcb-loc'><span>    }\n<\/span><\/span><span class='shcb-loc'><span>}\n<\/span><\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-5\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">Swift<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">swift<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h2 class=\"wp-block-heading\">Conclusion<\/h2>\n\n\n\n<p>I hope you learned something new today. If there is one thing you should take away: Always look around for style configurations on any SwiftUI elements. This is often a better way to style things in contrast to building together more or less generic <code>View<\/code>s.<\/p>\n\n\n\n<p>E.g. when you want to add custom styled <code>Toggle<\/code> elements, this is also the way to go. No need for basic <code>View<\/code> and <code>onTapGesture<\/code>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Today, I wanted to create a labeled TextField in SwiftUI. More specifically the label should appear as a small text above the TextField. My first quick hack was to just add a label above a TextField. Obviously, we can do better. Let me show you how. LabeledContent As a major thing in SwiftUI is its&hellip;<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"templates\/template-full-width.php","format":"standard","meta":{"inline_featured_image":false,"footnotes":""},"categories":[3,82],"tags":[4,35,61],"class_list":["post-938","post","type-post","status-publish","format-standard","hentry","category-ios-development","category-swiftui","tag-ios","tag-swift","tag-swiftui"],"_links":{"self":[{"href":"https:\/\/mic.st\/blog\/wp-json\/wp\/v2\/posts\/938","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/mic.st\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/mic.st\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/mic.st\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/mic.st\/blog\/wp-json\/wp\/v2\/comments?post=938"}],"version-history":[{"count":5,"href":"https:\/\/mic.st\/blog\/wp-json\/wp\/v2\/posts\/938\/revisions"}],"predecessor-version":[{"id":1025,"href":"https:\/\/mic.st\/blog\/wp-json\/wp\/v2\/posts\/938\/revisions\/1025"}],"wp:attachment":[{"href":"https:\/\/mic.st\/blog\/wp-json\/wp\/v2\/media?parent=938"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/mic.st\/blog\/wp-json\/wp\/v2\/categories?post=938"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/mic.st\/blog\/wp-json\/wp\/v2\/tags?post=938"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}