Last active
February 12, 2026 06:24
-
-
Save codewithsanthoshofficial/fe0cb9699b92afe89a00c812086ee2d9 to your computer and use it in GitHub Desktop.
SwiftUI_SampleAPICall_List_stackoverflow
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import Foundation | |
| struct APIClient { | |
| func fetchQuestions() async throws -> [QuestionsItemData] { | |
| var components = URLComponents(string: "https://api.stackexchange.com/2.3/questions")! | |
| components.queryItems = [ | |
| .init(name: "order", value: "desc"), | |
| .init(name: "sort", value: "votes"), | |
| .init(name: "site", value: "stackoverflow") | |
| ] | |
| let (data, response) = try await URLSession.shared.data(from: components.url!) | |
| guard let httpResponse = response as? HTTPURLResponse, | |
| 200...299 ~= httpResponse.statusCode else { | |
| throw NetworkError.serverError | |
| } | |
| return try JSONDecoder().decode(QuestionResponse.self, from: data).items | |
| } | |
| } | |
| =============================== | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import Foundation | |
| extension DateFormatter { | |
| static let cached: DateFormatter = { | |
| let f = DateFormatter() | |
| f.locale = Locale(identifier: "en_US") | |
| f.dateStyle = .medium | |
| return f | |
| }() | |
| } | |
| =========== | |
| import Foundation | |
| extension Int { | |
| func shorted() -> String { | |
| let hundred = 100 | |
| let thousandNum = 1000 | |
| let tenThousandNum = 10000 | |
| let oneLakh = 100000 | |
| let oneMillionNum = 1000000 | |
| let tenMillionNum = 10000000 | |
| if self >= thousandNum && self < tenThousandNum { | |
| return String(format: "%.1fK", Double(self/hundred)/10).replacingOccurrences(of: ".0", with: "") | |
| } | |
| if self >= tenThousandNum && self < oneMillionNum { | |
| return "\(self/thousandNum)k" | |
| } | |
| if self >= oneMillionNum && self < tenMillionNum { | |
| return String(format: "%.1fM", Double(self/oneLakh)/10).replacingOccurrences(of: ".0", with: "") | |
| } | |
| if self >= tenMillionNum { | |
| return "\(self/oneMillionNum)M" | |
| } | |
| return String(self) | |
| } | |
| } | |
| =================== | |
| import Foundation | |
| let formatter: DateFormatter = { | |
| let f = DateFormatter() | |
| f.locale = Locale(identifier: "en_US") | |
| f.dateStyle = .medium | |
| return f | |
| }() | |
| extension Double { | |
| func getDateStringFromUTC() -> String { | |
| let date = Date(timeIntervalSince1970: self) | |
| return formatter.string(from: date) | |
| } | |
| } | |
| ========== | |
| import SwiftUI | |
| struct ColorTheme { | |
| static let appColor = Color(.green) | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import SwiftUI | |
| struct QuestionView: View { | |
| @StateObject private var viewModel = QuestionViewModel() | |
| var body: some View { | |
| NavigationStack { | |
| Group { | |
| if viewModel.isLoading { | |
| ProgressView() | |
| } else { | |
| List(viewModel.questions) { question in | |
| QuestionsListView(questionInfo: question) | |
| } | |
| } | |
| } | |
| .navigationTitle("Questions") | |
| .alert("Error", isPresented: .constant(viewModel.errorMessage != nil)) { | |
| Button("OK", role: .cancel) {} | |
| } message: { | |
| Text(viewModel.errorMessage ?? "") | |
| } | |
| } | |
| } | |
| } | |
| ============================ | |
| import SwiftUI | |
| struct QuestionsListView: View { | |
| let questionInfo: QuestionsItemData | |
| var body: some View { | |
| VStack(alignment: .leading, spacing: 8) { | |
| HStack(alignment: .top) { | |
| AsyncImage(url: questionInfo.owner?.profileImageURL) { image in | |
| image.resizable().clipShape(Circle()) | |
| } placeholder: { | |
| Circle().foregroundStyle(.gray) | |
| } | |
| .frame(width: 60, height: 60) | |
| VStack(alignment: .leading, spacing: 6) { | |
| HStack { | |
| Text(questionInfo.owner?.displayName ?? "Unknown") | |
| .bold() | |
| Spacer() | |
| Text(questionInfo.creationDateValue) | |
| .font(.caption) | |
| } | |
| Text(questionInfo.title ?? "") | |
| .font(.subheadline) | |
| HStack { | |
| Label(questionInfo.viewAnswerCount, systemImage: "text.bubble") | |
| Spacer() | |
| Label(questionInfo.viewScore, systemImage: "chart.bar") | |
| Spacer() | |
| Label(questionInfo.viewCountValue, systemImage: "eye") | |
| } | |
| .font(.caption) | |
| } | |
| } | |
| } | |
| .padding(.vertical, 6) | |
| } | |
| } | |
| //#Preview { | |
| // QuestionsListView(questionInfo: QuestionsItemData) | |
| //} | |
| ============================ | |
| /* | |
| import SwiftUI | |
| struct QuestionsListView: View { | |
| var body: some View { | |
| VStack { | |
| HStack (alignment:.top) { | |
| AsyncImage(url: URL(string: "")) { getImage in | |
| getImage | |
| .resizable() | |
| .scaledToFit() | |
| .clipShape(Circle()) | |
| } placeholder : { | |
| Circle() | |
| .foregroundStyle(ColorTheme.appColor) | |
| } | |
| .frame(width: 70, height: 70) | |
| VStack(alignment: .leading, spacing: 5) { | |
| HStack { | |
| Text("Santhosh") | |
| .fontWeight(.bold) | |
| Text("•") | |
| .fontWeight(.regular) | |
| Text("501K") | |
| .fontWeight(.bold) | |
| Spacer() | |
| Text("01 01 1991") | |
| .fontWeight(.bold) | |
| } | |
| Text("Description") | |
| .fontWeight(.light) | |
| HStack { | |
| Image(systemName: "text.bubble") | |
| Text("11") | |
| Spacer() | |
| Image(systemName: "chart.bar.xaxis") | |
| Text("21K") | |
| Spacer() | |
| Image(systemName: "eye") | |
| Text("19M") | |
| } | |
| .fontWeight(.thin) | |
| .padding(.top) | |
| } | |
| } | |
| Divider() | |
| } | |
| .padding(.leading, 12) | |
| .padding(.trailing, 12) | |
| } | |
| } | |
| #Preview { | |
| QuestionsListView() | |
| } | |
| */ |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import Foundation | |
| final class RequestMethod { | |
| enum Method: String { | |
| case GET | |
| case POST | |
| case PUT | |
| case DELETE | |
| case PATCH | |
| } | |
| static func request(method: Method, url: URL,headers:[String:String]?, body: Data?) -> URLRequest { | |
| var urlRequest = URLRequest(url: url) | |
| urlRequest.httpMethod = method.rawValue | |
| urlRequest.setValue("application/json", forHTTPHeaderField: "Accept") | |
| urlRequest.setValue("application/json", forHTTPHeaderField: "Content-Type") | |
| headers?.forEach { | |
| urlRequest.setValue($0.value, forHTTPHeaderField: $0.key) | |
| } | |
| urlRequest.timeoutInterval = 60 | |
| if let body = body, method != .GET { | |
| urlRequest.httpBody = body | |
| } | |
| return urlRequest | |
| } | |
| } | |
| class NetworkManager { | |
| static let sharedService = NetworkManager() | |
| } | |
| extension NetworkManager { | |
| func proceedForNetworkCall(requestType: RequestMethod.Method = .GET, | |
| url: String) async throws -> Data { | |
| guard let request = URL(string: url) else { throw NetworkError.invalidURL } | |
| let (data, response) = try await URLSession.shared.data(from: request) | |
| guard let response = response as? HTTPURLResponse else { throw NetworkError.conversionFailedToHTTPURLResponse } | |
| try response.statusCodeChecker() | |
| return data | |
| } | |
| } | |
| extension NetworkManager { | |
| func request<T>(url: String, httpMethod: RequestMethod.Method = .GET, | |
| body: Data?, headers: [String: String]?, | |
| expectingReturnType: T.Type) async throws -> T where T: Codable { | |
| guard let url = URL(string: url) else { throw NetworkError.invalidURL } | |
| let urlRequest = RequestMethod.request(method: httpMethod, url: url, headers: [:], body: body) | |
| //request.addHeader(from: headers) | |
| return try await self.responseHeader(URLSession.shared.data(for: urlRequest)) | |
| } | |
| func responseHeader<T: Codable>(_ dataWithRrsponse: (data: Data, response: URLResponse)) async throws -> T { | |
| guard let response = dataWithRrsponse.response as? HTTPURLResponse else { throw NetworkError.conversionFailedToHTTPURLResponse } | |
| try response.statusCodeChecker() | |
| let dataResponse = try JSONDecoder().decode(T.self, from: dataWithRrsponse.data) | |
| // print(dataResponse) | |
| return dataResponse | |
| } | |
| } | |
| ============== | |
| import Foundation | |
| enum APIURL { | |
| static let BASE_URL = "https://api.stackexchange.com/2.3/questions" | |
| case getAllQuestions | |
| case getAnsweredBy | |
| var apiString: String { | |
| switch self { | |
| case .getAllQuestions: | |
| return APIURL.BASE_URL + "?order=*:orderType:*&sort=*:sortType:*&site=stackoverflow" | |
| case .getAnsweredBy: | |
| return "/answers?order=desc&sort=votes&site=stackoverflow" | |
| } | |
| } | |
| } | |
| ======== | |
| import Foundation | |
| enum NetworkError: Error, LocalizedError { | |
| case invalidURL | |
| case serverError | |
| case invalidData | |
| // case unkown(Error) | |
| case conversionFailedToHTTPURLResponse | |
| var errorDescription: String? { | |
| switch self { | |
| case .invalidURL: | |
| return "Malformed URL sent to session" | |
| case .serverError: | |
| return "There was an error with the server. Please try again later" | |
| case .invalidData: | |
| return "The data couldn’t be read because it isn’t in the correct format" | |
| // case .unkown(let error): | |
| // return error.localizedDescription | |
| case .conversionFailedToHTTPURLResponse: | |
| return "Type casting failed" | |
| } | |
| } | |
| } | |
| extension HTTPURLResponse { | |
| func statusCodeChecker() throws { | |
| switch self.statusCode { | |
| case 200...299: | |
| return | |
| case 400: | |
| throw NetworkError.serverError | |
| default: | |
| throw NetworkError.serverError | |
| } | |
| } | |
| } | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import SwiftUI | |
| @main | |
| struct StackOverFlowSampleApp: App { | |
| var body: some Scene { | |
| WindowGroup { | |
| TabBarView() | |
| } | |
| } | |
| } | |
| import SwiftUI | |
| struct TabBarView: View { | |
| var body: some View { | |
| TabView() { | |
| QuestionView() | |
| .tabItem { | |
| Image(systemName:"questionmark.app.dashed") | |
| } | |
| SearchView() | |
| .tabItem { | |
| Image(systemName: "magnifyingglass") | |
| } | |
| UsersView() | |
| .tabItem { | |
| Image(systemName: "person.3") | |
| .environment(\.symbolVariants, .none) | |
| } | |
| } | |
| } | |
| } | |
| #Preview { | |
| TabBarView() | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import Foundation | |
| @MainActor | |
| final class QuestionViewModel: ObservableObject { | |
| @Published private(set) var questions: [QuestionsItemData] = [] | |
| @Published var errorMessage: String? | |
| @Published var isLoading = false | |
| private let apiClient: APIClient | |
| init(apiClient: APIClient = APIClient()) { | |
| self.apiClient = apiClient | |
| fetchQuestions() | |
| } | |
| func fetchQuestions() { | |
| isLoading = true | |
| Task { | |
| do { | |
| questions = try await apiClient.fetchQuestions() | |
| } catch { | |
| errorMessage = error.localizedDescription | |
| } | |
| isLoading = false | |
| } | |
| } | |
| } | |
| ============================ | |
| import Foundation | |
| struct QuestionResponse: Codable { | |
| let items: [QuestionsItemData] | |
| } | |
| struct QuestionsItemData: Codable, Identifiable { | |
| let id: Int | |
| let owner: QuestionOwner? | |
| let viewCount: Int? | |
| let answerCount: Int? | |
| let score: Int? | |
| let creationDate: Int? | |
| let title: String? | |
| enum CodingKeys: String, CodingKey { | |
| case id = "question_id" | |
| case owner | |
| case viewCount = "view_count" | |
| case answerCount = "answer_count" | |
| case score | |
| case creationDate = "creation_date" | |
| case title | |
| } | |
| var viewCountValue: String { viewCount?.shorted() ?? "0" } | |
| var viewAnswerCount: String { answerCount?.shorted() ?? "0" } | |
| var viewScore: String { score?.shorted() ?? "0" } | |
| var creationDateValue: String { | |
| guard let creationDate else { return "-" } | |
| return DateFormatter.cached.string( | |
| from: Date(timeIntervalSince1970: TimeInterval(creationDate)) | |
| ) | |
| } | |
| } | |
| struct QuestionOwner: Codable { | |
| let reputation: Int? | |
| let profileImage: String? | |
| let displayName: String? | |
| enum CodingKeys: String, CodingKey { | |
| case reputation | |
| case profileImage = "profile_image" | |
| case displayName = "display_name" | |
| } | |
| var profileImageURL: URL? { | |
| guard let profileImage else { return nil } | |
| return URL(string: profileImage) | |
| } | |
| var reputationValue: String { | |
| reputation?.shorted() ?? "0" | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment