{"id":4059,"date":"2026-01-17T12:44:01","date_gmt":"2026-01-17T03:44:01","guid":{"rendered":"https:\/\/www.freelifemakers.org\/wordpress\/?p=4059"},"modified":"2026-01-18T13:10:37","modified_gmt":"2026-01-18T04:10:37","slug":"swift-randomuser-api-detail-screen","status":"publish","type":"post","link":"https:\/\/www.freelifemakers.org\/wordpress\/index.php\/2026\/01\/17\/swift-randomuser-api-detail-screen\/","title":{"rendered":"[Swift] RandomUser API Detail Screen"},"content":{"rendered":"\n<figure class=\"wp-block-embed is-type-wp-embed is-provider-freelifemakers-org wp-block-embed-freelifemakers-org\"><div class=\"wp-block-embed__wrapper\">\n<blockquote class=\"wp-embedded-content\" data-secret=\"j1yCVo51jA\"><a href=\"https:\/\/www.freelifemakers.org\/wordpress\/index.php\/2026\/01\/12\/swift-horizontalinfinite-scroll-component\/\">[swift]\uac00\ub85c\ubb34\ud55c\uc2a4\ud06c\ub864\ucef4\ud3ec\ub10c\ud2b8\/Horizontal Infinite Scroll Component<\/a><\/blockquote><iframe loading=\"lazy\" class=\"wp-embedded-content\" sandbox=\"allow-scripts\" security=\"restricted\" style=\"position: absolute; visibility: hidden;\" title=\"&#8220;[swift]\uac00\ub85c\ubb34\ud55c\uc2a4\ud06c\ub864\ucef4\ud3ec\ub10c\ud2b8\/Horizontal Infinite Scroll Component&#8221; &#8212; freelifemakers.org\" src=\"https:\/\/www.freelifemakers.org\/wordpress\/index.php\/2026\/01\/12\/swift-horizontalinfinite-scroll-component\/embed\/#?secret=gYl2FpejyM#?secret=j1yCVo51jA\" data-secret=\"j1yCVo51jA\" width=\"560\" height=\"315\" frameborder=\"0\" marginwidth=\"0\" marginheight=\"0\" scrolling=\"no\"><\/iframe>\n<\/div><\/figure>\n\n\n\n<p>\ud83d\udc49\ud83c\udffb \uac00\ub85c \ubb34\ud55c \uc2a4\ud06c\ub864 \ucef4\ud3ec\ub10c\ud2b8\uc5d0 \uc0c1\uc138\ud654\uba74 \ucd94\uac00\ud55c \ub0b4\uc6a9\uc785\ub2c8\ub2e4.<br>This is the content of adding a detail screen to the horizontal infinite scroll component.<\/p>\n\n\n\n<p>\ud83d\udc49\ud83c\udffb \uc774\uc804 \ud3ec\uc2a4\ud2b8\uc5d0\uc11c \uc544\ub798\uc758 \ucf54\ub4dc\ub97c \uc218\uc815\ud558\uc2dc\uba74\ub429\ub2c8\ub2e4.<br>You can modify the code below from the previous post.<\/p>\n\n\n\n<p>\ud83d\udc49\ud83c\udffbUserDetailView\ucf54\ub4dc\ub294 \uc0c8\ub85c \ucd94\uac00\ud569\ub2c8\ub2e4.<br>Add new UserDetailView code.<\/p>\n\n\n\n<p>\ud83d\udc49\ud83c\udffb\ucf54\ub4dc \/ Code<\/p>\n\n\n\n<p>\u2714\ufe0f UserDetailView<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>import SwiftUI\n\nstruct UserDetailView: View {\n    let user: RandomUser  \/\/ RandomUser \ubaa8\ub378\uc774\ub77c\uace0 \uac00\uc815\n    \n    var body: some View {\n        ScrollView {\n            VStack(spacing: 24) {\n                \/\/ \ud070 \ud504\ub85c\ud544 \uc0ac\uc9c4 \/ profile picture\n                AsyncImage(url: URL(string: user.picture.large)) { phase in\n                    switch phase {\n                    case .success(let image):\n                        image\n                            .resizable()\n                            .scaledToFit()\n                            .frame(width: 180, height: 180)\n                            .clipShape(Circle())\n                            .shadow(radius: 8)\n                    case .empty, .failure:\n                        ProgressView()\n                            .frame(width: 180, height: 180)\n                    @unknown default:\n                        EmptyView()\n                    }\n                }\n                \n                \/\/ \uc774\ub984,?? -&gt;nil\uc774\uba74 \uc624\ub978\ucabd \uac12\uc744 \uc0ac\uc6a9\n                \/\/ name, ?? -&gt; if nil, use the right value\n                Text(String(\"\\(user.name.title ?? \"\") \\(user.name.first) \\(user.name.last)\"))\n                    .font(.title)\n                    .fontWeight(.bold)\n                \n                \/\/ \uae30\ubcf8 \uc815\ubcf4 \/ basic information\n                VStack(alignment: .leading, spacing: 12) {\n                    DetailRow(icon: \"person.fill\", title: \"\uc131\ubcc4\", value: user.gender.capitalized)\n                    DetailRow(icon: \"calendar\", title: \"\ub098\uc774\", value: \"\\(user.dob.age)\uc138\")\n                    DetailRow(icon: \"envelope.fill\", title: \"\uc774\uba54\uc77c\", value: user.email)\n                    DetailRow(icon: \"phone.fill\", title: \"\uc804\ud654\ubc88\ud638\", value: user.phone)\n                    DetailRow(icon: \"mobilephone\", title: \"\ud734\ub300\ud3f0\", value: user.cell)\n                }\n                .padding(.horizontal)\n                \n                \/\/ \uc8fc\uc18c \/ address\n                VStack(alignment: .leading, spacing: 8) {\n                    Text(\"\uc8fc\uc18c\")\n                        .font(.title3)\n                        .fontWeight(.semibold)\n                    \n                        \/\/ street \uc788\ub294 \uacbd\uc6b0\ub9cc \ud45c\uc2dc (\uc635\uc154\ub110 \ucc98\ub9ac)\n                        \/\/ Display only when there is a street (optional handling)\n                        if let street = user.location?.street {\n                            Text(\"\\(street.number) \\(street.name)\")\n                        } else {\n                            Text(\"\uac70\ub9ac \uc815\ubcf4 \uc5c6\uc74c\")\n                        }\n                        \n                        Text(\"\\(user.location?.city ?? \"\"), \\(user.location?.state ?? \"\")\")\n                            .foregroundStyle(.secondary)\n                        \n                        Text(user.location?.country ?? \"\uad6d\uac00 \uc815\ubcf4 \uc5c6\uc74c\")\n                            .foregroundStyle(.secondary)\n                    \n                    \/\/Text(\"\\(user.location.country) \\(user.location.postcode)\")\n                }\n                .padding()\n                .frame(maxWidth: .infinity, alignment: .leading)\n                .background(Color(.systemGray6))\n                .clipShape(RoundedRectangle(cornerRadius: 12))\n                .padding(.horizontal)\n            }\n            .padding(.vertical, 32)\n        }\n        .navigationTitle(\"\\(user.name.first) \\(user.name.last)\")\n        .navigationBarTitleDisplayMode(.inline)\n    }\n}\n\n\/\/ basic information row\nstruct DetailRow: View {\n    let icon: String\n    let title: String\n    let value: String\n    \n    var body: some View {\n        HStack {\n            Image(systemName: icon)\n                .frame(width: 24)\n                .foregroundStyle(.blue)\n            \n            Text(title)\n                .foregroundStyle(.secondary)\n            \n            Spacer()\n            \n            Text(value)\n        }\n    }\n}\n<\/code><\/pre>\n\n\n\n<p>\u2714\ufe0fRandomUserModel<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>struct RandomUser: Codable, Identifiable, Equatable, Hashable { \/\/ hashaable\ucd94\uac00 \/ add hashable\n \n    let login: Login\n    let name: Name\n    let picture: Picture\n    let gender: String\n    \n    \/\/ \ucd94\uac00 \/ add\n    let dob: Dob\n    let email: String\n    let phone: String\n    let cell: String\n    let location: Location?\n    \n    \/\/ Identifiable\n    var id: String { login.uuid }\n    \n    \/\/ MARK: - \ub0b4\ubd80 \uad6c\uc870\uccb4\n    struct Login: Codable, Equatable, Hashable {\n        let uuid: String\n        \/\/ \ud544\uc694\ud558\uba74 username, password \ub4f1 \ucd94\uac00 \uac00\ub2a5\n        \/\/ You can add username, password, etc. if needed.\n    }\n    \n    struct Name: Codable, Equatable, Hashable{\n        let title: String?   \/\/ \"Mr\", \"Mrs\" ...\n        let first: String\n        let last: String\n    }\n    \n    struct Picture: Codable, Equatable, Hashable{\n        let large: String\n        let medium: String?\n        let thumbnail: String?\n    }\n    \/\/ dob \uad6c\uc870\uccb4 \ucd94\uac00 (\uac00\uc7a5 \ud754\ud55c \ud615\ud0dc) \/ add Dob struct\n    struct Dob: Codable, Equatable, Hashable {\n        let date: String     \/\/ \uc608: \"1990-05-14T...\" (ISO 8601 \ud615\uc2dd)\n        let age: Int\n    }\n    \/\/ add location\n    struct Location: Codable, Hashable {\n            let street: Street\n            let city: String\n            let state: String\n            let country: String\n\/\/            let postcode: StringOrInt \/\/ String \ub610\ub294 Int \uc62c \uc218 \uc788\uc74c\n\/\/            let coordinates: Coordinates\n\/\/            let timezone: Timezone\n            \n            struct Street: Codable, Hashable {\n                let number: Int\n                let name: String\n            }\n            \/\/ ... \ub098\uba38\uc9c0\n        }\n}<\/code><\/pre>\n\n\n\n<p>\u2714\ufe0fHorizontalUserScrollView<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>import SwiftUI\n\n\/\/ \uac00\ub85c \ubb34\ud55c \uc2a4\ud06c\ub864 \ucef4\ud3ec\ub10c\ud2b8\n\/\/ Horizontal infinite scroll component\nstruct HorizontalUserScrollView: View {\n    let title: String\n    let url: String\n    @StateObject var viewModel: RandomUserViewModel\n    \n    var body: some View {\n        VStack(alignment: .leading) {\n            \/\/ \uc139\uc158 \uc81c\ubaa9 \/ section title\n            Text(title)\n                .font(.title2)\n                .fontWeight(.bold)\n                .padding(.horizontal)\n            \n            ScrollView(.horizontal, showsIndicators: false) {\n                LazyHStack(spacing: 20) {\n                    ForEach(viewModel.users) { user in\n                        NavigationLink(value: user) {\n                        VStack {\n                            Text(\"\\(user.name.first) \\(user.name.last)\")\n                                .font(.headline)\n                                .multilineTextAlignment(.center)\n                            \n                            AsyncImage(url: URL(string: user.picture.large)) { phase in\n                                switch phase {\n                                case .success(let image):\n                                    image\n                                        .resizable()\n                                        .scaledToFit()\n                                        .frame(width: 120, height: 120)\n                                        .clipShape(Circle())\n                                case .empty, .failure:\n                                    ProgressView()\n                                        .frame(width: 120, height: 120)\n                                @unknown default:\n                                    EmptyView()\n                                }\n                            }\n                        }\n                        .frame(width: 160)\n                        .padding()\n                        .background(Color(.secondarySystemBackground))\n                        .cornerRadius(16)\n                        .shadow(radius: 3)\n                    }\n                        \n                        \/\/ \ub9c8\uc9c0\ub9c9 \uc0ac\uc6a9\uc790 \uce74\ub4dc\uac00 \ub098\ud0c0\ub0a0 \ub54c \ucd94\uac00 \ub85c\ub4dc\n                        \/\/ Load additional cards when the last user card appears\n                        \/\/ NavigationLink \ubc14\uae65\n                        .onAppear {\n                            if user == viewModel.users.last {\n                                viewModel.fetchMoreUsers(url:url)  \/\/ \ucd94\uac00 \ub85c\ub4dc (append)\n                                \/\/ viewModel.fetchMoreUsers(url: \"https:\/\/randomuser.me\/api\/?results=20\")  \/\/ \ucd94\uac00 \ub85c\ub4dc (append)\n                            }\n                        }\n                    }\n                    \n                    \/\/ \ub85c\ub529 \uc778\ub514\ucf00\uc774\ud130 \/ loading indicator\n                    if viewModel.isLoadingMore {\n                        ProgressView(\"\ubd88\ub7ec\uc624\ub294 \uc911\u2026\")\n                            .frame(width: 160, height: 240)\n                            .padding()\n                    }\n                }\n                .padding(.horizontal)\n            }\n        }\n        \/\/ \uc139\uc158\uc774 \ud654\uba74\uc5d0 \ub098\ud0c0\ub0a0 \ub54c \ucd08\uae30 \ub370\uc774\ud130 \ub85c\ub4dc (\ud55c \ubc88\ub9cc!)\n        \/\/ Load initial data when the section appears on screen (only once!)\n        .onAppear {\n            if viewModel.users.isEmpty {\n                Task {\n                    \/\/ \uc804\uccb4 \uad50\uccb4 (\ucd08\uae30 \ub85c\ub4dc \uc804\uc6a9)\n                    \/\/ Full replacement (initial load only)\n                    await viewModel.loadInitialUsers(url:url)\n                    \/\/ await viewModel.loadInitialUsers(url:\"https:\/\/randomuser.me\/api\/?results=20\")  \/\/ \uc804\uccb4 \uad50\uccb4 (\ucd08\uae30 \ub85c\ub4dc \uc804\uc6a9)\n                }\n            }\n        }\n        \/\/ \uc2a4\ud0dd\uc774\ub3d9 UserDetailView\n        .navigationDestination(for: RandomUser.self) { user in\n                    UserDetailView(user: user)\n        }\n    }\n}<\/code><\/pre>\n\n\n\n<p>\ud83d\udc49\ud83c\udffb\uc2a4\ud06c\ub9b0 \uc0f7 \/ ScreenShot<\/p>\n\n\n\n<figure class=\"wp-block-gallery has-nested-images columns-default is-cropped wp-block-gallery-1 is-layout-flex wp-block-gallery-is-layout-flex\">\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"474\" height=\"1024\" data-id=\"4069\" src=\"https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/01\/detailView-474x1024.png\" alt=\"\" class=\"wp-image-4069\" srcset=\"https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/01\/detailView-474x1024.png 474w, https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/01\/detailView-139x300.png 139w, https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/01\/detailView-400x864.png 400w, https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/01\/detailView.png 676w\" sizes=\"auto, (max-width: 474px) 100vw, 474px\" \/><\/figure>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"483\" height=\"1024\" data-id=\"4070\" src=\"https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/01\/AppMain-483x1024.png\" alt=\"\" class=\"wp-image-4070\" srcset=\"https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/01\/AppMain-483x1024.png 483w, https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/01\/AppMain-142x300.png 142w, https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/01\/AppMain-400x848.png 400w, https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/01\/AppMain.png 688w\" sizes=\"auto, (max-width: 483px) 100vw, 483px\" \/><\/figure>\n<\/figure>\n","protected":false},"excerpt":{"rendered":"<p>\ud83d\udc49\ud83c\udffb \uac00\ub85c \ubb34\ud55c \uc2a4\ud06c\ub864 \ucef4\ud3ec\ub10c\ud2b8\uc5d0 \uc0c1\uc138\ud654\uba74 \ucd94\uac00\ud55c \ub0b4\uc6a9\uc785\ub2c8\ub2e4.This is the content of adding a detail screen to the horizontal infinite scroll component. \ud83d\udc49\ud83c\udffb \uc774\uc804 \ud3ec\uc2a4\ud2b8\uc5d0\uc11c \uc544\ub798\uc758 \ucf54\ub4dc\ub97c \uc218\uc815\ud558\uc2dc\uba74\ub429\ub2c8\ub2e4.You can modify the code below from the previous post. \ud83d\udc49\ud83c\udffbUserDetailView\ucf54\ub4dc\ub294 \uc0c8\ub85c \ucd94\uac00\ud569\ub2c8\ub2e4.Add new UserDetailView code. \ud83d\udc49\ud83c\udffb\ucf54\ub4dc \/ Code \u2714\ufe0f UserDetailView \u2714\ufe0fRandomUserModel \u2714\ufe0fHorizontalUserScrollView \ud83d\udc49\ud83c\udffb\uc2a4\ud06c\ub9b0 \uc0f7 \/ ScreenShot<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[19,1],"tags":[],"class_list":["post-4059","post","type-post","status-publish","format-standard","hentry","category-swift","category-uncategorized","missing-thumbnail"],"_links":{"self":[{"href":"https:\/\/www.freelifemakers.org\/wordpress\/index.php\/wp-json\/wp\/v2\/posts\/4059","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.freelifemakers.org\/wordpress\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.freelifemakers.org\/wordpress\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.freelifemakers.org\/wordpress\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.freelifemakers.org\/wordpress\/index.php\/wp-json\/wp\/v2\/comments?post=4059"}],"version-history":[{"count":11,"href":"https:\/\/www.freelifemakers.org\/wordpress\/index.php\/wp-json\/wp\/v2\/posts\/4059\/revisions"}],"predecessor-version":[{"id":4074,"href":"https:\/\/www.freelifemakers.org\/wordpress\/index.php\/wp-json\/wp\/v2\/posts\/4059\/revisions\/4074"}],"wp:attachment":[{"href":"https:\/\/www.freelifemakers.org\/wordpress\/index.php\/wp-json\/wp\/v2\/media?parent=4059"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.freelifemakers.org\/wordpress\/index.php\/wp-json\/wp\/v2\/categories?post=4059"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.freelifemakers.org\/wordpress\/index.php\/wp-json\/wp\/v2\/tags?post=4059"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}