mirror of
https://github.com/ivanvorobei/SwiftUI.git
synced 2026-05-29 19:00:45 +02:00
Update
This commit is contained in:
Executable
+98
@@ -0,0 +1,98 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"size" : "20x20",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"size" : "20x20",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"size" : "29x29",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"size" : "29x29",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"size" : "40x40",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"size" : "40x40",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"size" : "60x60",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"size" : "60x60",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"size" : "20x20",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"size" : "20x20",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"size" : "29x29",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"size" : "29x29",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"size" : "40x40",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"size" : "40x40",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"size" : "76x76",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"size" : "76x76",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"size" : "83.5x83.5",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "ios-marketing",
|
||||
"size" : "1024x1024",
|
||||
"scale" : "1x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="13122.16" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13104.12"/>
|
||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--View Controller-->
|
||||
<scene sceneID="EHf-IW-A2E">
|
||||
<objects>
|
||||
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
|
||||
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="backgroundColor" xcode11CocoaTouchSystemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
|
||||
<viewLayoutGuide key="safeArea" id="6Tk-OE-BBY"/>
|
||||
</view>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="53" y="375"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
</document>
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>$(PRODUCT_NAME)</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1</string>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>UIApplicationSceneManifest</key>
|
||||
<dict>
|
||||
<key>UIApplicationSupportsMultipleScenes</key>
|
||||
<false/>
|
||||
<key>UISceneConfigurations</key>
|
||||
<dict>
|
||||
<key>UIWindowSceneSessionRoleApplication</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>UILaunchStoryboardName</key>
|
||||
<string>LaunchScreen</string>
|
||||
<key>UISceneConfigurationName</key>
|
||||
<string>Default Configuration</string>
|
||||
<key>UISceneDelegateClassName</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).SceneDelegate</string>
|
||||
</dict>
|
||||
</array>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>UILaunchStoryboardName</key>
|
||||
<string>LaunchScreen</string>
|
||||
<key>UIRequiredDeviceCapabilities</key>
|
||||
<array>
|
||||
<string>armv7</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
Executable
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
//
|
||||
// Action.swift
|
||||
// MovieSwift
|
||||
//
|
||||
// Created by Thomas Ricouard on 06/06/2019.
|
||||
// Copyright © 2019 Thomas Ricouard. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
protocol Action {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
//
|
||||
// MoviesAction.swift
|
||||
// MovieSwift
|
||||
//
|
||||
// Created by Thomas Ricouard on 06/06/2019.
|
||||
// Copyright © 2019 Thomas Ricouard. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
struct MoviesActions {
|
||||
struct FetchPopular: Action {
|
||||
init() {
|
||||
APIService.shared.GET(endpoint: .popular, params: nil) {
|
||||
(result: Result<PaginatedResponse<Movie>, APIService.APIError>) in
|
||||
switch result {
|
||||
case let .success(response):
|
||||
store.dispatch(action: SetPopular(response: response))
|
||||
case .failure(_):
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct SetPopular: Action {
|
||||
let response: PaginatedResponse<Movie>
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
//
|
||||
// Movie.swift
|
||||
// MovieSwift
|
||||
//
|
||||
// Created by Thomas Ricouard on 06/06/2019.
|
||||
// Copyright © 2019 Thomas Ricouard. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
struct Movie: Codable {
|
||||
let id: Int
|
||||
let original_title: String
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
//
|
||||
// PaginatedResponse.swift
|
||||
// MovieSwift
|
||||
//
|
||||
// Created by Thomas Ricouard on 06/06/2019.
|
||||
// Copyright © 2019 Thomas Ricouard. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
struct PaginatedResponse<T: Codable>: Codable {
|
||||
let page: Int
|
||||
let total_results: Int
|
||||
let total_pages: Int
|
||||
let results: [T]
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
//
|
||||
// MoviesStateReducer.swift
|
||||
// MovieSwift
|
||||
//
|
||||
// Created by Thomas Ricouard on 06/06/2019.
|
||||
// Copyright © 2019 Thomas Ricouard. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
struct MoviesStateReducer: Reducer {
|
||||
func reduce(state: MoviesState, action: Action) -> MoviesState {
|
||||
var state = state
|
||||
if let action = action as? MoviesActions.SetPopular {
|
||||
state.popular = action.response.results.map{ $0.id }
|
||||
for (_, value) in action.response.results.enumerated() {
|
||||
state.movies[value.id] = value
|
||||
}
|
||||
}
|
||||
return state
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
//
|
||||
// Reducer.swift
|
||||
// MovieSwift
|
||||
//
|
||||
// Created by Thomas Ricouard on 06/06/2019.
|
||||
// Copyright © 2019 Thomas Ricouard. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
protocol Reducer {
|
||||
associatedtype StateType: FluxState
|
||||
func reduce(state: StateType, action: Action) -> StateType
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
//
|
||||
// AppState.swift
|
||||
// MovieSwift
|
||||
//
|
||||
// Created by Thomas Ricouard on 06/06/2019.
|
||||
// Copyright © 2019 Thomas Ricouard. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
import Combine
|
||||
|
||||
final class AppState: BindableObject {
|
||||
var didChange = PassthroughSubject<AppState, Never>()
|
||||
|
||||
var moviesState: MoviesState
|
||||
|
||||
init(moviesState: MoviesState = MoviesState()) {
|
||||
self.moviesState = moviesState
|
||||
}
|
||||
|
||||
func dispatch(action: Action) {
|
||||
moviesState = MoviesStateReducer().reduce(state: moviesState, action: action)
|
||||
didChange.send(self)
|
||||
}
|
||||
}
|
||||
|
||||
let store = AppState()
|
||||
@@ -0,0 +1,13 @@
|
||||
//
|
||||
// FluxState.swift
|
||||
// MovieSwift
|
||||
//
|
||||
// Created by Thomas Ricouard on 06/06/2019.
|
||||
// Copyright © 2019 Thomas Ricouard. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
protocol FluxState {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
//
|
||||
// MoviesState.swift
|
||||
// MovieSwift
|
||||
//
|
||||
// Created by Thomas Ricouard on 06/06/2019.
|
||||
// Copyright © 2019 Thomas Ricouard. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
struct MoviesState: FluxState {
|
||||
var movies: [Int: Movie] = [:]
|
||||
var popular: [Int] = []
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
//
|
||||
// AppDelegate.swift
|
||||
// MovieSwift
|
||||
//
|
||||
// Created by Thomas Ricouard on 06/06/2019.
|
||||
// Copyright © 2019 Thomas Ricouard. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
@UIApplicationMain
|
||||
class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||
|
||||
|
||||
|
||||
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
|
||||
// Override point for customization after application launch.
|
||||
return true
|
||||
}
|
||||
|
||||
func applicationWillTerminate(_ application: UIApplication) {
|
||||
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
|
||||
}
|
||||
|
||||
// MARK: UISceneSession Lifecycle
|
||||
|
||||
func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
|
||||
// Called when a new scene session is being created.
|
||||
// Use this method to select a configuration to create the new scene with.
|
||||
return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
|
||||
}
|
||||
|
||||
func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set<UISceneSession>) {
|
||||
// Called when the user discards a scene session.
|
||||
// If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions.
|
||||
// Use this method to release any resources that were specific to the discarded scenes, as they will not return.
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
//
|
||||
// SceneDelegate.swift
|
||||
// MovieSwift
|
||||
//
|
||||
// Created by Thomas Ricouard on 06/06/2019.
|
||||
// Copyright © 2019 Thomas Ricouard. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import SwiftUI
|
||||
|
||||
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
|
||||
|
||||
var window: UIWindow?
|
||||
|
||||
|
||||
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
|
||||
// Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.
|
||||
// If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
|
||||
// This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).
|
||||
|
||||
// Use a UIHostingController as window root view controller
|
||||
let window = UIWindow(frame: UIScreen.main.bounds)
|
||||
window.rootViewController = UIHostingController(rootView: ContentView().environmentObject(store))
|
||||
self.window = window
|
||||
window.makeKeyAndVisible()
|
||||
}
|
||||
|
||||
func sceneDidDisconnect(_ scene: UIScene) {
|
||||
// Called as the scene is being released by the system.
|
||||
// This occurs shortly after the scene enters the background, or when its session is discarded.
|
||||
// Release any resources associated with this scene that can be re-created the next time the scene connects.
|
||||
// The scene may re-connect later, as its session was not neccessarily discarded (see `application:didDiscardSceneSessions` instead).
|
||||
}
|
||||
|
||||
func sceneDidBecomeActive(_ scene: UIScene) {
|
||||
// Called when the scene has moved from an inactive state to an active state.
|
||||
// Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive.
|
||||
}
|
||||
|
||||
func sceneWillResignActive(_ scene: UIScene) {
|
||||
// Called when the scene will move from an active state to an inactive state.
|
||||
// This may occur due to temporary interruptions (ex. an incoming phone call).
|
||||
}
|
||||
|
||||
func sceneWillEnterForeground(_ scene: UIScene) {
|
||||
// Called as the scene transitions from the background to the foreground.
|
||||
// Use this method to undo the changes made on entering the background.
|
||||
}
|
||||
|
||||
func sceneDidEnterBackground(_ scene: UIScene) {
|
||||
// Called as the scene transitions from the foreground to the background.
|
||||
// Use this method to save data, release shared resources, and store enough scene-specific state information
|
||||
// to restore the scene back to its current state.
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
//
|
||||
// APIService.swift
|
||||
// MovieSwift
|
||||
//
|
||||
// Created by Thomas Ricouard on 06/06/2019.
|
||||
// Copyright © 2019 Thomas Ricouard. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
struct APIService {
|
||||
let baseURL = URL(string: "https://api.themoviedb.org/3")!
|
||||
let apiKey = "1d9b898a212ea52e283351e521e17871"
|
||||
static let shared = APIService()
|
||||
let decoder = JSONDecoder()
|
||||
|
||||
enum APIError: Error {
|
||||
case noResponse
|
||||
case jsonDecodingError(error: Error)
|
||||
case networkError(error: Error)
|
||||
}
|
||||
|
||||
enum Endpoint {
|
||||
case popular
|
||||
|
||||
func path() -> String {
|
||||
switch self {
|
||||
case .popular:
|
||||
return "movie/popular"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func GET<T: Codable>(endpoint: Endpoint,
|
||||
params: [String: String]?,
|
||||
completionHandler: @escaping (Result<T, APIError>) -> Void) {
|
||||
let queryURL = baseURL.appendingPathComponent(endpoint.path())
|
||||
var components = URLComponents(url: queryURL, resolvingAgainstBaseURL: true)!
|
||||
components.queryItems = [
|
||||
URLQueryItem(name: "api_key", value: apiKey)
|
||||
]
|
||||
if let params = params {
|
||||
for (_, value) in params.enumerated() {
|
||||
components.queryItems?.append(URLQueryItem(name: value.key, value: value.value))
|
||||
}
|
||||
}
|
||||
var request = URLRequest(url: components.url!)
|
||||
request.httpMethod = "GET"
|
||||
let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
|
||||
guard let data = data else {
|
||||
completionHandler(.failure(.noResponse))
|
||||
return
|
||||
}
|
||||
guard error == nil else {
|
||||
completionHandler(.failure(.networkError(error: error!)))
|
||||
return
|
||||
}
|
||||
do {
|
||||
let object = try self.decoder.decode(T.self, from: data)
|
||||
completionHandler(.success(object))
|
||||
} catch let error {
|
||||
completionHandler(.failure(.jsonDecodingError(error: error)))
|
||||
}
|
||||
}
|
||||
task.resume()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
//
|
||||
// ContentView.swift
|
||||
// MovieSwift
|
||||
//
|
||||
// Created by Thomas Ricouard on 06/06/2019.
|
||||
// Copyright © 2019 Thomas Ricouard. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct ContentView : View {
|
||||
@EnvironmentObject var state: AppState
|
||||
|
||||
init() {
|
||||
store.dispatch(action: MoviesActions.FetchPopular())
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
List {
|
||||
ForEach(state.moviesState.popular) { id in
|
||||
Text(self.state.moviesState.movies[id]?.original_title ?? "No title")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
struct ContentView_Previews : PreviewProvider {
|
||||
static var previews: some View {
|
||||
ContentView()
|
||||
}
|
||||
}
|
||||
#endif
|
||||
Reference in New Issue
Block a user