아이폰 검색앱 생성(6)
아이폰 검색앱 생성(6)
엘라스틱 서치에 데이터를 저장했으니 검색 api를 통해서 데이터를 검색할 수 있다 그래서 이제 검색을 할 ios앱을 만들어야한다 앱은 간단히 메인 검색 페이지와 검색결과를 보여주는 리스트 페이지로 구성한다 결과에서 글의 제목을 클릭한다면 해당 공지사항이 있는 웹페이지로 이동한다
1. ContentView.swift 작성
1
2
3
4
5
6
7
8
9
10
11
// 프로젝트의 이름으로 만들어진 swift 파일
import SwiftUI
@main
struct search_app_iosApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
1
2
3
4
5
6
7
8
// ContentView.swift
import SwiftUI
struct ContentView: View {
var body: some View {
MainView()
}
}
기본적으로 생성되는 파일중 내가 만든 프로젝트의 이름으로 만들어진 swift파일이 존재하고 ContentView.swift 파일이 존재한다 기본적으로 작성되는 코드에 따라서 처음 진입은 @main이 있는 파일에서 시작된다 연결된 ContentView로 연결되고 거기서 메인 페이지로 만든 MainView를 연결했다( 바로 써도 상관 없음 )
2. MainView.swift 작성
검색창을 띄울 메인 페이지 일단 간단하게 메인 페이지에 검색창만 만들었다
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
import SwiftUI
struct MainView: View {
@State private var searchQuery: String = "" // 입력한 검색어 저장
@State private var isSearchActive: Bool = false // 입력이 있는지 확인
var body: some View {
NavigationView {
VStack {
TextField("Search...", text: $searchQuery)
.padding()
.textFieldStyle(RoundedBorderTextFieldStyle())
.padding(.horizontal)
Button(action: { // 검색창이 비어있지 않을 때 활성화
if !searchQuery.isEmpty {
isSearchActive = true
}
}) {
Text("Search")
.font(.headline)
.foregroundColor(.white)
.padding()
.background(Color.blue)
.cornerRadius(8)
}
.padding(.top, 20)
NavigationLink( // 검색 버튼을 눌렀을때
destination: SearchResultsView(searchQuery: searchQuery), // 목적지로 이동
isActive: $isSearchActive,
label: {
EmptyView()
}
)
}
.navigationTitle("Main Page")
}
}
}
3. SearchResultsView.swift 작성
앞에서 입력한 검색어를 이용해서 검색 결과를 출력해주는 페이지이다
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
import SwiftUI
// 검색 결과를 받고서 이용할 데이터 형태를 정의함
struct SearchResult: Identifiable {
let id: String
let site: String
let title: String
let url: String
let contentPreview: String? // content의 일부를 저장
}
// api 응답 형태 정의
struct ApiResponse: Decodable {
struct Hits: Decodable {
struct InnerHits: Decodable {
struct Source: Decodable {
let site: String
let title: String
let url: String
let content: String?
}
let _id: String
let _source: Source
}
let hits: [InnerHits]
}
let hits: Hits
}
struct SearchResultsView: View {
var searchQuery: String
@State private var results: [SearchResult] = [] // 검색 경과를 배열로
@State private var isLoading: Bool = false
@State private var errorMessage: String? = nil
var body: some View {
VStack {
if isLoading {
ProgressView("Loading...")
} else if let errorMessage = errorMessage {
Text("Error: \(errorMessage)").foregroundColor(.red)
} else if results.isEmpty {
Text("No results found")
} else {
List(results) { result in
VStack(alignment: .leading) {
Text(result.site)
.font(.headline)
// Title as a clickable link
Link(destination: URL(string: result.url)!) {
Text(result.title)
.font(.subheadline)
.foregroundColor(.blue)
}
if let contentPreview = result.contentPreview {
Text(contentPreview)
.font(.footnote)
.foregroundColor(.gray)
.lineLimit(2) // Only show two lines of content
}
}
}
}
}
.onAppear(perform: fetchSearchResults)
.navigationTitle("Search Results")
}
func fetchSearchResults() {
guard let url = URL(string: "http://elastic:<es_pw>@localhost:9200/notice_index/_search?q=title:\(searchQuery)") else {
errorMessage = "Invalid URL"
return
}
isLoading = true
errorMessage = nil
URLSession.shared.dataTask(with: url) { data, response, error in
DispatchQueue.main.async {
isLoading = false
if let error = error {
errorMessage = "Failed to load data: \(error.localizedDescription)"
return
}
guard let data = data else {
errorMessage = "No data received"
return
}
do {
let apiResponse = try JSONDecoder().decode(ApiResponse.self, from: data)
self.results = apiResponse.hits.hits.map { hit in
// 옵셔널 체이닝과 nil 병합 연산자를 사용해 안전하게 언래핑
let contentPreview = hit._source.content?.trimmingCharacters(in: .whitespacesAndNewlines)
.components(separatedBy: .newlines)
.joined(separator: " ")
.prefix(100)
let previewText = contentPreview.map(String.init) ?? "" // 옵셔널 값 변환 및 처리
return SearchResult(
id: hit._id,
site: hit._source.site,
title: hit._source.title,
url: hit._source.url,
contentPreview: previewText.isEmpty ? nil : previewText
)
}
} catch {
errorMessage = "Failed to parse data: \(error.localizedDescription)"
}
}
}.resume()
}
}
검색어 ‘공사’를 넣었을 때 결과
This post is licensed under CC BY 4.0 by the author.