Skip to content

Commit

Permalink
Merge pull request #23 from yumemi-inc/feature/uikit
Browse files Browse the repository at this point in the history
  • Loading branch information
novr authored Apr 20, 2022
2 parents bd1b1b5 + 3b340d5 commit a2fe1a8
Show file tree
Hide file tree
Showing 7 changed files with 332 additions and 47 deletions.
17 changes: 17 additions & 0 deletions Documentation/UITableView.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# UITableView

[UITableView](https://developer.apple.com/documentation/uikit/uitableview)を使ってみましょう

## 課題

- 呼び出しAPIの`List ver`を使用する

- 天気リスト画面を作成する

- 1行には天気アイコン、都市名、最低・最高気温を表示する
- 行選択で、詳細画面へ遷移する

### エクストラ

- [pull to refresh](https://developer.apple.com/design/human-interface-guidelines/ios/controls/refresh-content-controls/
)機能を追加する
31 changes: 16 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,21 +38,22 @@ Session1がレビュー待ちの場合...
1. Session2を進める
1. `session/1`のマージ後、`session/2``main`でrebaseする


# 課題
- [Session1](Documentation/AutoLayout.md)
- [Session2](Documentation/API.md)
- [Session3](Documentation/Error.md)
- [Session4](Documentation/Json.md)
- [Session5](Documentation/Codable.md)
- [Session6](Documentation/VC_Lifecycle.md)
- [Session7](Documentation/NotificationCenter.md)
- [Session8](Documentation/UnitTest.md)
- [Session9](Documentation/ThreadBlock.md)
- [Session10](Documentation/Delegate.md)
- [Session11](Documentation/Closure.md)
- [Session12](Documentation/Concurrency.md)
- [Session13](Documentation/BugFix.md)
# Session
1. [AutoLayout](Documentation/AutoLayout.md)
1. [API](Documentation/API.md)
1. [Error](Documentation/Error.md)
1. [Json](Documentation/Json.md)
1. [Codable](Documentation/Codable.md)
1. [Lifecycle](Documentation/VC_Lifecycle.md)
1. [NotificationCenter](Documentation/NotificationCenter.md)
1. [UnitTest](Documentation/UnitTest.md)
1. [ThreadBlock](Documentation/ThreadBlock.md)
1. [Delegate](Documentation/Delegate.md)
1. [Closure](Documentation/Closure.md)
1. [Concurrency](Documentation/Concurrency.md)
1. UIKit
1. [UITableView](Documentation/UITableView.md)
1. [BugFix](Documentation/BugFix.md)

**(注1)**
このようなケースで `rebase` コマンドを使うことが必ずしも正しいとは限りません。
Expand Down
19 changes: 19 additions & 0 deletions Sources/YumemiWeather/SeedRandomNumberGenerator.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
//
// SeedRandomNumberGenerator.swift
//
//
// Created by 古宮 伸久 on 2022/04/08.
//

import Foundation

struct SeedRandomNumberGenerator: RandomNumberGenerator {
init(seed: Int) {
// Set the random seed
srand48(seed)
}

func next() -> UInt64 {
UInt64(drand48() * Double(UInt64.max))
}
}
52 changes: 29 additions & 23 deletions Sources/YumemiWeather/YumemiWeather.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ struct Request: Decodable {
let date: Date
}

struct Response: Codable {
struct Response: Codable, Equatable {
let weather: String
let maxTemp: Int
let minTemp: Int
Expand All @@ -24,56 +24,62 @@ public enum YumemiWeatherError: Swift.Error {
}

final public class YumemiWeather {

static let apiDuration: TimeInterval = 2

private static let dateFormatter: DateFormatter = {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZZZZZ"
return dateFormatter
}()
private static let decoder: JSONDecoder = {

static let decoder: JSONDecoder = {
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .formatted(dateFormatter)
return decoder
}()
private static let encoder: JSONEncoder = {

static let encoder: JSONEncoder = {
let encoder = JSONEncoder()
encoder.keyEncodingStrategy = .convertToSnakeCase
encoder.dateEncodingStrategy = .formatted(dateFormatter)
return encoder
}()



/// 引数の値でResponse構造体を作成する。引数がnilの場合はランダムに値を作成する。
/// - Parameters:
/// - weather: 天気を表すenum
/// - maxTemp: 最高気温
/// - minTemp: 最低気温
/// - date: 日付
/// - seed: シード値
/// - Returns: Response構造体
private static func makeRandomResponse(weather: Weather? = nil, maxTemp: Int? = nil, minTemp: Int? = nil, date: Date? = nil) -> Response {
let weather = weather ?? Weather.allCases.randomElement()!
let maxTemp = maxTemp ?? Int.random(in: 10...40)
let minTemp = minTemp ?? Int.random(in: -40..<maxTemp)

static func makeRandomResponse(weather: Weather? = nil, maxTemp: Int? = nil, minTemp: Int? = nil, date: Date? = nil, seed: Int? = nil) -> Response {
return makeRandomResponse(weather: weather, maxTemp: maxTemp, minTemp: minTemp, date: date, seed: seed ?? Int.random(in: Int.min...Int.max))
}

private static func makeRandomResponse(weather: Weather?, maxTemp: Int?, minTemp: Int?, date: Date?, seed seedValue: Int) -> Response {
var seed = SeedRandomNumberGenerator(seed: seedValue)
let weather = weather ?? Weather.allCases.randomElement(using: &seed)!
let maxTemp = maxTemp ?? Int.random(in: 10...40, using: &seed)
let minTemp = minTemp ?? Int.random(in: -40..<maxTemp, using: &seed)
let date = date ?? Date()

return Response(
weather: weather.rawValue,
maxTemp: maxTemp,
minTemp: minTemp,
date: date
)
}

/// 擬似 天気予報API Simple ver
/// - Returns: 天気を表す文字列 "sunny" or "cloudy" or "rainy"
public static func fetchWeather() -> String {
return self.makeRandomResponse().weather
}

/// 擬似 天気予報API Throws ver
/// - Parameters:
/// - area: 天気予報を取得する対象地域 example: "tokyo"
Expand All @@ -83,10 +89,10 @@ final public class YumemiWeather {
if Int.random(in: 0...4) == 4 {
throw YumemiWeatherError.unknownError
}

return self.makeRandomResponse().weather
}

/// 擬似 天気予報API Json ver
/// - Parameter jsonString: 地域と日付を含むJson文字列
/// example:
Expand All @@ -102,17 +108,17 @@ final public class YumemiWeather {
let request = try? decoder.decode(Request.self, from: requestData) else {
throw YumemiWeatherError.invalidParameterError
}

let response = makeRandomResponse(date: request.date)
let responseData = try encoder.encode(response)

if Int.random(in: 0...4) == 4 {
throw YumemiWeatherError.unknownError
}

return String(data: responseData, encoding: .utf8)!
}

/// 擬似 天気予報API Sync ver
/// - Parameter jsonString: 地域と日付を含むJson文字列
/// example:
Expand Down Expand Up @@ -150,7 +156,7 @@ final public class YumemiWeather {
}
}
}

/// 擬似 天気予報API Async ver
/// - Parameter jsonString: 地域と日付を含むJson文字列
/// example:
Expand Down
124 changes: 124 additions & 0 deletions Sources/YumemiWeather/YumemiWeatherList.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
//
// YumemiWeatherList.swift
//
//
// Created by 古宮 伸久 on 2022/04/04.
//

import Foundation

struct AreaRequest: Decodable {
let areas: [String]
let date: Date
}

struct AreaResponse: Codable {
let area: Area
let info: Response
}

public enum Area: String, CaseIterable, Codable {
case Sapporo
case Sendai
case Niigata
case Kanazawa
case Tokyo
case Nagoya
case Osaka
case Hiroshima
case Kochi
case Fukuoka
case Kagoshima
case Naha
}

public extension YumemiWeather {

/// 擬似 天気予報一覧API Json ver
/// - Parameter jsonString: 地域と日付を含むJson文字列
/// example:
/// {
/// "areas": ["Tokyo"],
/// "date": "2020-04-01T12:00:00+09:00"
/// }
/// - Throws: YumemiWeatherError パラメータが正常でもランダムにエラーが発生する
/// - Returns: Json文字列
/// example: [{area: Tokyo, info: {"max_temp":25,"date":"2020-04-01T12:00:00+09:00","min_temp":7,"weather":"cloudy"}}]
static func fetchWeatherList(_ jsonString: String) throws -> String {
guard let requestData = jsonString.data(using: .utf8),
let request = try? decoder.decode(AreaRequest.self, from: requestData) else {
throw YumemiWeatherError.invalidParameterError
}

if Int.random(in: 0...4) == 4 {
throw YumemiWeatherError.unknownError
}

let areas = request.areas.isEmpty ? Area.allCases : request.areas.compactMap { Area(rawValue: $0) }
let response = areas.map { area -> AreaResponse in
var hasher = Hasher()
hasher.combine(area)
hasher.combine(request.date)
return AreaResponse(area: area, info: makeRandomResponse(date: request.date, seed: hasher.finalize()))
}
let responseData = try encoder.encode(response)

return String(data: responseData, encoding: .utf8)!
}

/// 擬似 天気予報一覧API Sync ver
/// - Parameter jsonString: 地域と日付を含むJson文字列
/// example:
/// {
/// "areas": ["Tokyo"],
/// "date": "2020-04-01T12:00:00+09:00"
/// }
/// - Throws: YumemiWeatherError パラメータが正常でもランダムにエラーが発生する
/// - Returns: Json文字列
static func syncFetchWeatherList(_ jsonString: String) throws -> String {
Thread.sleep(forTimeInterval: apiDuration)
return try self.fetchWeatherList(jsonString)
}

/// 擬似 天気予報一覧API Callback ver
/// - Parameters:
/// - jsonString: 地域と日付を含むJson文字列
/// example:
/// {
/// "areas": ["Tokyo"],
/// "date": "2020-04-01T12:00:00+09:00"
/// }
/// - completion: 完了コールバック
static func callbackFetchWeatherList(_ jsonString: String, completion: @escaping (Result<String, YumemiWeatherError>) -> Void) {
DispatchQueue.global().asyncAfter(deadline: .now() + apiDuration) {
do {
let response = try fetchWeatherList(jsonString)
completion(Result.success(response))
}
catch let error where error is YumemiWeatherError {
completion(Result.failure(error as! YumemiWeatherError))
}
catch {
fatalError()
}
}
}

/// 擬似 天気予報一覧API Async ver
/// - Parameter jsonString: 地域と日付を含むJson文字列
/// example:
/// {
/// "areas": ["Tokyo"],
/// "date": "2020-04-01T12:00:00+09:00"
/// }
/// - Throws: YumemiWeatherError パラメータが正常でもランダムにエラーが発生する
/// - Returns: Json文字列
@available(iOS 13, macOS 10.15, *)
static func asyncFetchWeatherList(_ jsonString: String) async throws -> String {
return try await withCheckedThrowingContinuation { continuation in
callbackFetchWeatherList(jsonString) { result in
continuation.resume(with: result)
}
}
}
}
Loading

0 comments on commit a2fe1a8

Please sign in to comment.