├── .gitignore
├── BlogApp.xcworkspace
├── contents.xcworkspacedata
└── xcshareddata
│ └── WorkspaceSettings.xcsettings
├── BlogApp
├── BlogApp.xcodeproj
│ ├── project.pbxproj
│ ├── project.xcworkspace
│ │ └── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── xcschemes
│ │ └── BlogApp.xcscheme
├── Resources
│ ├── Info.plist
│ ├── TestsInfo.plist
│ └── db.json
└── Sources
│ ├── AppDelegate.swift
│ ├── Flows
│ ├── MainFlow.swift
│ ├── PushNotificationHandlingFlow.swift
│ ├── PushNotificationHandlingFlowTests.swift
│ ├── UIFlow.swift
│ └── UIFlowTests.swift
│ ├── Launchers
│ ├── ComponentLaunchers.swift
│ ├── PresentationLaunchers.swift
│ ├── PushNotificationsLaunchers.swift
│ ├── ScreenLaunchers.swift
│ └── ServiceFactories.swift
│ └── UtilityServices
│ └── NetworkService.swift
├── Core
├── Core.xcodeproj
│ ├── project.pbxproj
│ └── xcshareddata
│ │ └── xcschemes
│ │ └── Core.xcscheme
├── Resources
│ └── TestsInfo.plist
└── Sources
│ ├── AnyObject+Retain.swift
│ ├── KeyPathBuildable.swift
│ ├── Observable.swift
│ ├── UserDefault+Reset.swift
│ └── Weakify.swift
├── Domain
├── Domain.xcodeproj
│ ├── project.pbxproj
│ └── xcshareddata
│ │ └── xcschemes
│ │ └── Domain.xcscheme
├── Resources
│ └── TestsInfo.plist
└── Sources
│ ├── Entities
│ ├── Comment.swift
│ ├── Post.swift
│ ├── PushNotification.swift
│ ├── Session.swift
│ └── User.swift
│ ├── Protocols
│ └── NetworkServiceProtocol.swift
│ ├── Repositories
│ ├── CommentsRepository.swift
│ ├── PostsRepository.swift
│ ├── UserCommentsRepository.swift
│ └── UserRepository.swift
│ └── Services
│ ├── FakeDomain.swift
│ ├── FakePushNotificationService.swift
│ ├── PushNotificationService.swift
│ └── SessionService.swift
├── Features
├── Features.xcodeproj
│ ├── project.pbxproj
│ └── xcshareddata
│ │ └── xcschemes
│ │ └── Features.xcscheme
├── Resources
│ └── TestsInfo.plist
└── Sources
│ ├── Comments
│ └── CommentsScreenFeature.swift
│ ├── Login
│ ├── LoginScreenFeature.swift
│ ├── LoginScreenFeatureTests.swift
│ └── LoginViewController.swift
│ ├── Logout
│ └── LogoutButtonFeature.swift
│ ├── Posts
│ ├── PostsScreenFeature.swift
│ └── PostsViewController.swift
│ ├── Presentation
│ ├── NavigationBarFeature.swift
│ ├── TabBarFeature.swift
│ ├── WindowFeature.swift
│ └── WindowFrameFeature.swift
│ └── Push notifications
│ ├── PushNotificationButtonFeature.swift
│ └── RestartPushNotificationFeature.swift
├── Podfile
├── Podfile.lock
├── Pods
├── Layoutless
│ ├── LICENSE
│ ├── README.md
│ └── Sources
│ │ ├── Layout
│ │ ├── Anchorable.swift
│ │ ├── ChildNode.swift
│ │ ├── Layout.swift
│ │ ├── LayoutNode.swift
│ │ ├── LayoutProtocol.swift
│ │ ├── Layoutless+UIKit.swift
│ │ ├── Layoutless.swift
│ │ └── Length.swift
│ │ ├── Style
│ │ └── Style.swift
│ │ └── Views
│ │ ├── Button.swift
│ │ ├── CollectionViewCell.swift
│ │ ├── Control.swift
│ │ ├── ImageView.swift
│ │ ├── Label.swift
│ │ ├── TableViewCell.swift
│ │ ├── TextField.swift
│ │ ├── View.swift
│ │ └── ViewController.swift
├── Manifest.lock
├── Pods.xcodeproj
│ └── project.pbxproj
└── Target Support Files
│ ├── Layoutless
│ ├── Info.plist
│ ├── Layoutless-dummy.m
│ ├── Layoutless-prefix.pch
│ ├── Layoutless-umbrella.h
│ ├── Layoutless.modulemap
│ └── Layoutless.xcconfig
│ ├── Pods-BlogApp
│ ├── Info.plist
│ ├── Pods-BlogApp-acknowledgements.markdown
│ ├── Pods-BlogApp-acknowledgements.plist
│ ├── Pods-BlogApp-dummy.m
│ ├── Pods-BlogApp-frameworks.sh
│ ├── Pods-BlogApp-resources.sh
│ ├── Pods-BlogApp-umbrella.h
│ ├── Pods-BlogApp.debug.xcconfig
│ ├── Pods-BlogApp.modulemap
│ └── Pods-BlogApp.release.xcconfig
│ ├── Pods-BlogAppTests
│ ├── Info.plist
│ ├── Pods-BlogAppTests-acknowledgements.markdown
│ ├── Pods-BlogAppTests-acknowledgements.plist
│ ├── Pods-BlogAppTests-dummy.m
│ ├── Pods-BlogAppTests-frameworks.sh
│ ├── Pods-BlogAppTests-resources.sh
│ ├── Pods-BlogAppTests-umbrella.h
│ ├── Pods-BlogAppTests.debug.xcconfig
│ ├── Pods-BlogAppTests.modulemap
│ └── Pods-BlogAppTests.release.xcconfig
│ ├── Pods-Features
│ ├── Info.plist
│ ├── Pods-Features-acknowledgements.markdown
│ ├── Pods-Features-acknowledgements.plist
│ ├── Pods-Features-dummy.m
│ ├── Pods-Features-resources.sh
│ ├── Pods-Features-umbrella.h
│ ├── Pods-Features.debug.xcconfig
│ ├── Pods-Features.modulemap
│ └── Pods-Features.release.xcconfig
│ ├── Pods-FeaturesTests
│ ├── Info.plist
│ ├── Pods-FeaturesTests-acknowledgements.markdown
│ ├── Pods-FeaturesTests-acknowledgements.plist
│ ├── Pods-FeaturesTests-dummy.m
│ ├── Pods-FeaturesTests-frameworks.sh
│ ├── Pods-FeaturesTests-resources.sh
│ ├── Pods-FeaturesTests-umbrella.h
│ ├── Pods-FeaturesTests.debug.xcconfig
│ ├── Pods-FeaturesTests.modulemap
│ └── Pods-FeaturesTests.release.xcconfig
│ ├── Pods-UI
│ ├── Info.plist
│ ├── Pods-UI-acknowledgements.markdown
│ ├── Pods-UI-acknowledgements.plist
│ ├── Pods-UI-dummy.m
│ ├── Pods-UI-resources.sh
│ ├── Pods-UI-umbrella.h
│ ├── Pods-UI.debug.xcconfig
│ ├── Pods-UI.modulemap
│ └── Pods-UI.release.xcconfig
│ ├── Pods-UITests
│ ├── Info.plist
│ ├── Pods-UITests-acknowledgements.markdown
│ ├── Pods-UITests-acknowledgements.plist
│ ├── Pods-UITests-dummy.m
│ ├── Pods-UITests-frameworks.sh
│ ├── Pods-UITests-resources.sh
│ ├── Pods-UITests-umbrella.h
│ ├── Pods-UITests.debug.xcconfig
│ ├── Pods-UITests.modulemap
│ └── Pods-UITests.release.xcconfig
│ └── Pods-UITestsSupport
│ ├── Info.plist
│ ├── Pods-UITestsSupport-acknowledgements.markdown
│ ├── Pods-UITestsSupport-acknowledgements.plist
│ ├── Pods-UITestsSupport-dummy.m
│ ├── Pods-UITestsSupport-resources.sh
│ ├── Pods-UITestsSupport-umbrella.h
│ ├── Pods-UITestsSupport.debug.xcconfig
│ ├── Pods-UITestsSupport.modulemap
│ └── Pods-UITestsSupport.release.xcconfig
├── README.md
├── UI
├── Resources
│ └── TestsInfo.plist
├── Sources
│ ├── Components
│ │ └── StringsTableViewController.swift
│ ├── Presentation
│ │ ├── RootViewController.swift
│ │ ├── UIWindowOwner.swift
│ │ ├── ViewControllerPresenter.swift
│ │ └── ViewPresenter.swift
│ └── UIKitExtensions
│ │ └── UIControl+ClosureCallback.swift
└── UI.xcodeproj
│ ├── project.pbxproj
│ └── xcshareddata
│ └── xcschemes
│ └── UI.xcscheme
├── flow.png
└── modules.png
/BlogApp.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
12 |
13 |
15 |
16 |
18 |
19 |
21 |
22 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/BlogApp.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEWorkspaceSharedSettings_AutocreateContextsIfNeeded
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/BlogApp/BlogApp.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/BlogApp/Resources/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | APPL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 | LSRequiresIPhoneOS
22 |
23 | UILaunchStoryboardName
24 | LaunchScreen
25 | UIRequiredDeviceCapabilities
26 |
27 | armv7
28 |
29 | UISupportedInterfaceOrientations
30 |
31 | UIInterfaceOrientationPortrait
32 | UIInterfaceOrientationLandscapeLeft
33 | UIInterfaceOrientationLandscapeRight
34 |
35 | UISupportedInterfaceOrientations~ipad
36 |
37 | UIInterfaceOrientationPortrait
38 | UIInterfaceOrientationPortraitUpsideDown
39 | UIInterfaceOrientationLandscapeLeft
40 | UIInterfaceOrientationLandscapeRight
41 |
42 | NSAppTransportSecurity
43 |
44 | NSAllowsArbitraryLoads
45 |
46 |
47 |
48 |
49 |
--------------------------------------------------------------------------------
/BlogApp/Resources/TestsInfo.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | BNDL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 |
22 |
23 |
--------------------------------------------------------------------------------
/BlogApp/Sources/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // BlogApp
4 | //
5 | // Created by Bohdan Orlov on 17/03/2018.
6 | // Copyright © 2018 Bohdan Orlov. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import UI
11 | import Domain
12 | import Features
13 |
14 | @UIApplicationMain
15 | class AppDelegate: UIResponder, UIApplicationDelegate, UIWindowOwner {
16 | var windows = [String : UIWindow]()
17 |
18 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
19 | MainFlow.start(windowOwner: self)
20 | return true
21 | }
22 | }
23 |
24 |
--------------------------------------------------------------------------------
/BlogApp/Sources/Flows/MainFlow.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MainFlow.swift
3 | // BlogApp
4 | //
5 | // Created by Bohdan Orlov on 25/03/2018.
6 | // Copyright © 2018 Bohdan Orlov. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import UIKit
11 | import Core
12 | import Domain
13 | import UI
14 | import Features
15 |
16 | struct MainFlow {
17 |
18 | static func start(windowOwner: UIWindowOwner) {
19 | makeWindows(windowOwner: windowOwner) { viewController in
20 | setupServices { networkService, sessionService, pushNotificationService in
21 | startUIFlow(viewController, sessionService, networkService)
22 | startPushNotificationHandlingFlow(viewController, pushNotificationService) {
23 | restart(windowOwner: windowOwner)
24 | }
25 | }
26 | }
27 | }
28 |
29 | private static func restart(windowOwner: UIWindowOwner) {
30 | UserDefaults.standard.reset()
31 | start(windowOwner: windowOwner)
32 |
33 | preventsWeirdCrash()
34 | }
35 |
36 | private static func setupServices(didSetup: (NetworkRequestSending, SessionServiceProtocol, PushNotificationServiceProtocol) -> Void) {
37 | setupNetworkService() { networkService in
38 | setupSessionService(networkService) { sessionService in
39 | setupPushNotificationService() { pushNotificationService in
40 | didSetup(networkService, sessionService, pushNotificationService)
41 | }
42 | }
43 | }
44 | }
45 |
46 | private static func makeWindows(windowOwner: UIWindowOwner, didSetupWindow: @escaping (UIViewController) -> Void) {
47 | defineWindowFrames(UIScreen.main.bounds) { windowFrame in
48 | setupWindow(windowFrame, windowOwner, didSetupWindow)
49 | }
50 | }
51 |
52 | static var startUIFlow = UIFlow.start
53 | static var startPushNotificationHandlingFlow = PushNotificationHandlingFlow.start
54 |
55 | static var defineWindowFrames = WindowFrameFeature.launch
56 | static var setupWindow = WindowFeature.launch
57 |
58 | static var setupNetworkService = NetworkService.shared
59 | static var setupSessionService = SessionService.shared
60 | static var setupPushNotificationService = PushNotificationService.shared
61 | }
62 |
63 |
--------------------------------------------------------------------------------
/BlogApp/Sources/Flows/PushNotificationHandlingFlow.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RestartPushNotificationFlow.swift
3 | // BlogApp
4 | //
5 | // Created by Bohdan Orlov on 31/03/2018.
6 | // Copyright © 2018 Bohdan Orlov. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import UIKit
11 | import Domain
12 | import Features
13 |
14 | struct PushNotificationHandlingFlow {
15 |
16 | static func start(viewController: UIViewController, pushNotificationService: PushNotificationServiceProtocol, didReceiveRestartPush: @escaping () -> Void) {
17 | showSendPushNotificationButton(viewController, pushNotificationService)
18 | setupRestartPushNotificationHandling(pushNotificationService, didReceiveRestartPush)
19 | }
20 |
21 | static var setupRestartPushNotificationHandling = RestartPushNotificationFeature.launch
22 | static var showSendPushNotificationButton = PushNotificationButtonFeature.launch
23 | }
24 |
--------------------------------------------------------------------------------
/BlogApp/Sources/Flows/PushNotificationHandlingFlowTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RestartPushNotificationFlowTests.swift
3 | // BlogAppTests
4 | //
5 | // Created by Bohdan Orlov on 31/03/2018.
6 | // Copyright © 2018 Bohdan Orlov. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | import Foundation
11 | import Core
12 | import CoreTestsSupport
13 | import Domain
14 | import DomainTestsSupport
15 | import UI
16 | import UITestsSupport
17 |
18 | class PushNotificationHandlingFlowTests: XCTestCase {
19 |
20 | func test_GivenMockedLauncher_WhenFlowStarted_ThenShowSendPushNotificationButtonCalled() {
21 | stubAllLaunchers()
22 | var called = false
23 | PushNotificationHandlingFlow.showSendPushNotificationButton = {_,_ in
24 | called = true
25 | }
26 |
27 | PushNotificationHandlingFlow.start(viewController: UIViewController(),
28 | pushNotificationService: FakePushNotificationService()) { }
29 |
30 | XCTAssert(called)
31 | }
32 |
33 | func test_GivenMockedLauncher_WhenFlowStarted_ThenSetupRestartPushNotificationHandlingCalled() {
34 | stubAllLaunchers()
35 | var called = false
36 | PushNotificationHandlingFlow.setupRestartPushNotificationHandling = {_,_ in
37 | called = true
38 | }
39 |
40 | PushNotificationHandlingFlow.start(viewController: UIViewController(),
41 | pushNotificationService: FakePushNotificationService()) { }
42 |
43 | XCTAssert(called)
44 | }
45 |
46 | func stubAllLaunchers() {
47 | PushNotificationHandlingFlow.setupRestartPushNotificationHandling = {_,_ in }
48 | PushNotificationHandlingFlow.showSendPushNotificationButton = {_,_ in }
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/BlogApp/Sources/Flows/UIFlow.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIFlow.swift
3 | // BlogApp
4 | //
5 | // Created by Bohdan Orlov on 31/03/2018.
6 | // Copyright © 2018 Bohdan Orlov. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import UIKit
11 | import Domain
12 | import UI
13 | import Features
14 |
15 | struct UIFlow {
16 |
17 | static func start(viewController: UIViewController, sessionService: SessionServiceProtocol, networkService: NetworkRequestSending) {
18 | showLoginScreen(viewController, sessionService) { [unowned viewController] session in
19 | showTabBar(viewController) { tabControllers in
20 | showNavigationBar(tabControllers.posts) { viewController in
21 | showPosts(viewController, session.userId, networkService) { buttonContainer in
22 | showLogoutButton(buttonContainer, sessionService) { }
23 | }
24 | }
25 | showNavigationBar(tabControllers.comments) { viewController in
26 | showComments(viewController, session.userId, networkService)
27 | }
28 | }
29 | }
30 | }
31 |
32 | static var showLoginScreen = LoginScreenFeature.launch
33 | static var showPosts = PostsScreenFeature.launch
34 | static var showComments = CommentsScreenFeature.launch
35 | static var showLogoutButton = LogoutButtonFeature.launch
36 | static var showTabBar = TabBarFeature.launchMain
37 | static var showNavigationBar = NavigationBarFeature.launch
38 | }
39 |
40 |
--------------------------------------------------------------------------------
/BlogApp/Sources/Flows/UIFlowTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIFlowTests.swift
3 | // BlogAppTests
4 | //
5 | // Created by Bohdan Orlov on 05/04/2018.
6 | // Copyright © 2018 Bohdan Orlov. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | import Foundation
11 | import Core
12 | import CoreTestsSupport
13 | import Domain
14 | import DomainTestsSupport
15 | import UI
16 | import UITestsSupport
17 |
18 | class UIFlowTests: XCTestCase {
19 |
20 | enum Launchers: String {
21 | case showLoginScreen
22 | case showPosts
23 | case showComments
24 | case showLogoutButton
25 | case showTabBar
26 | case showNavigationBar
27 | }
28 |
29 | var expectedLaunchers: [Launchers] = [.showLoginScreen,
30 | .showTabBar,
31 | .showNavigationBar,
32 | .showPosts,
33 | .showLogoutButton,
34 | .showNavigationBar,
35 | .showComments ]
36 | var calledLaunchers = [Launchers]()
37 |
38 | func test_GivenMockedLaunchers_WhenFlowStarted_ThenExpectedLaunchersCalled() {
39 | stubLaunchers()
40 |
41 | startFlow()
42 |
43 | XCTAssertEqual(calledLaunchers, expectedLaunchers)
44 | }
45 |
46 | func startFlow() {
47 | UIFlow.start(viewController: UIViewController(), sessionService: FakeSessionService(), networkService: FakeNetworkService())
48 | }
49 |
50 | func stubLaunchers() {
51 | UIFlow.showLoginScreen = { _,_, next in
52 | self.calledLaunchers.append(.showLoginScreen)
53 | next(Session(userId: 0, username: ""))
54 | }
55 | UIFlow.showPosts = { _,_,_, next in
56 | self.calledLaunchers.append(.showPosts)
57 | next(UIView())
58 | }
59 | UIFlow.showComments = { _,_,_ in
60 | self.calledLaunchers.append(.showComments)
61 | }
62 | UIFlow.showLogoutButton = { _,_,next in
63 | self.calledLaunchers.append(.showLogoutButton)
64 | }
65 | UIFlow.showTabBar = { _,next in
66 | self.calledLaunchers.append(.showTabBar)
67 | next(MainTabs(viewControllers: MainTabs.titles.map{ _ in return UIViewController() }))
68 | }
69 | UIFlow.showNavigationBar = { _,next in
70 | self.calledLaunchers.append(.showNavigationBar)
71 | next(UIViewController())
72 | }
73 | }
74 |
75 | }
76 |
77 | class FakeNetworkService: NetworkRequestSending {
78 |
79 | func send(request: Request, completionHandler: @escaping (Response) -> Void) -> URLSessionDataTask {
80 | return URLSessionDataTask()
81 | }
82 |
83 | }
84 |
--------------------------------------------------------------------------------
/BlogApp/Sources/Launchers/ComponentLaunchers.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ComponentLaunchers.swift
3 | // BlogApp
4 | //
5 | // Created by Bohdan Orlov on 31/03/2018.
6 | // Copyright © 2018 Bohdan Orlov. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import UIKit
11 | import UI
12 | import Domain
13 | import Features
14 |
15 | extension LogoutButtonFeature {
16 | static func launch(buttonContainer: UIView, sessionService: SessionServiceProtocol, didLogout: @escaping () -> Void) {
17 | LogoutButtonFeature(viewPresenter: ViewPresenter(rootView: buttonContainer), sessionService: sessionService, didLogout: didLogout)
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/BlogApp/Sources/Launchers/PresentationLaunchers.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PresentationLaunchers.swift
3 | // BlogApp
4 | //
5 | // Created by Bohdan Orlov on 31/03/2018.
6 | // Copyright © 2018 Bohdan Orlov. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import UIKit
11 | import Core
12 | import Domain
13 | import UI
14 | import Features
15 |
16 | extension WindowFrameFeature {
17 | static func launch(screenBounds: CGRect, didDefineScreenFrames: (CGRect) -> Void) {
18 | WindowFrameFeature(screenBounds: screenBounds, splitScreen: false, didDefineScreenFrames: didDefineScreenFrames)
19 | }
20 | }
21 |
22 | extension WindowFeature {
23 | static func launch(windowFrame: CGRect, windowOwner: UIWindowOwner, didSetupWindow: @escaping (UIViewController) -> Void) {
24 | WindowFeature(windowFrame: windowFrame, windowOwner: windowOwner, didSetupWindow: didSetupWindow)
25 | }
26 | }
27 |
28 | struct MainTabs: TabControllersContainer {
29 | init(viewControllers: [UIViewController]) {
30 | posts = viewControllers[0]
31 | comments = viewControllers[1]
32 | }
33 |
34 | let posts: UIViewController
35 | let comments: UIViewController
36 |
37 | static var titles: [String] {
38 | return ["Posts", "Comments"]
39 | }
40 | }
41 |
42 | extension TabBarFeature {
43 |
44 | static func launch(tabs: T.Type, rootViewController: UIViewController, didShowTabBar: @escaping (T) -> Void) {
45 | TabBarFeature(tabs: tabs,
46 | tabBarController: UITabBarController(),
47 | viewControllerPresenting: ViewControllerPresenter(rootViewController: rootViewController, application: .shared),
48 | didShowTabBar: didShowTabBar)
49 | }
50 |
51 | static func launchMain(rootViewController: UIViewController, didShowTabBar: @escaping (MainTabs) -> Void) {
52 | self.launch(tabs: MainTabs.self, rootViewController: rootViewController, didShowTabBar: didShowTabBar)
53 | }
54 |
55 |
56 | }
57 |
58 | extension NavigationBarFeature {
59 | static func launch(rootViewController: UIViewController, didShowNavigationBar: @escaping (UIViewController) -> Void) {
60 | NavigationBarFeature(viewControllerPresenter: ViewControllerPresenter(rootViewController: rootViewController, application: .shared),
61 | navigationController: UINavigationController(),
62 | didShowNavigationBar: didShowNavigationBar)
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/BlogApp/Sources/Launchers/PushNotificationsLaunchers.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PushNotificationsLaunchers.swift
3 | // BlogApp
4 | //
5 | // Created by Bohdan Orlov on 31/03/2018.
6 | // Copyright © 2018 Bohdan Orlov. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import UIKit
11 | import Domain
12 | import Features
13 |
14 | extension RestartPushNotificationFeature {
15 | static func launch(pushNotificationService: PushNotificationServiceProtocol,
16 | didReceiveRestartRequest: @escaping () -> Void) {
17 | RestartPushNotificationFeature(pushNotificationService:pushNotificationService, didReceiveRestartRequest: didReceiveRestartRequest)
18 | }
19 | }
20 |
21 | extension PushNotificationButtonFeature {
22 | static func launch(rootViewController: UIViewController,
23 | pushNotificationService: PushNotificationServiceProtocol) {
24 | PushNotificationButtonFeature(rootViewController: rootViewController, pushNotificationService:pushNotificationService)
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/BlogApp/Sources/Launchers/ScreenLaunchers.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ScreenLaunchers.swift
3 | // BlogApp
4 | //
5 | // Created by Bohdan Orlov on 31/03/2018.
6 | // Copyright © 2018 Bohdan Orlov. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import UIKit
11 | import Domain
12 | import UI
13 | import Features
14 |
15 | extension LoginScreenFeature {
16 | static func launch(rootViewController: UIViewController,
17 | sessionService: SessionServiceProtocol,
18 | didLogin: @escaping (Session) -> Void) {
19 | LoginScreenFeature(loginViewController: LoginViewController(),
20 | viewControllerPresenter: ViewControllerPresenter(rootViewController: rootViewController, application: .shared),
21 | sessionService: sessionService,
22 | didLogin: didLogin)
23 | }
24 | }
25 |
26 | extension PostsScreenFeature {
27 | static func launch(viewController: UIViewController, userId: Int, networkService: NetworkRequestSending, didPrepareButtonContainer: @escaping (UIView) -> Void) {
28 | PostsScreenFeature(postsViewController: PostsViewController(),
29 | viewControllerPresenting: ViewControllerPresenter(rootViewController: viewController, application: .shared),
30 | userId: userId,
31 | postsRepository: PostsRepository(networkService: networkService),
32 | didPrepareButtonContainer: didPrepareButtonContainer)
33 | }
34 | }
35 |
36 | extension CommentsScreenFeature {
37 | static func launch(viewController: UIViewController, userId: Int, networkService: NetworkRequestSending) {
38 | let postsRepository = PostsRepository(networkService: networkService)
39 | let commentsRepository = CommentsRepository(networkService: networkService)
40 | let userCommentsRepository = UserCommentsRepository(postsRepository: postsRepository,
41 | commentsRepository: commentsRepository)
42 | CommentsScreenFeature(commentsViewController: StringsTableViewController(),
43 | viewControllerPresenting: ViewControllerPresenter(rootViewController: viewController, application: .shared),
44 | userId: userId,
45 | commentsRepository: userCommentsRepository)
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/BlogApp/Sources/Launchers/ServiceFactories.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ServiceFactories.swift
3 | // BlogApp
4 | //
5 | // Created by Bohdan Orlov on 31/03/2018.
6 | // Copyright © 2018 Bohdan Orlov. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import Domain
11 |
12 | extension NetworkService {
13 | static func shared(didSetup: (NetworkRequestSending) -> Void) {
14 | let urlString = "https://jsonplaceholder.typicode.com" // this might not work since that data is publicly mutable
15 | // To start local server: https://github.com/typicode/json-server
16 | // let urlString = "http://localhost:3000" // json-server --watch db.json
17 | didSetup(NetworkService(hostURL: URL(string: urlString)!, session: URLSession(configuration: .default)))
18 | }
19 | }
20 |
21 | extension SessionService {
22 | static func shared(networkService: NetworkRequestSending, didSetup: (SessionServiceProtocol) -> Void) {
23 | let userRepository = UserRepository(networkService: networkService)
24 | didSetup(SessionService(userProvider: userRepository, userDefaults: .standard))
25 | }
26 | }
27 |
28 | extension PushNotificationServiceProtocol {
29 | static func shared(didSetupService: (PushNotificationServiceProtocol) -> Void) {
30 | didSetupService(PushNotificationService())
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/BlogApp/Sources/UtilityServices/NetworkService.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NetworkService.swift
3 | // BlogApp
4 | //
5 | // Created by Bohdan Orlov on 25/03/2018.
6 | // Copyright © 2018 Bohdan Orlov. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import Domain
11 |
12 | public final class NetworkService: NetworkRequestSending {
13 | public let hostURL: URL
14 | public let session: URLSessionProtocol
15 |
16 | public init(hostURL: URL, session: URLSessionProtocol) {
17 | self.hostURL = hostURL
18 | self.session = session
19 | }
20 |
21 | public func send(request: Request, completionHandler: @escaping (Response) -> Void) -> URLSessionDataTask {
22 | let dataTask = self.session.dataTask(with: self.url(for: request)) {
23 | data, response, error in
24 | DispatchQueue.main.async {
25 | guard let httpResponse = response as? HTTPURLResponse
26 | , (200..<300) ~= httpResponse.statusCode
27 | , let data = data else {
28 | let error = error ?? NSError(domain: String(describing: NetworkRequestSending.self), code: -1)
29 | completionHandler(Response.failure(error))
30 | return
31 | }
32 | completionHandler(Response.success(data))
33 | }
34 | }
35 | dataTask.resume()
36 | return dataTask
37 | }
38 |
39 | private func url(for request: Request) -> URL {
40 | return self.hostURL.appendingPathComponent(request.endpoint)
41 | }
42 |
43 | }
44 |
45 |
46 | public protocol URLSessionProtocol {
47 | func dataTask(with url: URL, completionHandler: @escaping (Data?, URLResponse?, Error?) -> Void) -> URLSessionDataTask
48 | }
49 |
50 |
51 | extension URLSession: URLSessionProtocol {
52 |
53 | }
54 |
55 |
--------------------------------------------------------------------------------
/Core/Core.xcodeproj/xcshareddata/xcschemes/Core.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
31 |
32 |
33 |
34 |
35 |
36 |
47 |
48 |
54 |
55 |
56 |
57 |
58 |
59 |
65 |
66 |
72 |
73 |
74 |
75 |
77 |
78 |
81 |
82 |
83 |
--------------------------------------------------------------------------------
/Core/Resources/TestsInfo.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | BNDL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 |
22 |
23 |
--------------------------------------------------------------------------------
/Core/Sources/AnyObject+Retain.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AnyObject+Retain.swift
3 | // Core
4 | //
5 | // Created by Bohdan Orlov on 08/04/2018.
6 | // Copyright © 2018 Bohdan Orlov. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 |
12 | public protocol Retaining {
13 | }
14 |
15 |
16 | private struct AssosiatedObject {
17 | static var key = "Key"
18 | }
19 |
20 | extension Retaining where Self: AnyObject {
21 | public func retain(_ object: AnyObject) {
22 | var retainedObjects = objc_getAssociatedObject(self, &AssosiatedObject.key) as? [AnyObject] ?? []
23 | retainedObjects.append(object)
24 | objc_setAssociatedObject(self, &AssosiatedObject.key, retainedObjects, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
25 | }
26 | }
27 |
28 | extension NSObject: Retaining {}
29 |
--------------------------------------------------------------------------------
/Core/Sources/KeyPathBuildable.swift:
--------------------------------------------------------------------------------
1 | //
2 | // KeyPathBuildable.swift
3 | // Core
4 | //
5 | // Created by Bohdan Orlov on 02/04/2018.
6 | // Copyright © 2018 Bohdan Orlov. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public protocol KeyPathBuildable {}
12 |
13 | extension NSObject: KeyPathBuildable {}
14 |
15 | extension KeyPathBuildable where Self: Any {
16 | public func set(_ property: ReferenceWritableKeyPath, _ value: T) -> Self {
17 | self[keyPath: property] = value
18 | return self
19 | }
20 | }
21 |
22 |
23 |
--------------------------------------------------------------------------------
/Core/Sources/Observable.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Observable.swift
3 | // Architecture
4 | //
5 | // Created by Bohdan Orlov on 10/03/2018.
6 | // Copyright © 2018 Bohdan Orlov. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public typealias ComputedPropertyChanges = Void
12 |
13 | public protocol ObserverProtocol: AnyObject {
14 |
15 | }
16 |
17 | public class ReadonlyObservable {
18 | private let mutableObservable: MutableObservable
19 | public var value: T {
20 | return mutableObservable.value
21 | }
22 | public init(_ mutableObservable: MutableObservable) {
23 | self.mutableObservable = mutableObservable
24 | }
25 |
26 | public func observe(_ closure: @escaping (_ old: T, _ new: T) -> Void) -> ObserverProtocol {
27 | return self.mutableObservable.observe(closure)
28 | }
29 |
30 | public func observeAndCall(_ closure: @escaping (T) -> Void) -> ObserverProtocol {
31 | return self.mutableObservable.observeAndCall(closure)
32 | }
33 | }
34 |
35 | public class MutableObservable {
36 |
37 | public init(_ value: T) {
38 | self._value = value
39 | }
40 |
41 | public var value: T {
42 | get {
43 | return _value
44 | }
45 | set {
46 | self.setValue(newValue)
47 | }
48 | }
49 |
50 | public func makeReadonly() -> ReadonlyObservable {
51 | return ReadonlyObservable(self)
52 | }
53 |
54 | /**
55 | Sets new value allowing specifying which observers shouldn't be notified
56 |
57 | - parameter value: the new value
58 | - parameter withoutNofiyingObservers: observers that won't be notified. For convenience of the caller, you can pass nil in the collection
59 | */
60 | public func setValue(_ value: T, withoutNofiyingObservers blacklistedObservers: [ObserverProtocol?] = []) {
61 | let oldValue = self._value
62 | self._value = value
63 |
64 | let blacklistedObserverPointers = Set(blacklistedObservers.flatMap({ return ($0 as? ObserverWrapper)?.observer }).map({ return Unmanaged.passUnretained($0).toOpaque() }))
65 | for observer in self.observers {
66 | let observerIsBlacklisted = blacklistedObserverPointers.contains(Unmanaged.passUnretained(observer).toOpaque())
67 | if !observerIsBlacklisted {
68 | observer.closure(oldValue, self.value)
69 | }
70 | }
71 | }
72 |
73 | private var _value: T
74 |
75 | /**
76 | Subscribes to a notification.
77 |
78 | Returns an opaque observer that needs to be retained. This opaque observer unsubscribes automatically on dealloc.
79 |
80 | @return AnyObject Opaque observer
81 | */
82 | public func observe(_ closure: @escaping (_ old: T, _ new: T) -> Void) -> ObserverProtocol {
83 | let observer = Observer(closure: closure)
84 | self.observers.append(observer)
85 | return ObserverWrapper(observer: observer, onDeinit: { [weak self] observer in
86 | self?.removeObserver(observer)
87 | })
88 | }
89 |
90 | public func observeAndCall(_ closure: @escaping (T) -> Void) -> ObserverProtocol {
91 | let observer = self.observe({ _, new in
92 | closure(new)
93 | })
94 | closure(self.value)
95 | return observer
96 | }
97 |
98 | fileprivate func removeObserver(_ observer: Observer) {
99 | self.observers = self.observers.filter { $0 !== observer }
100 | }
101 |
102 | private lazy var observers = [Observer]()
103 | }
104 |
105 | public class Stream {
106 | private let mutableStream: MutableStream
107 | public init(_ mutableStream: MutableStream) {
108 | self.mutableStream = mutableStream
109 | }
110 |
111 | public func subscribe(_ closure: @escaping (_ value: T) -> Void) -> ObserverProtocol {
112 | return self.mutableStream.subscribe(closure)
113 | }
114 | }
115 |
116 | public class MutableStream {
117 |
118 | public init() {
119 | }
120 |
121 | public func makeReadonly() -> Stream {
122 | return Stream(self)
123 | }
124 |
125 | public func emit(value: T) {
126 | for observer in self.observers {
127 | observer.closure(value, value)
128 | }
129 | }
130 |
131 | public func subscribe(_ closure: @escaping (_ value: T) -> Void) -> ObserverProtocol {
132 | let observer = Observer(closure: { _, value in
133 | closure(value)
134 | })
135 | self.observers.append(observer)
136 | return ObserverWrapper(observer: observer, onDeinit: { [weak self] observer in
137 | self?.removeObserver(observer)
138 | })
139 | }
140 |
141 | fileprivate func removeObserver(_ observer: Observer) {
142 | self.observers = self.observers.filter { $0 !== observer }
143 | }
144 |
145 | private lazy var observers = [Observer]()
146 | }
147 |
148 | private class Observer {
149 | let closure: (_ old: T, _ new: T) -> Void
150 | init (closure: @escaping (_ old: T, _ new: T) -> Void) {
151 | self.closure = closure
152 | }
153 | }
154 |
155 | private class ObserverWrapper: ObserverProtocol {
156 | let observer: Observer
157 | let onDeinit: (Observer) -> Void
158 |
159 | init(observer: Observer, onDeinit: @escaping (Observer) -> Void) {
160 | self.observer = observer
161 | self.onDeinit = onDeinit
162 | }
163 |
164 | deinit {
165 | self.onDeinit(self.observer)
166 | }
167 | }
168 |
--------------------------------------------------------------------------------
/Core/Sources/UserDefault+Reset.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UserDefaults+Reset.swift
3 | // Core
4 | //
5 | // Created by Bohdan Orlov on 03/04/2018.
6 | // Copyright © 2018 Bohdan Orlov. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | extension UserDefaults {
12 | public func reset() {
13 | removePersistentDomain(forName: Bundle.main.bundleIdentifier!)
14 | synchronize()
15 | }
16 | }
17 |
18 |
19 | public func preventsWeirdCrash() {
20 | // not making this call resulted in unrecognized selector on calling reset()
21 | }
22 |
--------------------------------------------------------------------------------
/Domain/Domain.xcodeproj/xcshareddata/xcschemes/Domain.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
31 |
32 |
34 |
40 |
41 |
42 |
43 |
44 |
50 |
51 |
52 |
53 |
54 |
55 |
66 |
67 |
73 |
74 |
75 |
76 |
77 |
78 |
84 |
85 |
91 |
92 |
93 |
94 |
96 |
97 |
100 |
101 |
102 |
--------------------------------------------------------------------------------
/Domain/Resources/TestsInfo.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | BNDL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 |
22 |
23 |
--------------------------------------------------------------------------------
/Domain/Sources/Entities/Comment.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Comment.swift
3 | // Domain
4 | //
5 | // Created by Bohdan Orlov on 26/03/2018.
6 | // Copyright © 2018 Bohdan Orlov. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public struct Comment: Codable {
12 | public let id: Int
13 | public let postId: Int
14 | public let body: String
15 | }
16 |
--------------------------------------------------------------------------------
/Domain/Sources/Entities/Post.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Post.swift
3 | // Domain
4 | //
5 | // Created by Bohdan Orlov on 26/03/2018.
6 | // Copyright © 2018 Bohdan Orlov. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public struct Post: Decodable {
12 | public let id: Int
13 | public let userId: Int
14 | public let body: String
15 | }
16 |
--------------------------------------------------------------------------------
/Domain/Sources/Entities/PushNotification.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PushNotification.swift
3 | // Domain
4 | //
5 | // Created by Bohdan Orlov on 06/04/2018.
6 | // Copyright © 2018 Bohdan Orlov. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public enum PushNotification {
12 | case restart
13 | }
14 |
--------------------------------------------------------------------------------
/Domain/Sources/Entities/Session.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Entities.swift
3 | // Architecture
4 | //
5 | // Created by Bohdan Orlov on 27/02/2018.
6 | // Copyright © 2018 Bohdan Orlov. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import UIKit
11 |
12 | public struct Session: Codable {
13 | public let userId: Int
14 | public let username: String
15 | public init(userId: Int, username: String) {
16 | self.userId = userId
17 | self.username = username
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/Domain/Sources/Entities/User.swift:
--------------------------------------------------------------------------------
1 | //
2 | // User.swift
3 | // Domain
4 | //
5 | // Created by Bohdan Orlov on 25/03/2018.
6 | // Copyright © 2018 Bohdan Orlov. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public struct User: Codable {
12 |
13 | public var id: Int
14 | public var username: String
15 |
16 | }
17 |
--------------------------------------------------------------------------------
/Domain/Sources/Protocols/NetworkServiceProtocol.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NetworkServiceProtocol.swift
3 | // Domain
4 | //
5 | // Created by Bohdan Orlov on 25/03/2018.
6 | // Copyright © 2018 Bohdan Orlov. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 |
12 | public struct Request {
13 | public let endpoint: String
14 | public init(endpoint: String) {
15 | self.endpoint = endpoint
16 | }
17 | }
18 |
19 | public enum Response {
20 | case success(Data)
21 | case failure(Error)
22 | }
23 |
24 | public protocol NetworkServiceTask {
25 | var url: URL { get }
26 | }
27 |
28 | public protocol NetworkRequestSending {
29 | @discardableResult func send(request: Request, completionHandler: @escaping (Response) -> Void) -> URLSessionDataTask
30 | }
31 |
32 |
--------------------------------------------------------------------------------
/Domain/Sources/Repositories/CommentsRepository.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CommentsRepository.swift
3 | // Domain
4 | //
5 | // Created by Bohdan Orlov on 26/03/2018.
6 | // Copyright © 2018 Bohdan Orlov. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public protocol CommentsProviding {
12 | func comments(postIds: [Int], didFinish: @escaping ([Comment]) -> Void)
13 | }
14 |
15 | public final class CommentsRepository: CommentsProviding {
16 | private let networkService: NetworkRequestSending
17 |
18 | public init(networkService: NetworkRequestSending) {
19 | self.networkService = networkService
20 | }
21 |
22 | public func comments(postIds: [Int], didFinish: @escaping ([Comment]) -> Void) {
23 | self.networkService.send(request: .init(endpoint: "comments"), completionHandler: { response in
24 | switch response {
25 | case .success(let data):
26 | guard let comments = try? JSONDecoder().decode([Comment].self, from: data) else {
27 | didFinish([])
28 | return
29 | }
30 | let postComments = comments.filter { comment in
31 | postIds.contains(comment.postId)
32 | }
33 | didFinish(postComments)
34 | case .failure(_):
35 | didFinish([])
36 | }
37 | })
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/Domain/Sources/Repositories/PostsRepository.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PostsRepository.swift
3 | // Domain
4 | //
5 | // Created by Bohdan Orlov on 26/03/2018.
6 | // Copyright © 2018 Bohdan Orlov. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public protocol PostsProviding {
12 | func posts(userId: Int, didFinish: @escaping ([Post]) -> Void)
13 | }
14 |
15 | public final class PostsRepository: PostsProviding {
16 | private let networkService: NetworkRequestSending
17 |
18 | public init(networkService: NetworkRequestSending) {
19 | self.networkService = networkService
20 | }
21 |
22 | public func posts(userId: Int, didFinish: @escaping ([Post]) -> Void) {
23 | self.networkService.send(request: .init(endpoint: "posts"), completionHandler: { response in
24 | switch response {
25 | case .success(let data):
26 | guard let posts = try? JSONDecoder().decode([Post].self, from: data) else {
27 | didFinish([])
28 | return
29 | }
30 | let userPosts = posts.filter { post in
31 | post.userId == userId
32 | }
33 | didFinish(userPosts)
34 | case .failure(_):
35 | didFinish([])
36 | }
37 | })
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/Domain/Sources/Repositories/UserCommentsRepository.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UserCommentsRepository.swift
3 | // Domain
4 | //
5 | // Created by Bohdan Orlov on 26/03/2018.
6 | // Copyright © 2018 Bohdan Orlov. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public protocol UserCommentsProviding {
12 | func comments(userId: Int, didFinish: @escaping ([Comment]) -> Void)
13 | }
14 |
15 | public class UserCommentsRepository: UserCommentsProviding {
16 |
17 | private let postsRepository: PostsProviding
18 | private let commentsRepository: CommentsProviding
19 |
20 | public init(postsRepository: PostsProviding, commentsRepository: CommentsProviding) {
21 | self.postsRepository = postsRepository
22 | self.commentsRepository = commentsRepository
23 | }
24 |
25 | public func comments(userId: Int, didFinish: @escaping ([Comment]) -> Void) {
26 | self.postsRepository.posts(userId: userId) { posts in
27 | self.commentsRepository.comments(postIds: posts.map { $0.id }) { comments in
28 | didFinish(comments)
29 | }
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/Domain/Sources/Repositories/UserRepository.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UserRepository.swift
3 | // Domain
4 | //
5 | // Created by Bohdan Orlov on 25/03/2018.
6 | // Copyright © 2018 Bohdan Orlov. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public protocol UserProviding {
12 | func user(username: String, didFinish: @escaping (User?) -> Void)
13 | }
14 |
15 | public final class UserRepository: UserProviding {
16 | private let networkService: NetworkRequestSending
17 |
18 | public init(networkService: NetworkRequestSending) {
19 | self.networkService = networkService
20 | }
21 |
22 | public func user(username: String, didFinish: @escaping (User?) -> Void) {
23 | self.networkService.send(request: .init(endpoint: "users"), completionHandler: { response in
24 | switch response {
25 | case .success(let data):
26 | var users: [User]?
27 | do {
28 | users = try JSONDecoder().decode([User].self, from: data)
29 | } catch {
30 | print(error)
31 | }
32 | let user = users?.first(where: { user in
33 | return user.username == username
34 | })
35 | didFinish(user)
36 | case .failure(_):
37 | didFinish(nil)
38 | }
39 | })
40 | }
41 | }
42 |
43 |
--------------------------------------------------------------------------------
/Domain/Sources/Services/FakeDomain.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FakeDomain.swift
3 | // DomainTestsSupport
4 | //
5 | // Created by Bohdan Orlov on 17/03/2018.
6 | // Copyright © 2018 Bohdan Orlov. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import Core
11 | import CoreTestsSupport
12 | import Domain
13 |
14 | public final class FakeSessionService: SessionServiceProtocol {
15 |
16 | public init() { }
17 |
18 | public var observableSessionState: ReadonlyObservable {
19 | return stubbedSessionState.makeReadonly()
20 | }
21 |
22 | public var startSessionCalled = false
23 | public var stopSessionCalled = false
24 |
25 | public var stubbedSessionState = MutableObservable(.readyToStart)
26 |
27 | public func startSession(username: String, password: String) {
28 | startSessionCalled = true
29 | }
30 |
31 | public func stopSession() {
32 | stopSessionCalled = true
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/Domain/Sources/Services/FakePushNotificationService.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FakePushNotificationService.swift
3 | // DomainTestsSupport
4 | //
5 | // Created by Bohdan Orlov on 31/03/2018.
6 | // Copyright © 2018 Bohdan Orlov. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import Core
11 | import Domain
12 |
13 | public final class FakePushNotificationService: PushNotificationServiceProtocol {
14 | public var stubLastReceivedPush = MutableObservable(nil)
15 | public var lastReceivedPush: ReadonlyObservable {
16 | return stubLastReceivedPush.makeReadonly()
17 | }
18 |
19 | public init() {
20 |
21 | }
22 |
23 | public func push(notification: PushNotification) {
24 | fatalError()
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/Domain/Sources/Services/PushNotificationService.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Services.swift
3 | // Domain
4 | //
5 | // Created by Bohdan Orlov on 01/03/2018.
6 | // Copyright © 2018 Bohdan Orlov. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import Core
11 |
12 | public protocol PushNotificationServiceProtocol: AnyObject, Retaining {
13 | var lastReceivedPush: ReadonlyObservable { get }
14 | func push(notification: PushNotification)
15 | }
16 |
17 | public class PushNotificationService: PushNotificationServiceProtocol {
18 | public var lastReceivedPush: ReadonlyObservable {
19 | return self.mutableLastReceivedPush.makeReadonly()
20 | }
21 | private var mutableLastReceivedPush = MutableObservable(nil)
22 |
23 | public init() {}
24 |
25 | public func push(notification: PushNotification) {
26 | self.mutableLastReceivedPush.value = notification
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/Domain/Sources/Services/SessionService.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SessionService.swift
3 | // Domain
4 | //
5 | // Created by Bohdan Orlov on 25/03/2018.
6 | // Copyright © 2018 Bohdan Orlov. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import Core
11 |
12 | public enum SessionState {
13 | case readyToStart
14 | case starting
15 | case started(Session)
16 | case stopped
17 | case failed(Error)
18 | }
19 |
20 | public protocol SessionServiceProtocol: AnyObject {
21 | var observableSessionState: ReadonlyObservable { get }
22 | func startSession(username: String, password: String)
23 | func stopSession()
24 | }
25 |
26 | public class SessionService: SessionServiceProtocol {
27 | public lazy var observableSessionState = self.mutableObservableSessionState.makeReadonly()
28 | private let mutableObservableSessionState = MutableObservable(.readyToStart)
29 |
30 | private let userProvider: UserProviding
31 | private let userDefaults: UserDefaults
32 | private let persistenceKey: String
33 |
34 | static var persistenceSalt = 0 // in the split screen mode we have to keep persistant storages separate for separate session services
35 |
36 | public init(userProvider: UserProviding, userDefaults: UserDefaults) {
37 | self.userProvider = userProvider
38 | self.userDefaults = userDefaults
39 | self.persistenceKey = "blog.app.session.\(SessionService.persistenceSalt)"
40 | self.attemptToRestoreSession()
41 | SessionService.persistenceSalt += 1
42 | }
43 |
44 | public func startSession(username: String, password: String) {
45 | guard case .readyToStart = self.observableSessionState.value else {
46 | assertionFailure()
47 | return
48 | }
49 | self.mutableObservableSessionState.value = .starting
50 | self.userProvider.user(username: username) { [weak self] user in
51 | self?.handle(user)
52 | }
53 | }
54 |
55 | public func stopSession() {
56 | guard case .started(_) = self.observableSessionState.value else {
57 | assertionFailure()
58 | return
59 | }
60 | self.userDefaults.removeObject(forKey: self.persistenceKey)
61 | self.mutableObservableSessionState.value = .stopped
62 | self.mutableObservableSessionState.value = .readyToStart
63 | }
64 |
65 | private func handle(_ user: User?) {
66 | if let user = user {
67 | let session = Session(userId: user.id, username: user.username)
68 | self.mutableObservableSessionState.value = .started(session)
69 | self.userDefaults.set(try? PropertyListEncoder().encode(session), forKey: self.persistenceKey)
70 | } else {
71 | self.mutableObservableSessionState.value = .failed(NSError(domain: "", code: 0, userInfo: nil))
72 | self.mutableObservableSessionState.value = .readyToStart
73 | }
74 | }
75 |
76 | private func attemptToRestoreSession() {
77 | guard let encodedSession = userDefaults.object(forKey: self.persistenceKey) as? Data else { return }
78 | guard let session = try? PropertyListDecoder().decode(Session.self, from: encodedSession) else {
79 | assertionFailure()
80 | return
81 | }
82 | self.mutableObservableSessionState.value = .started(session)
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/Features/Features.xcodeproj/xcshareddata/xcschemes/Features.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
31 |
32 |
34 |
40 |
41 |
42 |
43 |
44 |
50 |
51 |
52 |
53 |
54 |
55 |
66 |
67 |
73 |
74 |
75 |
76 |
77 |
78 |
84 |
85 |
91 |
92 |
93 |
94 |
96 |
97 |
100 |
101 |
102 |
--------------------------------------------------------------------------------
/Features/Resources/TestsInfo.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | BNDL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 |
22 |
23 |
--------------------------------------------------------------------------------
/Features/Sources/Comments/CommentsScreenFeature.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CommentsScreenFeature.swift
3 | // BlogApp
4 | //
5 | // Created by Bohdan Orlov on 26/03/2018.
6 | // Copyright © 2018 Bohdan Orlov. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import UIKit
11 | import Domain
12 | import UI
13 |
14 | public class CommentsScreenFeature {
15 |
16 | @discardableResult
17 | public init(commentsViewController: StringsRenderingViewController,
18 | viewControllerPresenting: ViewControllerPresenting,
19 | userId: Int,
20 | commentsRepository: UserCommentsRepository) {
21 |
22 | commentsViewController.retain(self)
23 | commentsRepository.comments(userId: userId) { [weak commentsViewController] comments in
24 | commentsViewController?.data = .init(strings: comments.map{ $0.body })
25 | }
26 |
27 | viewControllerPresenting.present(viewController: commentsViewController, completion: { })
28 | }
29 |
30 | }
31 |
--------------------------------------------------------------------------------
/Features/Sources/Login/LoginScreenFeature.swift:
--------------------------------------------------------------------------------
1 | // Created by Bohdan Orlov on 27/02/2018.
2 | // Copyright © 2018 Bohdan Orlov. All rights reserved.
3 | //
4 |
5 | import Foundation
6 | import UIKit
7 | import Core
8 | import Domain
9 | import UI
10 |
11 | public class LoginScreenFeature {
12 | private weak var loginViewController: LoginViewControlling?
13 | private let viewControllerPresenter: ViewControllerPresenting
14 | private let sessionService: SessionServiceProtocol
15 | private let didLogin: (Session) -> Void
16 | private var observer: AnyObject?
17 |
18 | @discardableResult
19 | public init(loginViewController: LoginViewControlling,
20 | viewControllerPresenter: ViewControllerPresenting,
21 | sessionService: SessionServiceProtocol,
22 | didLogin: @escaping (Session) -> Void) {
23 | self.viewControllerPresenter = viewControllerPresenter
24 | self.sessionService = sessionService
25 | self.didLogin = didLogin
26 | self.loginViewController = loginViewController
27 | self.start()
28 | }
29 |
30 | private func start() {
31 | self.loginViewController?.retain(self)
32 | self.loginViewController?.didTapButton = { [weak self] credentials in
33 | self?.sessionService.startSession(username: credentials.username, password: credentials.password)
34 | }
35 | self.observer = self.sessionService.observableSessionState.observeAndCall(weakify(self, type(of: self).updateUIState))
36 | }
37 |
38 | private func updateUIState(_ session:SessionState) {
39 | guard let loginViewController = self.loginViewController else { return }
40 | switch session {
41 | case .readyToStart:
42 | self.presentLoginScreenIfNeeded()
43 | fallthrough
44 | case .failed(_): fallthrough
45 | case .stopped:
46 | loginViewController.isUserInteractionEnabled = true
47 | loginViewController.showsActivityIndicator = false
48 | case .starting:
49 | loginViewController.isUserInteractionEnabled = false
50 | loginViewController.showsActivityIndicator = true
51 | case .started(let session):
52 | self.presentLoginScreenIfNeeded()
53 | self.didLogin(session)
54 | loginViewController.showsActivityIndicator = false
55 | }
56 | }
57 |
58 | private func presentLoginScreenIfNeeded() {
59 | guard let loginViewController = self.loginViewController, loginViewController.presentingViewController == nil else { return }
60 | self.viewControllerPresenter.present(viewController: loginViewController, completion: { })
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/Features/Sources/Login/LoginScreenFeatureTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LoginScreenFeatureTests.swift
3 | // BlogAppTests
4 | //
5 | // Created by Bohdan Orlov on 05/04/2018.
6 | // Copyright © 2018 Bohdan Orlov. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | import Foundation
11 | import UIKit
12 | import Core
13 | import CoreTestsSupport
14 | import Domain
15 | import DomainTestsSupport
16 | import UI
17 | import UITestsSupport
18 | import Features
19 |
20 | class LoginScreenFeatureTests: XCTestCase {
21 |
22 | var fakeLoginViewController: FakeLoginViewController!
23 | var fakeViewControllerPresenter: FakeViewControllerPresenter!
24 | var fakeSessionService: FakeSessionService!
25 | var fakeDidLogin: ((Session) -> Void)!
26 |
27 | override func setUp() {
28 | super.setUp()
29 | fakeLoginViewController = FakeLoginViewController()
30 | fakeViewControllerPresenter = FakeViewControllerPresenter()
31 | fakeSessionService = FakeSessionService()
32 | fakeDidLogin = { _ in }
33 | }
34 |
35 | override func tearDown() {
36 | fakeLoginViewController = nil
37 | fakeViewControllerPresenter = nil
38 | fakeSessionService = nil
39 | fakeDidLogin = nil
40 | super.tearDown()
41 | }
42 |
43 | func test_GivenSessionReadyToStart_WhenFeatureLaunched_ThenPresentIsCalled() {
44 | self.fakeSessionService.stubbedSessionState.value = .readyToStart
45 |
46 | launchFeature()
47 |
48 | XCTAssert(fakeViewControllerPresenter.presentCalled)
49 | }
50 |
51 | func test_GivenSessionReadyToStart_WhenFeatureLaunched_ThenLoginViewControllerIsPresented() {
52 | self.fakeSessionService.stubbedSessionState.value = .readyToStart
53 |
54 | launchFeature()
55 |
56 | XCTAssertEqual(fakeViewControllerPresenter.presentedViewController, fakeLoginViewController)
57 | }
58 |
59 |
60 | func test_GivenSessionStarted_WhenFeatureLaunched_ThenPresentIsCalled() {
61 | self.fakeSessionService.stubbedSessionState.value = .started(fakeSession)
62 |
63 | launchFeature()
64 |
65 | XCTAssert(fakeViewControllerPresenter.presentCalled)
66 | }
67 |
68 | func test_GivenLaunchedFeature_WhenSessionStarted_ThenPresentIsNotCalledAgain() {
69 | launchFeature()
70 | fakeLoginViewController.stubbedPresentingViewController = UIViewController()
71 |
72 | self.fakeSessionService.stubbedSessionState.value = .started(fakeSession)
73 |
74 | XCTAssertEqual(fakeViewControllerPresenter.presentCounter, 1)
75 | }
76 |
77 | func test_GivenLaunchedFeature_WhenDidTapButton_ThenStartSessionCalled() {
78 | launchFeature()
79 |
80 | self.fakeLoginViewController.setDidTapButton(fakeCredentials)
81 |
82 | XCTAssert(fakeSessionService.startSessionCalled)
83 | }
84 |
85 |
86 | //MARK: - Tests support
87 | func launchFeature() {
88 | LoginScreenFeature(loginViewController: fakeLoginViewController,
89 | viewControllerPresenter: fakeViewControllerPresenter,
90 | sessionService: fakeSessionService,
91 | didLogin: fakeDidLogin)
92 | }
93 |
94 | let fakeSession = Session(userId: 7, username: "John")
95 | let fakeCredentials = SessionCredentials(username: "John", password: "")
96 | }
97 |
98 | final class FakeLoginViewController: UIViewController, LoginRendering {
99 | var setIsUserInteractionEnabled: Bool!
100 | var setShowsActivityIndicator: Bool!
101 | var stubbedIsUserInteractionEnabled: Bool!
102 | var stubbedShowsActivityIndicator: Bool!
103 | var stubbedPresentingViewController: UIViewController!
104 |
105 | override var presentingViewController: UIViewController? {
106 | return stubbedPresentingViewController
107 | }
108 |
109 | var isUserInteractionEnabled: Bool {
110 | set {
111 | setIsUserInteractionEnabled = newValue
112 | }
113 | get {
114 | return stubbedIsUserInteractionEnabled
115 | }
116 | }
117 |
118 | var showsActivityIndicator: Bool {
119 | set {
120 | setShowsActivityIndicator = newValue
121 | }
122 | get {
123 | return stubbedShowsActivityIndicator
124 | }
125 | }
126 |
127 | var setDidTapButton: ((SessionCredentials) -> Void)!
128 | var didTapButton: ((SessionCredentials) -> Void)? {
129 | get {
130 | return setDidTapButton
131 | }
132 | set {
133 | setDidTapButton = newValue
134 | }
135 | }
136 |
137 | }
138 |
139 | final class FakeViewControllerPresenter: ViewControllerPresenting {
140 | var presentCalled = false
141 | var dismissCalled = false
142 | var presentCounter = 0
143 | var dismissCounter = 0
144 | var presentedViewController: UIViewController!
145 | var dismissedViewController: UIViewController!
146 |
147 | func present(viewController: UIViewController, completion: @escaping () -> Void) {
148 | presentedViewController = viewController
149 | presentCalled = true
150 | presentCounter += 1
151 | }
152 |
153 | func dismiss(viewController: UIViewController, completion: @escaping () -> Void) {
154 | dismissedViewController = viewController
155 | dismissCalled = true
156 | dismissCounter = 0
157 | }
158 |
159 |
160 | }
161 |
162 |
163 |
--------------------------------------------------------------------------------
/Features/Sources/Login/LoginViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LoginViewController.swift
3 | // BlogApp
4 | //
5 | // Created by Bohdan Orlov on 02/04/2018.
6 | // Copyright © 2018 Bohdan Orlov. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import UIKit
11 | import Layoutless
12 |
13 | public struct SessionCredentials {
14 | public let username: String
15 | public let password: String
16 | }
17 |
18 | public protocol LoginRendering: AnyObject {
19 | var isUserInteractionEnabled: Bool { get set }
20 | var showsActivityIndicator: Bool { get set }
21 | var didTapButton: ((SessionCredentials) -> Void)? { get set }
22 | }
23 |
24 | public typealias LoginViewControlling = LoginRendering & UIViewController
25 |
26 | public class LoginViewController: ViewController, LoginRendering {
27 |
28 | public var isUserInteractionEnabled: Bool {
29 | get {
30 | return view.isUserInteractionEnabled
31 | }
32 | set {
33 | view.isUserInteractionEnabled = newValue
34 | }
35 | }
36 |
37 | public var showsActivityIndicator: Bool {
38 | get {
39 | return activityIndicator.isAnimating
40 | }
41 | set {
42 | if newValue {
43 | activityIndicator.startAnimating()
44 | } else {
45 | activityIndicator.stopAnimating()
46 | }
47 | }
48 | }
49 |
50 | public var didTapButton: ((SessionCredentials) -> Void)?
51 |
52 | public override func viewDidLoad() {
53 | super.viewDidLoad()
54 | self.view.translatesAutoresizingMaskIntoConstraints = false
55 | self.view.backgroundColor = .white
56 | self.view.addSubview(self.activityIndicator)
57 | self.button.rightAnchor.constraint(equalTo: self.activityIndicator.leftAnchor, constant: 40).isActive = true
58 | self.button.centerYAnchor.constraint(equalTo: self.activityIndicator.centerYAnchor).isActive = true
59 |
60 | }
61 |
62 | public override var subviewsLayout: AnyLayout {
63 | return stack(.vertical, spacing: 20, alignment: .fill)(
64 | usernameField,
65 | passwordField,
66 | button
67 | ).centeringInParent().sizing(toWidth: 200)
68 | }
69 |
70 | private lazy var usernameField = UITextField()
71 | .set(\.placeholder, "Username (Samantha)")
72 | .set(\.borderStyle, .line)
73 |
74 | private let passwordField = UITextField()
75 | .set(\.placeholder, "Password")
76 | .set(\.borderStyle, .line)
77 |
78 | private lazy var button: UIButton = {
79 | let button = UIButton(type: .system)
80 | button.setTitle("Login", for: .normal)
81 | button.addTarget(self, action: #selector(onButtonTap), for: .touchUpInside)
82 | return button
83 | }()
84 |
85 | private lazy var activityIndicator: UIActivityIndicatorView = {
86 | let indicator = UIActivityIndicatorView(activityIndicatorStyle: .gray)
87 | indicator.translatesAutoresizingMaskIntoConstraints = false
88 | return indicator
89 | }()
90 |
91 | @objc
92 | private func onButtonTap() {
93 | self.didTapButton?(SessionCredentials(username: self.usernameField.text ?? "", password: self.passwordField.text ?? ""))
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/Features/Sources/Logout/LogoutButtonFeature.swift:
--------------------------------------------------------------------------------
1 | // Created by Bohdan Orlov on 01/03/2018.
2 | // Copyright © 2018 Bohdan Orlov. All rights reserved.
3 | //
4 |
5 | import Foundation
6 | import UIKit
7 | import Core
8 | import Domain
9 | import UI
10 |
11 | public class LogoutButtonFeature {
12 | private let viewPresenter: ViewPresenting
13 | private let sessionService: SessionServiceProtocol
14 | private let didLogout: () -> Void
15 | private weak var button: UIButton?
16 |
17 | @discardableResult
18 | public init(viewPresenter: ViewPresenting, sessionService: SessionServiceProtocol, didLogout: @escaping () -> Void) {
19 | self.viewPresenter = viewPresenter
20 | self.sessionService = sessionService
21 | self.didLogout = didLogout
22 | self.start()
23 | }
24 |
25 | private func start() {
26 | let button = self.makeButton()
27 | self.button = button
28 | self.button?.retain(self)
29 | self.observer = self.sessionService.observableSessionState.observeAndCall(weakify(self, type(of: self).updateUIState))
30 | }
31 |
32 | private func makeButton() -> UIButton {
33 | let button = UIButton(type: .system)
34 | button.translatesAutoresizingMaskIntoConstraints = false
35 | button.heightAnchor.constraint(equalToConstant: 45).isActive = true
36 | button.setTitle("Logout", for: .normal)
37 | button.add(for: .touchUpInside) { [weak self] in
38 | self?.sessionService.stopSession()
39 | }
40 | button.setTitleColor(.black, for: .normal)
41 | return button
42 | }
43 |
44 | private func updateUIState(_ session:SessionState) {
45 | guard let button = self.button else {
46 | return
47 | }
48 | switch session {
49 | case .started(_):
50 | self.viewPresenter.present(view: button)
51 | button.isEnabled = true
52 | case .stopped:
53 | self.viewPresenter.dismiss(view: button)
54 | self.didLogout()
55 | fallthrough
56 | case .readyToStart: fallthrough
57 | case .starting: fallthrough
58 | case .failed(_):
59 | button.isEnabled = false
60 | }
61 | }
62 |
63 | private var observer: AnyObject?
64 | }
65 |
--------------------------------------------------------------------------------
/Features/Sources/Posts/PostsScreenFeature.swift:
--------------------------------------------------------------------------------
1 | // Created by Bohdan Orlov on 01/03/2018.
2 | // Copyright © 2018 Bohdan Orlov. All rights reserved.
3 | //
4 |
5 | import Foundation
6 | import UIKit
7 | import Domain
8 | import UI
9 |
10 | public typealias PostsRenderingViewController = UIViewController & StringsRendering & SubviewContainerProviding
11 |
12 | public class PostsScreenFeature {
13 |
14 | @discardableResult
15 | public init(postsViewController: PostsRenderingViewController,
16 | viewControllerPresenting: ViewControllerPresenting,
17 | userId: Int,
18 | postsRepository: PostsProviding,
19 | didPrepareButtonContainer: @escaping (UIView) -> Void) {
20 |
21 | postsViewController.retain(self)
22 | postsViewController.title = "User ID: \(userId)"
23 | postsViewController.didPrepareSubviewsContainer = didPrepareButtonContainer
24 | postsRepository.posts(userId: userId) { [weak postsViewController] posts in
25 | postsViewController?.data = .init(strings: posts.map { $0.body })
26 | }
27 |
28 | viewControllerPresenting.present(viewController: postsViewController, completion: { })
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/Features/Sources/Posts/PostsViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PostsViewController.swift
3 | // BlogApp
4 | //
5 | // Created by Bohdan Orlov on 02/04/2018.
6 | // Copyright © 2018 Bohdan Orlov. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import UIKit
11 | import Core
12 | import UI
13 | import Layoutless
14 |
15 | public protocol SubviewContainerProviding: AnyObject {
16 | var didPrepareSubviewsContainer: ((UIView) -> Void)? { get set }
17 | }
18 |
19 | public final class PostsViewController: ViewController, StringsRendering, SubviewContainerProviding {
20 | public var data: StringsData {
21 | get {
22 | return self.stringsTableViewController.data
23 | }
24 | set {
25 | self.stringsTableViewController.data = newValue
26 | }
27 | }
28 |
29 | public var didPrepareSubviewsContainer: ((UIView) -> Void)?
30 |
31 | public override func viewDidLoad() {
32 | super.viewDidLoad()
33 | self.view.backgroundColor = .white
34 | }
35 |
36 | public override var subviewsLayout: AnyLayout {
37 | return stack(.vertical)(
38 | self.stringsTableViewController.view,
39 | self.buttonContainer
40 | ).fillingParent(relativeToSafeArea: true)
41 | }
42 |
43 | public override func defineLayout() {
44 | super.defineLayout()
45 | self.stringsTableViewController.didMove(toParentViewController: self)
46 | self.didPrepareSubviewsContainer?(self.buttonContainer)
47 | }
48 |
49 | private let buttonContainer = UIView()
50 | private let stringsTableViewController = StringsTableViewController()
51 | }
52 |
--------------------------------------------------------------------------------
/Features/Sources/Presentation/NavigationBarFeature.swift:
--------------------------------------------------------------------------------
1 | // Created by Bohdan Orlov on 12/03/2018.
2 | // Copyright © 2018 Bohdan Orlov. All rights reserved.
3 | //
4 |
5 | import Foundation
6 | import UIKit
7 | import UI
8 |
9 | public class NavigationBarFeature {
10 |
11 | @discardableResult
12 | public init(viewControllerPresenter: ViewControllerPresenting,
13 | navigationController: UINavigationController,
14 | didShowNavigationBar: @escaping (UIViewController) -> Void) {
15 | didShowNavigationBar(navigationController)
16 | viewControllerPresenter.present(viewController: navigationController, completion: {
17 | })
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/Features/Sources/Presentation/TabBarFeature.swift:
--------------------------------------------------------------------------------
1 | // Created by Bohdan Orlov on 02/03/2018.
2 | // Copyright © 2018 Bohdan Orlov. All rights reserved.
3 | //
4 |
5 | import Foundation
6 | import UIKit
7 | import UI
8 |
9 | public protocol TabControllersContainer {
10 | static var titles: [String] { get }
11 | init(viewControllers: [UIViewController])
12 | }
13 |
14 | public class TabBarFeature {
15 |
16 | @discardableResult
17 | public init(tabs: T.Type,
18 | tabBarController: UITabBarController,
19 | viewControllerPresenting: ViewControllerPresenting,
20 | didShowTabBar: @escaping (T) -> Void) {
21 | let rootViewControllers: [UIViewController] = tabs.titles.map {
22 | let viewController = RootViewController()
23 | viewController.title = $0
24 | return viewController
25 | }
26 | tabBarController.viewControllers = rootViewControllers
27 | didShowTabBar(T.init(viewControllers: rootViewControllers))
28 | viewControllerPresenting.present(viewController: tabBarController, completion: {})
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/Features/Sources/Presentation/WindowFeature.swift:
--------------------------------------------------------------------------------
1 | // Created by Bohdan Orlov on 01/03/2018.
2 | // Copyright © 2018 Bohdan Orlov. All rights reserved.
3 | //
4 |
5 | import Foundation
6 | import UIKit
7 | import UI
8 |
9 | public class WindowFeature {
10 |
11 | @discardableResult
12 | public init(windowFrame: CGRect, windowOwner: UIWindowOwner, didSetupWindow: @escaping (UIViewController) -> Void) {
13 | let windowKey = windowFrame.debugDescription
14 | let window = UIWindow(frame: windowFrame)
15 | let oldWindow = windowOwner.windows[windowKey]
16 | let rootViewController = RootViewController()
17 | rootViewController.didAppear = {
18 | if let oldWindow = oldWindow {
19 | if let viewController = oldWindow.rootViewController {
20 | viewController.dismiss(animated: false, completion: nil)
21 | }
22 | oldWindow.rootViewController = nil
23 | }
24 | didSetupWindow(rootViewController)
25 | }
26 | window.rootViewController = rootViewController
27 | window.makeKeyAndVisible()
28 | windowOwner.windows[windowKey] = window
29 |
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/Features/Sources/Presentation/WindowFrameFeature.swift:
--------------------------------------------------------------------------------
1 | // Created by Bohdan Orlov on 25/03/2018.
2 | // Copyright © 2018 Bohdan Orlov. All rights reserved.
3 | //
4 |
5 | import Foundation
6 | import UIKit
7 |
8 | public class WindowFrameFeature {
9 |
10 | @discardableResult
11 | public init(screenBounds: CGRect, splitScreen: Bool, didDefineScreenFrames: (CGRect) -> Void) {
12 | guard splitScreen else {
13 | didDefineScreenFrames(screenBounds)
14 | return
15 | }
16 | let gap: CGFloat = 10
17 | let screenRect = screenBounds
18 | var (top, bottom) = screenRect.divided(atDistance: screenRect.midY, from: CGRectEdge.minYEdge)
19 | top.size.height -= gap
20 | bottom.size.height -= gap
21 | bottom.origin.y += gap
22 | [top, bottom].forEach(didDefineScreenFrames)
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/Features/Sources/Push notifications/PushNotificationButtonFeature.swift:
--------------------------------------------------------------------------------
1 | // Created by Bohdan Orlov on 03/03/2018.
2 | // Copyright © 2018 Bohdan Orlov. All rights reserved.
3 | //
4 |
5 | import Foundation
6 | import UIKit
7 | import Domain
8 |
9 |
10 | public class PushNotificationButtonFeature {
11 |
12 | @discardableResult
13 | public init(rootViewController: UIViewController, pushNotificationService: PushNotificationServiceProtocol) {
14 | guard let window = rootViewController.view.window else {
15 | assertionFailure()
16 | return
17 | }
18 | let button = UIButton(type: .system)
19 | button.setTitle("Fake Restart Push", for: .normal)
20 | button.translatesAutoresizingMaskIntoConstraints = false
21 | window.addSubview(button)
22 | button.leftAnchor.constraint(equalTo: window.leftAnchor, constant: 20).isActive = true
23 | button.topAnchor.constraint(equalTo: window.topAnchor, constant: 20).isActive = true
24 | button.add(for: .touchUpInside) {
25 | pushNotificationService.push(notification: PushNotification.restart)
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/Features/Sources/Push notifications/RestartPushNotificationFeature.swift:
--------------------------------------------------------------------------------
1 | // Created by Bohdan Orlov on 03/03/2018.
2 | // Copyright © 2018 Bohdan Orlov. All rights reserved.
3 | //
4 |
5 | import Foundation
6 | import UIKit
7 | import Core
8 | import Domain
9 |
10 | public class RestartPushNotificationFeature {
11 |
12 | @discardableResult
13 | public init(pushNotificationService: PushNotificationServiceProtocol, didReceiveRestartRequest: @escaping () -> Void) {
14 | observer = pushNotificationService.lastReceivedPush.observe { _, notification in
15 | if case .restart? = notification {
16 | didReceiveRestartRequest()
17 | }
18 | }
19 | pushNotificationService.retain(self)
20 | }
21 |
22 | private let observer: AnyObject
23 | }
24 |
--------------------------------------------------------------------------------
/Podfile:
--------------------------------------------------------------------------------
1 | platform :ios, '11.2'
2 | workspace 'BlogApp.xcworkspace'
3 |
4 | target 'BlogApp' do
5 | use_frameworks!
6 | project 'BlogApp/BlogApp.xcodeproj'
7 |
8 | pod 'Layoutless'
9 |
10 | target 'BlogAppTests' do
11 | inherit! :search_paths
12 | pod 'Layoutless'
13 | end
14 | end
15 |
16 | target 'Features' do
17 | use_frameworks!
18 | project 'Features/Features.xcodeproj'
19 | pod 'Layoutless'
20 |
21 | target 'FeaturesTests' do
22 | inherit! :search_paths
23 | pod 'Layoutless'
24 | end
25 | end
26 |
27 | target 'UI' do
28 | use_frameworks!
29 | project 'UI/UI.xcodeproj'
30 | pod 'Layoutless'
31 |
32 | target 'UITestsSupport' do
33 | inherit! :search_paths
34 | end
35 |
36 | target 'UITests' do
37 | inherit! :search_paths
38 | end
39 | end
40 |
41 | #target 'Domain' do
42 | # project 'Domain/Domain.xcodeproj'
43 | #
44 | # target 'DomainTestsSupport' do
45 | # inherit! :search_paths
46 | # end
47 | #
48 | # target 'DomainTests' do
49 | # inherit! :search_paths
50 | # end
51 | #end
52 |
53 | #target 'Core' do
54 | # project 'Core/Core.xcodeproj'
55 | #
56 | # target 'CoreTestsSupport' do
57 | # inherit! :search_paths
58 | # end
59 | #
60 | # target 'CoreTests' do
61 | # inherit! :search_paths
62 | # end
63 | #end
64 |
--------------------------------------------------------------------------------
/Podfile.lock:
--------------------------------------------------------------------------------
1 | PODS:
2 | - Layoutless (0.1.2)
3 |
4 | DEPENDENCIES:
5 | - Layoutless
6 |
7 | SPEC CHECKSUMS:
8 | Layoutless: 9e5bc51aacced232401af71c70644c069d119ec5
9 |
10 | PODFILE CHECKSUM: dcb85b7e4ac2972148d60b5628a51163922d3ea2
11 |
12 | COCOAPODS: 1.4.0
13 |
--------------------------------------------------------------------------------
/Pods/Layoutless/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Srdan Rasic (@srdanrasic)
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6 |
7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8 |
9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------------
/Pods/Layoutless/Sources/Layout/Anchorable.swift:
--------------------------------------------------------------------------------
1 | //
2 | // The MIT License (MIT)
3 | //
4 | // Copyright (c) 2018 Srdan Rasic (@srdanrasic)
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to deal
8 | // in the Software without restriction, including without limitation the rights
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | // THE SOFTWARE.
23 | //
24 |
25 | import UIKit
26 |
27 | /// A type whose instances provide basic layout anchors.
28 | public protocol Anchorable {
29 |
30 | var leadingAnchor: NSLayoutXAxisAnchor { get }
31 | var trailingAnchor: NSLayoutXAxisAnchor { get }
32 | var leftAnchor: NSLayoutXAxisAnchor { get }
33 | var rightAnchor: NSLayoutXAxisAnchor { get }
34 | var topAnchor: NSLayoutYAxisAnchor { get }
35 | var bottomAnchor: NSLayoutYAxisAnchor { get }
36 | var widthAnchor: NSLayoutDimension { get }
37 | var heightAnchor: NSLayoutDimension { get }
38 | var centerXAnchor: NSLayoutXAxisAnchor { get }
39 | var centerYAnchor: NSLayoutYAxisAnchor { get }
40 | }
41 |
--------------------------------------------------------------------------------
/Pods/Layoutless/Sources/Layout/ChildNode.swift:
--------------------------------------------------------------------------------
1 | //
2 | // The MIT License (MIT)
3 | //
4 | // Copyright (c) 2018 Srdan Rasic (@srdanrasic)
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to deal
8 | // in the Software without restriction, including without limitation the rights
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | // THE SOFTWARE.
23 | //
24 |
25 | /// A decorator over child layout node that provides a way for the child to layout itself
26 | /// with the respect to the parent it will eventually be added to.
27 | public class ChildNode: Layoutless.LayoutNode, Anchorable where LayoutNode: Anchorable {
28 |
29 | public let child: LayoutNode
30 | public let layout: (UIView) -> Void
31 |
32 | public init(_ child: LayoutNode, layout: @escaping (UIView, LayoutNode) -> Void) {
33 | self.child = child
34 | self.layout = { container in
35 | child.layout(in: container)
36 | layout(container, child)
37 | }
38 | }
39 |
40 | public func layout(in container: UIView) {
41 | return layout(container)
42 | }
43 |
44 | public var leadingAnchor: NSLayoutXAxisAnchor { return child.leadingAnchor }
45 | public var trailingAnchor: NSLayoutXAxisAnchor { return child.trailingAnchor }
46 | public var leftAnchor: NSLayoutXAxisAnchor { return child.leftAnchor }
47 | public var rightAnchor: NSLayoutXAxisAnchor { return child.rightAnchor }
48 | public var topAnchor: NSLayoutYAxisAnchor { return child.topAnchor }
49 | public var bottomAnchor: NSLayoutYAxisAnchor { return child.bottomAnchor }
50 | public var widthAnchor: NSLayoutDimension { return child.widthAnchor }
51 | public var heightAnchor: NSLayoutDimension { return child.heightAnchor }
52 | public var centerXAnchor: NSLayoutXAxisAnchor { return child.centerXAnchor }
53 | public var centerYAnchor: NSLayoutYAxisAnchor { return child.centerYAnchor }
54 | }
55 |
--------------------------------------------------------------------------------
/Pods/Layoutless/Sources/Layout/Layout.swift:
--------------------------------------------------------------------------------
1 | //
2 | // The MIT License (MIT)
3 | //
4 | // Copyright (c) 2018 Srdan Rasic (@srdanrasic)
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to deal
8 | // in the Software without restriction, including without limitation the rights
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | // THE SOFTWARE.
23 | //
24 |
25 | /// A type that represents layout calculation as a closure.
26 | public struct Layout: LayoutProtocol {
27 |
28 | private let _generate: () -> LayoutNode
29 |
30 | public init(_ generate: @escaping () -> LayoutNode) {
31 | _generate = generate
32 | }
33 |
34 | public static func just(_ node: LayoutNode) -> Layout {
35 | return Layout { node }
36 | }
37 |
38 | public func makeLayoutNode() -> LayoutNode {
39 | return _generate()
40 | }
41 | }
42 |
43 | /// A layout of nothing.
44 | public func EmptyLayout() -> Layout {
45 | return Layout.just(EmptyLayoutNode())
46 | }
47 |
48 | /// A node that does not have any content.
49 | public struct EmptyLayoutNode: LayoutNode {
50 | public init() {}
51 | public func layout(in container: UIView) {}
52 | }
53 |
--------------------------------------------------------------------------------
/Pods/Layoutless/Sources/Layout/LayoutNode.swift:
--------------------------------------------------------------------------------
1 | //
2 | // The MIT License (MIT)
3 | //
4 | // Copyright (c) 2018 Srdan Rasic (@srdanrasic)
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to deal
8 | // in the Software without restriction, including without limitation the rights
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | // THE SOFTWARE.
23 | //
24 |
25 | import UIKit
26 |
27 | /// A type that represents a node in the layout tree.
28 | /// A layout node is usually a view, but can also be an abstract type representing some layout calculation.
29 | public protocol LayoutNode {
30 |
31 | /// Layouts the receiver within the container.
32 | func layout(in container: UIView)
33 | }
34 |
--------------------------------------------------------------------------------
/Pods/Layoutless/Sources/Layout/LayoutProtocol.swift:
--------------------------------------------------------------------------------
1 | //
2 | // The MIT License (MIT)
3 | //
4 | // Copyright (c) 2018 Srdan Rasic (@srdanrasic)
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to deal
8 | // in the Software without restriction, including without limitation the rights
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | // THE SOFTWARE.
23 | //
24 |
25 | import Foundation
26 |
27 | /// A type-erased `LayoutProtocol`.
28 | public protocol AnyLayout: LayoutNode {
29 |
30 | /// Generates the layout and returns the layout's root node.
31 | func makeAnyLayoutNode() -> LayoutNode
32 | }
33 |
34 | /// A type that encapsulates layout, basically anything that can make a layout node.
35 | public protocol LayoutProtocol: AnyLayout {
36 | associatedtype LayoutNode: Layoutless.LayoutNode
37 |
38 | /// Generates the layout and returns the layout's root node.
39 | func makeLayoutNode() -> LayoutNode
40 | }
41 |
42 | extension LayoutProtocol {
43 |
44 | public func makeAnyLayoutNode() -> Layoutless.LayoutNode {
45 | return makeLayoutNode()
46 | }
47 | }
48 |
49 | extension AnyLayout {
50 |
51 | public func layout(in container: UIView) {
52 | makeAnyLayoutNode().layout(in: container)
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/Pods/Layoutless/Sources/Layout/Layoutless+UIKit.swift:
--------------------------------------------------------------------------------
1 | //
2 | // The MIT License (MIT)
3 | //
4 | // Copyright (c) 2018 Srdan Rasic (@srdanrasic)
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to deal
8 | // in the Software without restriction, including without limitation the rights
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | // THE SOFTWARE.
23 | //
24 |
25 | import UIKit
26 |
27 | // MARK: Protocol conformances
28 |
29 | extension UIView: Anchorable {
30 |
31 | // Conforms automatically
32 | }
33 |
34 | extension UILayoutGuide: Anchorable {
35 |
36 | // Conforms automatically
37 | }
38 |
39 | extension UIView: LayoutProtocol {
40 |
41 | /// UIView's layout node is the view itself. Returns `self`.
42 | public func makeLayoutNode() -> UIView {
43 | return self
44 | }
45 | }
46 |
47 | extension UIView: LayoutNode {
48 |
49 | /// Makes the receiver a subview of the container.
50 | public func layout(in container: UIView) {
51 | translatesAutoresizingMaskIntoConstraints = false
52 | if let container = container as? UIStackView {
53 | container.addArrangedSubview(self)
54 | } else {
55 | container.addSubview(self)
56 | }
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/Pods/Layoutless/Sources/Layout/Length.swift:
--------------------------------------------------------------------------------
1 | //
2 | // The MIT License (MIT)
3 | //
4 | // Copyright (c) 2018 Srdan Rasic (@srdanrasic)
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to deal
8 | // in the Software without restriction, including without limitation the rights
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | // THE SOFTWARE.
23 | //
24 |
25 | import UIKit
26 |
27 | @available(*, deprecated, renamed: "Length")
28 | public typealias Dimension = Length
29 |
30 | /// A type that represents a spatial dimension like width, height, inset, offset, etc.
31 | /// Expressible by float or integer literal.
32 | public enum Length: ExpressibleByFloatLiteral, ExpressibleByIntegerLiteral {
33 |
34 | case exactly(CGFloat)
35 | case greaterThanOrEqualTo(CGFloat)
36 | case lessThanOrEqualTo(CGFloat)
37 |
38 | public init(floatLiteral value: Float) {
39 | self = .exactly(CGFloat(value))
40 | }
41 |
42 | public init(integerLiteral value: Int) {
43 | self = .exactly(CGFloat(value))
44 | }
45 |
46 | public var cgFloatValue: CGFloat {
47 | switch self {
48 | case .exactly(let value):
49 | return value
50 | case .greaterThanOrEqualTo(let value):
51 | return value
52 | case .lessThanOrEqualTo(let value):
53 | return value
54 | }
55 | }
56 | }
57 |
58 | extension Length {
59 |
60 | func constrain(_ lhs: NSLayoutAnchor, to rhs: NSLayoutAnchor) -> NSLayoutConstraint {
61 | switch self {
62 | case .exactly(let value):
63 | return lhs.constraint(equalTo: rhs, constant: CGFloat(value))
64 | case .lessThanOrEqualTo(let value):
65 | return lhs.constraint(lessThanOrEqualTo: rhs, constant: CGFloat(value))
66 | case .greaterThanOrEqualTo(let value):
67 | return lhs.constraint(greaterThanOrEqualTo: rhs, constant: CGFloat(value))
68 | }
69 | }
70 |
71 | func constrainToConstant(_ lhs: NSLayoutDimension) -> NSLayoutConstraint {
72 | switch self {
73 | case .exactly(let value):
74 | return lhs.constraint(equalToConstant: CGFloat(value))
75 | case .lessThanOrEqualTo(let value):
76 | return lhs.constraint(lessThanOrEqualToConstant: CGFloat(value))
77 | case .greaterThanOrEqualTo(let value):
78 | return lhs.constraint(greaterThanOrEqualToConstant: CGFloat(value))
79 | }
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/Pods/Layoutless/Sources/Style/Style.swift:
--------------------------------------------------------------------------------
1 | //
2 | // The MIT License (MIT)
3 | //
4 | // Copyright (c) 2018 Srdan Rasic (@srdanrasic)
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to deal
8 | // in the Software without restriction, including without limitation the rights
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | // THE SOFTWARE.
23 | //
24 |
25 | import UIKit
26 |
27 | /// Represents view style with a closure that configures the view.
28 | public struct Style {
29 |
30 | public let style: (View) -> Void
31 |
32 | public init(style: @escaping (View) -> Void) {
33 | self.style = style
34 | }
35 |
36 | /// Applies self to the view.
37 | public func apply(to view: View) {
38 | style(view)
39 | }
40 |
41 | /// Style that does nothing (keeps the default/native style).
42 | public static var native: Style {
43 | return Style { _ in }
44 | }
45 | }
46 |
47 | extension UIView {
48 |
49 | /// For example: `let nameLabel = UILabel(style: Stylesheet.Profile.name)`.
50 | public convenience init(style: Style) {
51 | self.init(frame: .zero)
52 | apply(style)
53 | }
54 |
55 | /// Applies the given style to self.
56 | public func apply(_ style: Style) {
57 | guard let view = self as? V else {
58 | print("💥 Could not apply style for \(V.self) to \(type(of: self))")
59 | return
60 | }
61 | style.apply(to: view)
62 | }
63 |
64 | /// Returns self with the style applied. For example: `let nameLabel = UILabel().styled(with: Stylesheet.Profile.name)`.
65 | public func styled(with style: Style) -> Self {
66 | guard let view = self as? V else {
67 | print("💥 Could not apply style for \(V.self) to \(type(of: self))")
68 | return self
69 | }
70 | style.apply(to: view)
71 | return self
72 | }
73 | }
74 |
75 | extension Style {
76 |
77 | /// Marges two styles together.
78 | public func adding(_ other: Style) -> Style {
79 | return Style {
80 | self.apply(to: $0)
81 | other.apply(to: $0 as! V)
82 | }
83 | }
84 |
85 | /// Returns current style modified by the given closure.
86 | public func modifying(_ other: @escaping (View) -> Void) -> Style {
87 | return Style {
88 | self.apply(to: $0)
89 | other($0)
90 | }
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/Pods/Layoutless/Sources/Views/Button.swift:
--------------------------------------------------------------------------------
1 | //
2 | // The MIT License (MIT)
3 | //
4 | // Copyright (c) 2018 Srdan Rasic (@srdanrasic)
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to deal
8 | // in the Software without restriction, including without limitation the rights
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | // THE SOFTWARE.
23 | //
24 |
25 | import UIKit
26 |
27 | open class Button: UIButton {
28 |
29 | /// A closure that gets called with `self` as an argument on `layoutSubviews`.
30 | /// Use it to configure styles that are derived from the view bounds.
31 | public var onLayout: (Button) -> Void = { _ in }
32 |
33 | public var intrinsicContentInsets: CGSize = .zero
34 |
35 | open override var intrinsicContentSize: CGSize {
36 | var size = super.intrinsicContentSize
37 | size.width += intrinsicContentInsets.width * 2
38 | size.height += intrinsicContentInsets.height * 2
39 | return size
40 | }
41 |
42 | public override init(frame: CGRect) {
43 | super.init(frame: frame)
44 | setup()
45 | defineLayout()
46 | }
47 |
48 | public required init?(coder aDecoder: NSCoder) {
49 | super.init(coder: aDecoder)
50 | setup()
51 | defineLayout()
52 | }
53 |
54 | open override func layoutSubviews() {
55 | super.layoutSubviews()
56 | onLayout(self)
57 | if layer.shadowOpacity > 0 {
58 | layer.shadowPath = UIBezierPath(roundedRect: bounds, cornerRadius: layer.cornerRadius).cgPath
59 | }
60 | }
61 |
62 | open func setup() {
63 | }
64 |
65 | open func defineLayout() {
66 | _ = subviewsLayout.layout(in: self)
67 | }
68 |
69 | open var subviewsLayout: AnyLayout {
70 | return EmptyLayout()
71 | }
72 | }
73 |
74 |
--------------------------------------------------------------------------------
/Pods/Layoutless/Sources/Views/CollectionViewCell.swift:
--------------------------------------------------------------------------------
1 | //
2 | // The MIT License (MIT)
3 | //
4 | // Copyright (c) 2018 Srdan Rasic (@srdanrasic)
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to deal
8 | // in the Software without restriction, including without limitation the rights
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | // THE SOFTWARE.
23 | //
24 |
25 | import UIKit
26 |
27 | open class CollectionViewCell: UICollectionViewCell {
28 |
29 | /// A closure that gets called with `self` as an argument on `layoutSubviews`.
30 | /// Use it to configure styles that are derived from the view bounds.
31 | public var onLayout: (CollectionViewCell) -> Void = { _ in }
32 |
33 | public override init(frame: CGRect) {
34 | super.init(frame: frame)
35 | setup()
36 | defineLayout()
37 | }
38 |
39 | public required init?(coder aDecoder: NSCoder) {
40 | super.init(coder: aDecoder)
41 | setup()
42 | defineLayout()
43 | }
44 |
45 | open override func layoutSubviews() {
46 | super.layoutSubviews()
47 | onLayout(self)
48 | if layer.shadowOpacity > 0 {
49 | layer.shadowPath = UIBezierPath(roundedRect: bounds, cornerRadius: layer.cornerRadius).cgPath
50 | }
51 | }
52 |
53 | open func setup() {
54 | }
55 |
56 | open func defineLayout() {
57 | _ = subviewsLayout.layout(in: contentView)
58 | }
59 |
60 | open var subviewsLayout: AnyLayout {
61 | return EmptyLayout()
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/Pods/Layoutless/Sources/Views/Control.swift:
--------------------------------------------------------------------------------
1 | //
2 | // The MIT License (MIT)
3 | //
4 | // Copyright (c) 2018 Srdan Rasic (@srdanrasic)
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to deal
8 | // in the Software without restriction, including without limitation the rights
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | // THE SOFTWARE.
23 | //
24 |
25 | import UIKit
26 |
27 | open class Control: UIControl {
28 |
29 | /// A closure that gets called with `self` as an argument on `layoutSubviews`.
30 | /// Use it to configure styles that are derived from the view bounds.
31 | public var onLayout: (Control) -> Void = { _ in }
32 |
33 | public override init(frame: CGRect) {
34 | super.init(frame: frame)
35 | setup()
36 | defineLayout()
37 | }
38 |
39 | public required init?(coder aDecoder: NSCoder) {
40 | super.init(coder: aDecoder)
41 | setup()
42 | defineLayout()
43 | }
44 |
45 | open override func layoutSubviews() {
46 | super.layoutSubviews()
47 | onLayout(self)
48 | if layer.shadowOpacity > 0 {
49 | layer.shadowPath = UIBezierPath(roundedRect: bounds, cornerRadius: layer.cornerRadius).cgPath
50 | }
51 | }
52 |
53 | open func setup() {
54 | }
55 |
56 | open func defineLayout() {
57 | _ = subviewsLayout.layout(in: self)
58 | }
59 |
60 | open var subviewsLayout: AnyLayout {
61 | return EmptyLayout()
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/Pods/Layoutless/Sources/Views/ImageView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // The MIT License (MIT)
3 | //
4 | // Copyright (c) 2018 Srdan Rasic (@srdanrasic)
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to deal
8 | // in the Software without restriction, including without limitation the rights
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | // THE SOFTWARE.
23 | //
24 |
25 | import UIKit
26 |
27 | open class ImageView: UIImageView {
28 |
29 | /// A closure that gets called with `self` as an argument on `layoutSubviews`.
30 | /// Use it to configure styles that are derived from the view bounds.
31 | public var onLayout: (ImageView) -> Void = { _ in }
32 |
33 | public override init(image: UIImage? = nil) {
34 | super.init(image: image)
35 | setup()
36 | defineLayout()
37 | }
38 |
39 | public override init(frame: CGRect) {
40 | super.init(frame: frame)
41 | setup()
42 | defineLayout()
43 | }
44 |
45 | public required init?(coder aDecoder: NSCoder) {
46 | super.init(coder: aDecoder)
47 | setup()
48 | defineLayout()
49 | }
50 |
51 | open override func layoutSubviews() {
52 | super.layoutSubviews()
53 | onLayout(self)
54 | if layer.shadowOpacity > 0 {
55 | layer.shadowPath = UIBezierPath(roundedRect: bounds, cornerRadius: layer.cornerRadius).cgPath
56 | }
57 | }
58 |
59 | open func setup() {
60 | }
61 |
62 | open func defineLayout() {
63 | _ = subviewsLayout.layout(in: self)
64 | }
65 |
66 | open var subviewsLayout: AnyLayout {
67 | return EmptyLayout()
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/Pods/Layoutless/Sources/Views/Label.swift:
--------------------------------------------------------------------------------
1 | //
2 | // The MIT License (MIT)
3 | //
4 | // Copyright (c) 2018 Srdan Rasic (@srdanrasic)
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to deal
8 | // in the Software without restriction, including without limitation the rights
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | // THE SOFTWARE.
23 | //
24 |
25 | import UIKit
26 |
27 | open class Label: UILabel {
28 |
29 | /// A closure that gets called with `self` as an argument on `layoutSubviews`.
30 | /// Use it to configure styles that are derived from the view bounds.
31 | public var onLayout: (Label) -> Void = { _ in }
32 |
33 | public var intrinsicContentInsets: CGSize = .zero
34 |
35 | /// Text insets. Use it to add padding to the text within the label bounds.
36 | public var textInsets: UIEdgeInsets = .zero
37 |
38 | public var attributes: [NSAttributedStringKey: Any] = [:] {
39 | didSet {
40 | if let text = text {
41 | self.text = text
42 | }
43 | }
44 | }
45 |
46 | public var paragraphStyle: NSMutableParagraphStyle {
47 | get {
48 | if let paragraphStyle = attributes[.paragraphStyle] as? NSMutableParagraphStyle {
49 | return paragraphStyle
50 | } else {
51 | let paragraphStyle = NSMutableParagraphStyle()
52 | attributes[.paragraphStyle] = paragraphStyle
53 | return paragraphStyle
54 | }
55 | }
56 | set {
57 | attributes[.paragraphStyle] = newValue
58 | }
59 | }
60 |
61 | @objc
62 | public dynamic var attributedLineSpacing: CGFloat {
63 | get {
64 | return paragraphStyle.lineSpacing
65 | }
66 | set {
67 | paragraphStyle.lineSpacing = newValue
68 | }
69 | }
70 |
71 | open override var intrinsicContentSize: CGSize {
72 | var size = super.intrinsicContentSize
73 | size.width += textInsets.right + textInsets.left + intrinsicContentInsets.width * 2
74 | size.height += textInsets.top + textInsets.bottom + intrinsicContentInsets.height * 2
75 | return size
76 | }
77 |
78 | public override init(frame: CGRect) {
79 | super.init(frame: frame)
80 | setup()
81 | defineLayout()
82 | }
83 |
84 | public required init?(coder aDecoder: NSCoder) {
85 | super.init(coder: aDecoder)
86 | setup()
87 | defineLayout()
88 | }
89 |
90 | open override func layoutSubviews() {
91 | super.layoutSubviews()
92 | onLayout(self)
93 | }
94 |
95 | open override func drawText(in rect: CGRect) {
96 | super.drawText(in: UIEdgeInsetsInsetRect(rect, textInsets))
97 | }
98 |
99 | open override var text: String? {
100 | get {
101 | return attributedText?.string ?? super.text
102 | }
103 | set {
104 | if let newValue = newValue, attributes.count > 0 {
105 | attributedText = NSAttributedString(string: newValue, attributes: attributes)
106 | } else {
107 | super.text = newValue
108 | }
109 | }
110 | }
111 |
112 | open func setup() {
113 | }
114 |
115 | open func defineLayout() {
116 | _ = subviewsLayout.layout(in: self)
117 | }
118 |
119 | open var subviewsLayout: AnyLayout {
120 | return EmptyLayout()
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/Pods/Layoutless/Sources/Views/TableViewCell.swift:
--------------------------------------------------------------------------------
1 | //
2 | // The MIT License (MIT)
3 | //
4 | // Copyright (c) 2018 Srdan Rasic (@srdanrasic)
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to deal
8 | // in the Software without restriction, including without limitation the rights
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | // THE SOFTWARE.
23 | //
24 |
25 | import UIKit
26 |
27 | open class TableViewCell: UITableViewCell {
28 |
29 | /// A closure that gets called with `self` as an argument on `layoutSubviews`.
30 | /// Use it to configure styles that are derived from the view bounds.
31 | public var onLayout: (TableViewCell) -> Void = { _ in }
32 |
33 | public override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
34 | super.init(style: style, reuseIdentifier: reuseIdentifier)
35 | setup()
36 | defineLayout()
37 | }
38 |
39 | public required init?(coder aDecoder: NSCoder) {
40 | super.init(coder: aDecoder)
41 | setup()
42 | defineLayout()
43 | }
44 |
45 | open override func layoutSubviews() {
46 | super.layoutSubviews()
47 | onLayout(self)
48 | if layer.shadowOpacity > 0 {
49 | layer.shadowPath = UIBezierPath(roundedRect: bounds, cornerRadius: layer.cornerRadius).cgPath
50 | }
51 | }
52 |
53 | open func setup() {
54 | }
55 |
56 | open func defineLayout() {
57 | _ = subviewsLayout.layout(in: contentView)
58 | }
59 |
60 | open var subviewsLayout: AnyLayout {
61 | return EmptyLayout()
62 | }
63 | }
64 |
65 |
--------------------------------------------------------------------------------
/Pods/Layoutless/Sources/Views/TextField.swift:
--------------------------------------------------------------------------------
1 | //
2 | // The MIT License (MIT)
3 | //
4 | // Copyright (c) 2018 Srdan Rasic (@srdanrasic)
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to deal
8 | // in the Software without restriction, including without limitation the rights
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | // THE SOFTWARE.
23 | //
24 |
25 | import UIKit
26 |
27 | open class TextField: UITextField {
28 |
29 | /// A closure that gets called with `self` as an argument on `layoutSubviews`.
30 | /// Use it to configure styles that are derived from the view bounds.
31 | public var onLayout: (TextField) -> Void = { _ in }
32 |
33 | public var textInsets: UIEdgeInsets = .zero
34 |
35 | open override var intrinsicContentSize: CGSize {
36 | var size = super.intrinsicContentSize
37 | size.width += textInsets.right + textInsets.left
38 | size.height += textInsets.top + textInsets.bottom
39 | return size
40 | }
41 |
42 | public override init(frame: CGRect) {
43 | super.init(frame: frame)
44 | setup()
45 | defineLayout()
46 | }
47 |
48 | public required init?(coder aDecoder: NSCoder) {
49 | super.init(coder: aDecoder)
50 | setup()
51 | defineLayout()
52 | }
53 |
54 | open override func layoutSubviews() {
55 | super.layoutSubviews()
56 | onLayout(self)
57 | if layer.shadowOpacity > 0 {
58 | layer.shadowPath = UIBezierPath(roundedRect: bounds, cornerRadius: layer.cornerRadius).cgPath
59 | }
60 | }
61 |
62 | open override func editingRect(forBounds bounds: CGRect) -> CGRect {
63 | return UIEdgeInsetsInsetRect(super.editingRect(forBounds: bounds), textInsets)
64 | }
65 |
66 | open override func textRect(forBounds bounds: CGRect) -> CGRect {
67 | return UIEdgeInsetsInsetRect(super.textRect(forBounds: bounds), textInsets)
68 | }
69 |
70 | open func setup() {
71 | }
72 |
73 | open func defineLayout() {
74 | _ = subviewsLayout.layout(in: self)
75 | }
76 |
77 | open var subviewsLayout: AnyLayout {
78 | return EmptyLayout()
79 | }
80 | }
81 |
82 |
--------------------------------------------------------------------------------
/Pods/Layoutless/Sources/Views/View.swift:
--------------------------------------------------------------------------------
1 | //
2 | // The MIT License (MIT)
3 | //
4 | // Copyright (c) 2018 Srdan Rasic (@srdanrasic)
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to deal
8 | // in the Software without restriction, including without limitation the rights
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | // THE SOFTWARE.
23 | //
24 |
25 | import UIKit
26 |
27 | open class View: UIView {
28 |
29 | /// A closure that gets called with `self` as an argument on `layoutSubviews`.
30 | /// Use it to configure styles that are derived from the view bounds.
31 | public var onLayout: (View) -> Void = { _ in }
32 |
33 | public override init(frame: CGRect) {
34 | super.init(frame: frame)
35 | setup()
36 | defineLayout()
37 | }
38 |
39 | public required init?(coder aDecoder: NSCoder) {
40 | super.init(coder: aDecoder)
41 | setup()
42 | defineLayout()
43 | }
44 |
45 | open override func layoutSubviews() {
46 | super.layoutSubviews()
47 | onLayout(self)
48 | if layer.shadowOpacity > 0 {
49 | layer.shadowPath = UIBezierPath(roundedRect: bounds, cornerRadius: layer.cornerRadius).cgPath
50 | }
51 | }
52 |
53 | open func setup() {
54 | }
55 |
56 | open func defineLayout() {
57 | _ = subviewsLayout.layout(in: self)
58 | }
59 |
60 | open var subviewsLayout: AnyLayout {
61 | return EmptyLayout()
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/Pods/Layoutless/Sources/Views/ViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // The MIT License (MIT)
3 | //
4 | // Copyright (c) 2018 Srdan Rasic (@srdanrasic)
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to deal
8 | // in the Software without restriction, including without limitation the rights
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | // THE SOFTWARE.
23 | //
24 |
25 | import UIKit
26 |
27 | open class ViewController: UIViewController {
28 |
29 | public override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
30 | super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
31 | setup()
32 | }
33 |
34 | public required init?(coder aDecoder: NSCoder) {
35 | super.init(coder: aDecoder)
36 | setup()
37 | }
38 |
39 | open override func viewDidLoad() {
40 | super.viewDidLoad()
41 | defineLayout()
42 | }
43 |
44 | open func setup() {
45 | }
46 |
47 | open func defineLayout() {
48 | _ = subviewsLayout.layout(in: view)
49 | }
50 |
51 | open var subviewsLayout: AnyLayout {
52 | return EmptyLayout()
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/Pods/Manifest.lock:
--------------------------------------------------------------------------------
1 | PODS:
2 | - Layoutless (0.1.2)
3 |
4 | DEPENDENCIES:
5 | - Layoutless
6 |
7 | SPEC CHECKSUMS:
8 | Layoutless: 9e5bc51aacced232401af71c70644c069d119ec5
9 |
10 | PODFILE CHECKSUM: dcb85b7e4ac2972148d60b5628a51163922d3ea2
11 |
12 | COCOAPODS: 1.4.0
13 |
--------------------------------------------------------------------------------
/Pods/Target Support Files/Layoutless/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | ${EXECUTABLE_NAME}
9 | CFBundleIdentifier
10 | ${PRODUCT_BUNDLE_IDENTIFIER}
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | ${PRODUCT_NAME}
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | 0.1.2
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | ${CURRENT_PROJECT_VERSION}
23 | NSPrincipalClass
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/Pods/Target Support Files/Layoutless/Layoutless-dummy.m:
--------------------------------------------------------------------------------
1 | #import
2 | @interface PodsDummy_Layoutless : NSObject
3 | @end
4 | @implementation PodsDummy_Layoutless
5 | @end
6 |
--------------------------------------------------------------------------------
/Pods/Target Support Files/Layoutless/Layoutless-prefix.pch:
--------------------------------------------------------------------------------
1 | #ifdef __OBJC__
2 | #import
3 | #else
4 | #ifndef FOUNDATION_EXPORT
5 | #if defined(__cplusplus)
6 | #define FOUNDATION_EXPORT extern "C"
7 | #else
8 | #define FOUNDATION_EXPORT extern
9 | #endif
10 | #endif
11 | #endif
12 |
13 |
--------------------------------------------------------------------------------
/Pods/Target Support Files/Layoutless/Layoutless-umbrella.h:
--------------------------------------------------------------------------------
1 | #ifdef __OBJC__
2 | #import
3 | #else
4 | #ifndef FOUNDATION_EXPORT
5 | #if defined(__cplusplus)
6 | #define FOUNDATION_EXPORT extern "C"
7 | #else
8 | #define FOUNDATION_EXPORT extern
9 | #endif
10 | #endif
11 | #endif
12 |
13 |
14 | FOUNDATION_EXPORT double LayoutlessVersionNumber;
15 | FOUNDATION_EXPORT const unsigned char LayoutlessVersionString[];
16 |
17 |
--------------------------------------------------------------------------------
/Pods/Target Support Files/Layoutless/Layoutless.modulemap:
--------------------------------------------------------------------------------
1 | framework module Layoutless {
2 | umbrella header "Layoutless-umbrella.h"
3 |
4 | export *
5 | module * { export * }
6 | }
7 |
--------------------------------------------------------------------------------
/Pods/Target Support Files/Layoutless/Layoutless.xcconfig:
--------------------------------------------------------------------------------
1 | CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/Layoutless
2 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
3 | HEADER_SEARCH_PATHS = "${PODS_ROOT}/Headers/Private" "${PODS_ROOT}/Headers/Public"
4 | OTHER_SWIFT_FLAGS = $(inherited) "-D" "COCOAPODS"
5 | PODS_BUILD_DIR = ${BUILD_DIR}
6 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
7 | PODS_ROOT = ${SRCROOT}
8 | PODS_TARGET_SRCROOT = ${PODS_ROOT}/Layoutless
9 | PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier}
10 | SKIP_INSTALL = YES
11 |
--------------------------------------------------------------------------------
/Pods/Target Support Files/Pods-BlogApp/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | ${EXECUTABLE_NAME}
9 | CFBundleIdentifier
10 | ${PRODUCT_BUNDLE_IDENTIFIER}
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | ${PRODUCT_NAME}
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | 1.0.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | ${CURRENT_PROJECT_VERSION}
23 | NSPrincipalClass
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/Pods/Target Support Files/Pods-BlogApp/Pods-BlogApp-acknowledgements.markdown:
--------------------------------------------------------------------------------
1 | # Acknowledgements
2 | This application makes use of the following third party libraries:
3 |
4 | ## Layoutless
5 |
6 | MIT License
7 |
8 | Copyright (c) 2018 Srdan Rasic (@srdanrasic)
9 |
10 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
13 |
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
15 | Generated by CocoaPods - https://cocoapods.org
16 |
--------------------------------------------------------------------------------
/Pods/Target Support Files/Pods-BlogApp/Pods-BlogApp-acknowledgements.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PreferenceSpecifiers
6 |
7 |
8 | FooterText
9 | This application makes use of the following third party libraries:
10 | Title
11 | Acknowledgements
12 | Type
13 | PSGroupSpecifier
14 |
15 |
16 | FooterText
17 | MIT License
18 |
19 | Copyright (c) 2018 Srdan Rasic (@srdanrasic)
20 |
21 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
22 |
23 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
24 |
25 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
26 | License
27 | MIT
28 | Title
29 | Layoutless
30 | Type
31 | PSGroupSpecifier
32 |
33 |
34 | FooterText
35 | Generated by CocoaPods - https://cocoapods.org
36 | Title
37 |
38 | Type
39 | PSGroupSpecifier
40 |
41 |
42 | StringsTable
43 | Acknowledgements
44 | Title
45 | Acknowledgements
46 |
47 |
48 |
--------------------------------------------------------------------------------
/Pods/Target Support Files/Pods-BlogApp/Pods-BlogApp-dummy.m:
--------------------------------------------------------------------------------
1 | #import
2 | @interface PodsDummy_Pods_BlogApp : NSObject
3 | @end
4 | @implementation PodsDummy_Pods_BlogApp
5 | @end
6 |
--------------------------------------------------------------------------------
/Pods/Target Support Files/Pods-BlogApp/Pods-BlogApp-resources.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | set -e
3 |
4 | mkdir -p "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
5 |
6 | RESOURCES_TO_COPY=${PODS_ROOT}/resources-to-copy-${TARGETNAME}.txt
7 | > "$RESOURCES_TO_COPY"
8 |
9 | XCASSET_FILES=()
10 |
11 | # This protects against multiple targets copying the same framework dependency at the same time. The solution
12 | # was originally proposed here: https://lists.samba.org/archive/rsync/2008-February/020158.html
13 | RSYNC_PROTECT_TMP_FILES=(--filter "P .*.??????")
14 |
15 | case "${TARGETED_DEVICE_FAMILY}" in
16 | 1,2)
17 | TARGET_DEVICE_ARGS="--target-device ipad --target-device iphone"
18 | ;;
19 | 1)
20 | TARGET_DEVICE_ARGS="--target-device iphone"
21 | ;;
22 | 2)
23 | TARGET_DEVICE_ARGS="--target-device ipad"
24 | ;;
25 | 3)
26 | TARGET_DEVICE_ARGS="--target-device tv"
27 | ;;
28 | 4)
29 | TARGET_DEVICE_ARGS="--target-device watch"
30 | ;;
31 | *)
32 | TARGET_DEVICE_ARGS="--target-device mac"
33 | ;;
34 | esac
35 |
36 | install_resource()
37 | {
38 | if [[ "$1" = /* ]] ; then
39 | RESOURCE_PATH="$1"
40 | else
41 | RESOURCE_PATH="${PODS_ROOT}/$1"
42 | fi
43 | if [[ ! -e "$RESOURCE_PATH" ]] ; then
44 | cat << EOM
45 | error: Resource "$RESOURCE_PATH" not found. Run 'pod install' to update the copy resources script.
46 | EOM
47 | exit 1
48 | fi
49 | case $RESOURCE_PATH in
50 | *.storyboard)
51 | echo "ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile ${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .storyboard`.storyboardc $RESOURCE_PATH --sdk ${SDKROOT} ${TARGET_DEVICE_ARGS}" || true
52 | ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .storyboard`.storyboardc" "$RESOURCE_PATH" --sdk "${SDKROOT}" ${TARGET_DEVICE_ARGS}
53 | ;;
54 | *.xib)
55 | echo "ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile ${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .xib`.nib $RESOURCE_PATH --sdk ${SDKROOT} ${TARGET_DEVICE_ARGS}" || true
56 | ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .xib`.nib" "$RESOURCE_PATH" --sdk "${SDKROOT}" ${TARGET_DEVICE_ARGS}
57 | ;;
58 | *.framework)
59 | echo "mkdir -p ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" || true
60 | mkdir -p "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
61 | echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" $RESOURCE_PATH ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" || true
62 | rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
63 | ;;
64 | *.xcdatamodel)
65 | echo "xcrun momc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH"`.mom\"" || true
66 | xcrun momc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodel`.mom"
67 | ;;
68 | *.xcdatamodeld)
69 | echo "xcrun momc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodeld`.momd\"" || true
70 | xcrun momc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodeld`.momd"
71 | ;;
72 | *.xcmappingmodel)
73 | echo "xcrun mapc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcmappingmodel`.cdm\"" || true
74 | xcrun mapc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcmappingmodel`.cdm"
75 | ;;
76 | *.xcassets)
77 | ABSOLUTE_XCASSET_FILE="$RESOURCE_PATH"
78 | XCASSET_FILES+=("$ABSOLUTE_XCASSET_FILE")
79 | ;;
80 | *)
81 | echo "$RESOURCE_PATH" || true
82 | echo "$RESOURCE_PATH" >> "$RESOURCES_TO_COPY"
83 | ;;
84 | esac
85 | }
86 |
87 | mkdir -p "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
88 | rsync -avr --copy-links --no-relative --exclude '*/.svn/*' --files-from="$RESOURCES_TO_COPY" / "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
89 | if [[ "${ACTION}" == "install" ]] && [[ "${SKIP_INSTALL}" == "NO" ]]; then
90 | mkdir -p "${INSTALL_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
91 | rsync -avr --copy-links --no-relative --exclude '*/.svn/*' --files-from="$RESOURCES_TO_COPY" / "${INSTALL_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
92 | fi
93 | rm -f "$RESOURCES_TO_COPY"
94 |
95 | if [[ -n "${WRAPPER_EXTENSION}" ]] && [ "`xcrun --find actool`" ] && [ -n "$XCASSET_FILES" ]
96 | then
97 | # Find all other xcassets (this unfortunately includes those of path pods and other targets).
98 | OTHER_XCASSETS=$(find "$PWD" -iname "*.xcassets" -type d)
99 | while read line; do
100 | if [[ $line != "${PODS_ROOT}*" ]]; then
101 | XCASSET_FILES+=("$line")
102 | fi
103 | done <<<"$OTHER_XCASSETS"
104 |
105 | printf "%s\0" "${XCASSET_FILES[@]}" | xargs -0 xcrun actool --output-format human-readable-text --notices --warnings --platform "${PLATFORM_NAME}" --minimum-deployment-target "${!DEPLOYMENT_TARGET_SETTING_NAME}" ${TARGET_DEVICE_ARGS} --compress-pngs --compile "${BUILT_PRODUCTS_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
106 | fi
107 |
--------------------------------------------------------------------------------
/Pods/Target Support Files/Pods-BlogApp/Pods-BlogApp-umbrella.h:
--------------------------------------------------------------------------------
1 | #ifdef __OBJC__
2 | #import
3 | #else
4 | #ifndef FOUNDATION_EXPORT
5 | #if defined(__cplusplus)
6 | #define FOUNDATION_EXPORT extern "C"
7 | #else
8 | #define FOUNDATION_EXPORT extern
9 | #endif
10 | #endif
11 | #endif
12 |
13 |
14 | FOUNDATION_EXPORT double Pods_BlogAppVersionNumber;
15 | FOUNDATION_EXPORT const unsigned char Pods_BlogAppVersionString[];
16 |
17 |
--------------------------------------------------------------------------------
/Pods/Target Support Files/Pods-BlogApp/Pods-BlogApp.debug.xcconfig:
--------------------------------------------------------------------------------
1 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES
2 | FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/Layoutless"
3 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
4 | LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks'
5 | OTHER_CFLAGS = $(inherited) -iquote "${PODS_CONFIGURATION_BUILD_DIR}/Layoutless/Layoutless.framework/Headers"
6 | OTHER_LDFLAGS = $(inherited) -framework "Layoutless"
7 | OTHER_SWIFT_FLAGS = $(inherited) "-D" "COCOAPODS"
8 | PODS_BUILD_DIR = ${BUILD_DIR}
9 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
10 | PODS_PODFILE_DIR_PATH = ${SRCROOT}/..
11 | PODS_ROOT = ${SRCROOT}/../Pods
12 |
--------------------------------------------------------------------------------
/Pods/Target Support Files/Pods-BlogApp/Pods-BlogApp.modulemap:
--------------------------------------------------------------------------------
1 | framework module Pods_BlogApp {
2 | umbrella header "Pods-BlogApp-umbrella.h"
3 |
4 | export *
5 | module * { export * }
6 | }
7 |
--------------------------------------------------------------------------------
/Pods/Target Support Files/Pods-BlogApp/Pods-BlogApp.release.xcconfig:
--------------------------------------------------------------------------------
1 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES
2 | FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/Layoutless"
3 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
4 | LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks'
5 | OTHER_CFLAGS = $(inherited) -iquote "${PODS_CONFIGURATION_BUILD_DIR}/Layoutless/Layoutless.framework/Headers"
6 | OTHER_LDFLAGS = $(inherited) -framework "Layoutless"
7 | OTHER_SWIFT_FLAGS = $(inherited) "-D" "COCOAPODS"
8 | PODS_BUILD_DIR = ${BUILD_DIR}
9 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
10 | PODS_PODFILE_DIR_PATH = ${SRCROOT}/..
11 | PODS_ROOT = ${SRCROOT}/../Pods
12 |
--------------------------------------------------------------------------------
/Pods/Target Support Files/Pods-BlogAppTests/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | ${EXECUTABLE_NAME}
9 | CFBundleIdentifier
10 | ${PRODUCT_BUNDLE_IDENTIFIER}
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | ${PRODUCT_NAME}
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | 1.0.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | ${CURRENT_PROJECT_VERSION}
23 | NSPrincipalClass
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/Pods/Target Support Files/Pods-BlogAppTests/Pods-BlogAppTests-acknowledgements.markdown:
--------------------------------------------------------------------------------
1 | # Acknowledgements
2 | This application makes use of the following third party libraries:
3 |
4 | ## Layoutless
5 |
6 | MIT License
7 |
8 | Copyright (c) 2018 Srdan Rasic (@srdanrasic)
9 |
10 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
13 |
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
15 | Generated by CocoaPods - https://cocoapods.org
16 |
--------------------------------------------------------------------------------
/Pods/Target Support Files/Pods-BlogAppTests/Pods-BlogAppTests-acknowledgements.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PreferenceSpecifiers
6 |
7 |
8 | FooterText
9 | This application makes use of the following third party libraries:
10 | Title
11 | Acknowledgements
12 | Type
13 | PSGroupSpecifier
14 |
15 |
16 | FooterText
17 | MIT License
18 |
19 | Copyright (c) 2018 Srdan Rasic (@srdanrasic)
20 |
21 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
22 |
23 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
24 |
25 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
26 | License
27 | MIT
28 | Title
29 | Layoutless
30 | Type
31 | PSGroupSpecifier
32 |
33 |
34 | FooterText
35 | Generated by CocoaPods - https://cocoapods.org
36 | Title
37 |
38 | Type
39 | PSGroupSpecifier
40 |
41 |
42 | StringsTable
43 | Acknowledgements
44 | Title
45 | Acknowledgements
46 |
47 |
48 |
--------------------------------------------------------------------------------
/Pods/Target Support Files/Pods-BlogAppTests/Pods-BlogAppTests-dummy.m:
--------------------------------------------------------------------------------
1 | #import
2 | @interface PodsDummy_Pods_BlogAppTests : NSObject
3 | @end
4 | @implementation PodsDummy_Pods_BlogAppTests
5 | @end
6 |
--------------------------------------------------------------------------------
/Pods/Target Support Files/Pods-BlogAppTests/Pods-BlogAppTests-umbrella.h:
--------------------------------------------------------------------------------
1 | #ifdef __OBJC__
2 | #import
3 | #else
4 | #ifndef FOUNDATION_EXPORT
5 | #if defined(__cplusplus)
6 | #define FOUNDATION_EXPORT extern "C"
7 | #else
8 | #define FOUNDATION_EXPORT extern
9 | #endif
10 | #endif
11 | #endif
12 |
13 |
14 | FOUNDATION_EXPORT double Pods_BlogAppTestsVersionNumber;
15 | FOUNDATION_EXPORT const unsigned char Pods_BlogAppTestsVersionString[];
16 |
17 |
--------------------------------------------------------------------------------
/Pods/Target Support Files/Pods-BlogAppTests/Pods-BlogAppTests.debug.xcconfig:
--------------------------------------------------------------------------------
1 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES
2 | FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/Layoutless"
3 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
4 | LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks'
5 | OTHER_CFLAGS = $(inherited) -iquote "${PODS_CONFIGURATION_BUILD_DIR}/Layoutless/Layoutless.framework/Headers"
6 | OTHER_LDFLAGS = $(inherited) -framework "Layoutless"
7 | OTHER_SWIFT_FLAGS = $(inherited) "-D" "COCOAPODS"
8 | PODS_BUILD_DIR = ${BUILD_DIR}
9 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
10 | PODS_PODFILE_DIR_PATH = ${SRCROOT}/..
11 | PODS_ROOT = ${SRCROOT}/../Pods
12 |
--------------------------------------------------------------------------------
/Pods/Target Support Files/Pods-BlogAppTests/Pods-BlogAppTests.modulemap:
--------------------------------------------------------------------------------
1 | framework module Pods_BlogAppTests {
2 | umbrella header "Pods-BlogAppTests-umbrella.h"
3 |
4 | export *
5 | module * { export * }
6 | }
7 |
--------------------------------------------------------------------------------
/Pods/Target Support Files/Pods-BlogAppTests/Pods-BlogAppTests.release.xcconfig:
--------------------------------------------------------------------------------
1 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES
2 | FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/Layoutless"
3 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
4 | LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks'
5 | OTHER_CFLAGS = $(inherited) -iquote "${PODS_CONFIGURATION_BUILD_DIR}/Layoutless/Layoutless.framework/Headers"
6 | OTHER_LDFLAGS = $(inherited) -framework "Layoutless"
7 | OTHER_SWIFT_FLAGS = $(inherited) "-D" "COCOAPODS"
8 | PODS_BUILD_DIR = ${BUILD_DIR}
9 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
10 | PODS_PODFILE_DIR_PATH = ${SRCROOT}/..
11 | PODS_ROOT = ${SRCROOT}/../Pods
12 |
--------------------------------------------------------------------------------
/Pods/Target Support Files/Pods-Features/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | ${EXECUTABLE_NAME}
9 | CFBundleIdentifier
10 | ${PRODUCT_BUNDLE_IDENTIFIER}
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | ${PRODUCT_NAME}
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | 1.0.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | ${CURRENT_PROJECT_VERSION}
23 | NSPrincipalClass
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/Pods/Target Support Files/Pods-Features/Pods-Features-acknowledgements.markdown:
--------------------------------------------------------------------------------
1 | # Acknowledgements
2 | This application makes use of the following third party libraries:
3 |
4 | ## Layoutless
5 |
6 | MIT License
7 |
8 | Copyright (c) 2018 Srdan Rasic (@srdanrasic)
9 |
10 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
13 |
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
15 | Generated by CocoaPods - https://cocoapods.org
16 |
--------------------------------------------------------------------------------
/Pods/Target Support Files/Pods-Features/Pods-Features-acknowledgements.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PreferenceSpecifiers
6 |
7 |
8 | FooterText
9 | This application makes use of the following third party libraries:
10 | Title
11 | Acknowledgements
12 | Type
13 | PSGroupSpecifier
14 |
15 |
16 | FooterText
17 | MIT License
18 |
19 | Copyright (c) 2018 Srdan Rasic (@srdanrasic)
20 |
21 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
22 |
23 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
24 |
25 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
26 | License
27 | MIT
28 | Title
29 | Layoutless
30 | Type
31 | PSGroupSpecifier
32 |
33 |
34 | FooterText
35 | Generated by CocoaPods - https://cocoapods.org
36 | Title
37 |
38 | Type
39 | PSGroupSpecifier
40 |
41 |
42 | StringsTable
43 | Acknowledgements
44 | Title
45 | Acknowledgements
46 |
47 |
48 |
--------------------------------------------------------------------------------
/Pods/Target Support Files/Pods-Features/Pods-Features-dummy.m:
--------------------------------------------------------------------------------
1 | #import
2 | @interface PodsDummy_Pods_Features : NSObject
3 | @end
4 | @implementation PodsDummy_Pods_Features
5 | @end
6 |
--------------------------------------------------------------------------------
/Pods/Target Support Files/Pods-Features/Pods-Features-resources.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | set -e
3 |
4 | mkdir -p "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
5 |
6 | RESOURCES_TO_COPY=${PODS_ROOT}/resources-to-copy-${TARGETNAME}.txt
7 | > "$RESOURCES_TO_COPY"
8 |
9 | XCASSET_FILES=()
10 |
11 | # This protects against multiple targets copying the same framework dependency at the same time. The solution
12 | # was originally proposed here: https://lists.samba.org/archive/rsync/2008-February/020158.html
13 | RSYNC_PROTECT_TMP_FILES=(--filter "P .*.??????")
14 |
15 | case "${TARGETED_DEVICE_FAMILY}" in
16 | 1,2)
17 | TARGET_DEVICE_ARGS="--target-device ipad --target-device iphone"
18 | ;;
19 | 1)
20 | TARGET_DEVICE_ARGS="--target-device iphone"
21 | ;;
22 | 2)
23 | TARGET_DEVICE_ARGS="--target-device ipad"
24 | ;;
25 | 3)
26 | TARGET_DEVICE_ARGS="--target-device tv"
27 | ;;
28 | 4)
29 | TARGET_DEVICE_ARGS="--target-device watch"
30 | ;;
31 | *)
32 | TARGET_DEVICE_ARGS="--target-device mac"
33 | ;;
34 | esac
35 |
36 | install_resource()
37 | {
38 | if [[ "$1" = /* ]] ; then
39 | RESOURCE_PATH="$1"
40 | else
41 | RESOURCE_PATH="${PODS_ROOT}/$1"
42 | fi
43 | if [[ ! -e "$RESOURCE_PATH" ]] ; then
44 | cat << EOM
45 | error: Resource "$RESOURCE_PATH" not found. Run 'pod install' to update the copy resources script.
46 | EOM
47 | exit 1
48 | fi
49 | case $RESOURCE_PATH in
50 | *.storyboard)
51 | echo "ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile ${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .storyboard`.storyboardc $RESOURCE_PATH --sdk ${SDKROOT} ${TARGET_DEVICE_ARGS}" || true
52 | ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .storyboard`.storyboardc" "$RESOURCE_PATH" --sdk "${SDKROOT}" ${TARGET_DEVICE_ARGS}
53 | ;;
54 | *.xib)
55 | echo "ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile ${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .xib`.nib $RESOURCE_PATH --sdk ${SDKROOT} ${TARGET_DEVICE_ARGS}" || true
56 | ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .xib`.nib" "$RESOURCE_PATH" --sdk "${SDKROOT}" ${TARGET_DEVICE_ARGS}
57 | ;;
58 | *.framework)
59 | echo "mkdir -p ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" || true
60 | mkdir -p "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
61 | echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" $RESOURCE_PATH ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" || true
62 | rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
63 | ;;
64 | *.xcdatamodel)
65 | echo "xcrun momc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH"`.mom\"" || true
66 | xcrun momc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodel`.mom"
67 | ;;
68 | *.xcdatamodeld)
69 | echo "xcrun momc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodeld`.momd\"" || true
70 | xcrun momc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodeld`.momd"
71 | ;;
72 | *.xcmappingmodel)
73 | echo "xcrun mapc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcmappingmodel`.cdm\"" || true
74 | xcrun mapc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcmappingmodel`.cdm"
75 | ;;
76 | *.xcassets)
77 | ABSOLUTE_XCASSET_FILE="$RESOURCE_PATH"
78 | XCASSET_FILES+=("$ABSOLUTE_XCASSET_FILE")
79 | ;;
80 | *)
81 | echo "$RESOURCE_PATH" || true
82 | echo "$RESOURCE_PATH" >> "$RESOURCES_TO_COPY"
83 | ;;
84 | esac
85 | }
86 |
87 | mkdir -p "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
88 | rsync -avr --copy-links --no-relative --exclude '*/.svn/*' --files-from="$RESOURCES_TO_COPY" / "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
89 | if [[ "${ACTION}" == "install" ]] && [[ "${SKIP_INSTALL}" == "NO" ]]; then
90 | mkdir -p "${INSTALL_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
91 | rsync -avr --copy-links --no-relative --exclude '*/.svn/*' --files-from="$RESOURCES_TO_COPY" / "${INSTALL_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
92 | fi
93 | rm -f "$RESOURCES_TO_COPY"
94 |
95 | if [[ -n "${WRAPPER_EXTENSION}" ]] && [ "`xcrun --find actool`" ] && [ -n "$XCASSET_FILES" ]
96 | then
97 | # Find all other xcassets (this unfortunately includes those of path pods and other targets).
98 | OTHER_XCASSETS=$(find "$PWD" -iname "*.xcassets" -type d)
99 | while read line; do
100 | if [[ $line != "${PODS_ROOT}*" ]]; then
101 | XCASSET_FILES+=("$line")
102 | fi
103 | done <<<"$OTHER_XCASSETS"
104 |
105 | printf "%s\0" "${XCASSET_FILES[@]}" | xargs -0 xcrun actool --output-format human-readable-text --notices --warnings --platform "${PLATFORM_NAME}" --minimum-deployment-target "${!DEPLOYMENT_TARGET_SETTING_NAME}" ${TARGET_DEVICE_ARGS} --compress-pngs --compile "${BUILT_PRODUCTS_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
106 | fi
107 |
--------------------------------------------------------------------------------
/Pods/Target Support Files/Pods-Features/Pods-Features-umbrella.h:
--------------------------------------------------------------------------------
1 | #ifdef __OBJC__
2 | #import
3 | #else
4 | #ifndef FOUNDATION_EXPORT
5 | #if defined(__cplusplus)
6 | #define FOUNDATION_EXPORT extern "C"
7 | #else
8 | #define FOUNDATION_EXPORT extern
9 | #endif
10 | #endif
11 | #endif
12 |
13 |
14 | FOUNDATION_EXPORT double Pods_FeaturesVersionNumber;
15 | FOUNDATION_EXPORT const unsigned char Pods_FeaturesVersionString[];
16 |
17 |
--------------------------------------------------------------------------------
/Pods/Target Support Files/Pods-Features/Pods-Features.debug.xcconfig:
--------------------------------------------------------------------------------
1 | FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/Layoutless"
2 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
3 | LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' '@executable_path/../../Frameworks'
4 | OTHER_CFLAGS = $(inherited) -iquote "${PODS_CONFIGURATION_BUILD_DIR}/Layoutless/Layoutless.framework/Headers"
5 | OTHER_LDFLAGS = $(inherited) -framework "Layoutless"
6 | OTHER_SWIFT_FLAGS = $(inherited) "-D" "COCOAPODS"
7 | PODS_BUILD_DIR = ${BUILD_DIR}
8 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
9 | PODS_PODFILE_DIR_PATH = ${SRCROOT}/..
10 | PODS_ROOT = ${SRCROOT}/../Pods
11 |
--------------------------------------------------------------------------------
/Pods/Target Support Files/Pods-Features/Pods-Features.modulemap:
--------------------------------------------------------------------------------
1 | framework module Pods_Features {
2 | umbrella header "Pods-Features-umbrella.h"
3 |
4 | export *
5 | module * { export * }
6 | }
7 |
--------------------------------------------------------------------------------
/Pods/Target Support Files/Pods-Features/Pods-Features.release.xcconfig:
--------------------------------------------------------------------------------
1 | FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/Layoutless"
2 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
3 | LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' '@executable_path/../../Frameworks'
4 | OTHER_CFLAGS = $(inherited) -iquote "${PODS_CONFIGURATION_BUILD_DIR}/Layoutless/Layoutless.framework/Headers"
5 | OTHER_LDFLAGS = $(inherited) -framework "Layoutless"
6 | OTHER_SWIFT_FLAGS = $(inherited) "-D" "COCOAPODS"
7 | PODS_BUILD_DIR = ${BUILD_DIR}
8 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
9 | PODS_PODFILE_DIR_PATH = ${SRCROOT}/..
10 | PODS_ROOT = ${SRCROOT}/../Pods
11 |
--------------------------------------------------------------------------------
/Pods/Target Support Files/Pods-FeaturesTests/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | ${EXECUTABLE_NAME}
9 | CFBundleIdentifier
10 | ${PRODUCT_BUNDLE_IDENTIFIER}
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | ${PRODUCT_NAME}
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | 1.0.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | ${CURRENT_PROJECT_VERSION}
23 | NSPrincipalClass
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/Pods/Target Support Files/Pods-FeaturesTests/Pods-FeaturesTests-acknowledgements.markdown:
--------------------------------------------------------------------------------
1 | # Acknowledgements
2 | This application makes use of the following third party libraries:
3 |
4 | ## Layoutless
5 |
6 | MIT License
7 |
8 | Copyright (c) 2018 Srdan Rasic (@srdanrasic)
9 |
10 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
13 |
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
15 | Generated by CocoaPods - https://cocoapods.org
16 |
--------------------------------------------------------------------------------
/Pods/Target Support Files/Pods-FeaturesTests/Pods-FeaturesTests-acknowledgements.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PreferenceSpecifiers
6 |
7 |
8 | FooterText
9 | This application makes use of the following third party libraries:
10 | Title
11 | Acknowledgements
12 | Type
13 | PSGroupSpecifier
14 |
15 |
16 | FooterText
17 | MIT License
18 |
19 | Copyright (c) 2018 Srdan Rasic (@srdanrasic)
20 |
21 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
22 |
23 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
24 |
25 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
26 | License
27 | MIT
28 | Title
29 | Layoutless
30 | Type
31 | PSGroupSpecifier
32 |
33 |
34 | FooterText
35 | Generated by CocoaPods - https://cocoapods.org
36 | Title
37 |
38 | Type
39 | PSGroupSpecifier
40 |
41 |
42 | StringsTable
43 | Acknowledgements
44 | Title
45 | Acknowledgements
46 |
47 |
48 |
--------------------------------------------------------------------------------
/Pods/Target Support Files/Pods-FeaturesTests/Pods-FeaturesTests-dummy.m:
--------------------------------------------------------------------------------
1 | #import
2 | @interface PodsDummy_Pods_FeaturesTests : NSObject
3 | @end
4 | @implementation PodsDummy_Pods_FeaturesTests
5 | @end
6 |
--------------------------------------------------------------------------------
/Pods/Target Support Files/Pods-FeaturesTests/Pods-FeaturesTests-umbrella.h:
--------------------------------------------------------------------------------
1 | #ifdef __OBJC__
2 | #import
3 | #else
4 | #ifndef FOUNDATION_EXPORT
5 | #if defined(__cplusplus)
6 | #define FOUNDATION_EXPORT extern "C"
7 | #else
8 | #define FOUNDATION_EXPORT extern
9 | #endif
10 | #endif
11 | #endif
12 |
13 |
14 | FOUNDATION_EXPORT double Pods_FeaturesTestsVersionNumber;
15 | FOUNDATION_EXPORT const unsigned char Pods_FeaturesTestsVersionString[];
16 |
17 |
--------------------------------------------------------------------------------
/Pods/Target Support Files/Pods-FeaturesTests/Pods-FeaturesTests.debug.xcconfig:
--------------------------------------------------------------------------------
1 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES
2 | FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/Layoutless"
3 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
4 | LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks'
5 | OTHER_CFLAGS = $(inherited) -iquote "${PODS_CONFIGURATION_BUILD_DIR}/Layoutless/Layoutless.framework/Headers"
6 | OTHER_LDFLAGS = $(inherited) -framework "Layoutless"
7 | OTHER_SWIFT_FLAGS = $(inherited) "-D" "COCOAPODS"
8 | PODS_BUILD_DIR = ${BUILD_DIR}
9 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
10 | PODS_PODFILE_DIR_PATH = ${SRCROOT}/..
11 | PODS_ROOT = ${SRCROOT}/../Pods
12 |
--------------------------------------------------------------------------------
/Pods/Target Support Files/Pods-FeaturesTests/Pods-FeaturesTests.modulemap:
--------------------------------------------------------------------------------
1 | framework module Pods_FeaturesTests {
2 | umbrella header "Pods-FeaturesTests-umbrella.h"
3 |
4 | export *
5 | module * { export * }
6 | }
7 |
--------------------------------------------------------------------------------
/Pods/Target Support Files/Pods-FeaturesTests/Pods-FeaturesTests.release.xcconfig:
--------------------------------------------------------------------------------
1 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES
2 | FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/Layoutless"
3 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
4 | LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks'
5 | OTHER_CFLAGS = $(inherited) -iquote "${PODS_CONFIGURATION_BUILD_DIR}/Layoutless/Layoutless.framework/Headers"
6 | OTHER_LDFLAGS = $(inherited) -framework "Layoutless"
7 | OTHER_SWIFT_FLAGS = $(inherited) "-D" "COCOAPODS"
8 | PODS_BUILD_DIR = ${BUILD_DIR}
9 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
10 | PODS_PODFILE_DIR_PATH = ${SRCROOT}/..
11 | PODS_ROOT = ${SRCROOT}/../Pods
12 |
--------------------------------------------------------------------------------
/Pods/Target Support Files/Pods-UI/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | ${EXECUTABLE_NAME}
9 | CFBundleIdentifier
10 | ${PRODUCT_BUNDLE_IDENTIFIER}
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | ${PRODUCT_NAME}
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | 1.0.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | ${CURRENT_PROJECT_VERSION}
23 | NSPrincipalClass
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/Pods/Target Support Files/Pods-UI/Pods-UI-acknowledgements.markdown:
--------------------------------------------------------------------------------
1 | # Acknowledgements
2 | This application makes use of the following third party libraries:
3 |
4 | ## Layoutless
5 |
6 | MIT License
7 |
8 | Copyright (c) 2018 Srdan Rasic (@srdanrasic)
9 |
10 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
13 |
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
15 | Generated by CocoaPods - https://cocoapods.org
16 |
--------------------------------------------------------------------------------
/Pods/Target Support Files/Pods-UI/Pods-UI-acknowledgements.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PreferenceSpecifiers
6 |
7 |
8 | FooterText
9 | This application makes use of the following third party libraries:
10 | Title
11 | Acknowledgements
12 | Type
13 | PSGroupSpecifier
14 |
15 |
16 | FooterText
17 | MIT License
18 |
19 | Copyright (c) 2018 Srdan Rasic (@srdanrasic)
20 |
21 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
22 |
23 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
24 |
25 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
26 | License
27 | MIT
28 | Title
29 | Layoutless
30 | Type
31 | PSGroupSpecifier
32 |
33 |
34 | FooterText
35 | Generated by CocoaPods - https://cocoapods.org
36 | Title
37 |
38 | Type
39 | PSGroupSpecifier
40 |
41 |
42 | StringsTable
43 | Acknowledgements
44 | Title
45 | Acknowledgements
46 |
47 |
48 |
--------------------------------------------------------------------------------
/Pods/Target Support Files/Pods-UI/Pods-UI-dummy.m:
--------------------------------------------------------------------------------
1 | #import
2 | @interface PodsDummy_Pods_UI : NSObject
3 | @end
4 | @implementation PodsDummy_Pods_UI
5 | @end
6 |
--------------------------------------------------------------------------------
/Pods/Target Support Files/Pods-UI/Pods-UI-resources.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | set -e
3 |
4 | mkdir -p "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
5 |
6 | RESOURCES_TO_COPY=${PODS_ROOT}/resources-to-copy-${TARGETNAME}.txt
7 | > "$RESOURCES_TO_COPY"
8 |
9 | XCASSET_FILES=()
10 |
11 | # This protects against multiple targets copying the same framework dependency at the same time. The solution
12 | # was originally proposed here: https://lists.samba.org/archive/rsync/2008-February/020158.html
13 | RSYNC_PROTECT_TMP_FILES=(--filter "P .*.??????")
14 |
15 | case "${TARGETED_DEVICE_FAMILY}" in
16 | 1,2)
17 | TARGET_DEVICE_ARGS="--target-device ipad --target-device iphone"
18 | ;;
19 | 1)
20 | TARGET_DEVICE_ARGS="--target-device iphone"
21 | ;;
22 | 2)
23 | TARGET_DEVICE_ARGS="--target-device ipad"
24 | ;;
25 | 3)
26 | TARGET_DEVICE_ARGS="--target-device tv"
27 | ;;
28 | 4)
29 | TARGET_DEVICE_ARGS="--target-device watch"
30 | ;;
31 | *)
32 | TARGET_DEVICE_ARGS="--target-device mac"
33 | ;;
34 | esac
35 |
36 | install_resource()
37 | {
38 | if [[ "$1" = /* ]] ; then
39 | RESOURCE_PATH="$1"
40 | else
41 | RESOURCE_PATH="${PODS_ROOT}/$1"
42 | fi
43 | if [[ ! -e "$RESOURCE_PATH" ]] ; then
44 | cat << EOM
45 | error: Resource "$RESOURCE_PATH" not found. Run 'pod install' to update the copy resources script.
46 | EOM
47 | exit 1
48 | fi
49 | case $RESOURCE_PATH in
50 | *.storyboard)
51 | echo "ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile ${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .storyboard`.storyboardc $RESOURCE_PATH --sdk ${SDKROOT} ${TARGET_DEVICE_ARGS}" || true
52 | ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .storyboard`.storyboardc" "$RESOURCE_PATH" --sdk "${SDKROOT}" ${TARGET_DEVICE_ARGS}
53 | ;;
54 | *.xib)
55 | echo "ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile ${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .xib`.nib $RESOURCE_PATH --sdk ${SDKROOT} ${TARGET_DEVICE_ARGS}" || true
56 | ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .xib`.nib" "$RESOURCE_PATH" --sdk "${SDKROOT}" ${TARGET_DEVICE_ARGS}
57 | ;;
58 | *.framework)
59 | echo "mkdir -p ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" || true
60 | mkdir -p "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
61 | echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" $RESOURCE_PATH ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" || true
62 | rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
63 | ;;
64 | *.xcdatamodel)
65 | echo "xcrun momc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH"`.mom\"" || true
66 | xcrun momc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodel`.mom"
67 | ;;
68 | *.xcdatamodeld)
69 | echo "xcrun momc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodeld`.momd\"" || true
70 | xcrun momc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodeld`.momd"
71 | ;;
72 | *.xcmappingmodel)
73 | echo "xcrun mapc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcmappingmodel`.cdm\"" || true
74 | xcrun mapc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcmappingmodel`.cdm"
75 | ;;
76 | *.xcassets)
77 | ABSOLUTE_XCASSET_FILE="$RESOURCE_PATH"
78 | XCASSET_FILES+=("$ABSOLUTE_XCASSET_FILE")
79 | ;;
80 | *)
81 | echo "$RESOURCE_PATH" || true
82 | echo "$RESOURCE_PATH" >> "$RESOURCES_TO_COPY"
83 | ;;
84 | esac
85 | }
86 |
87 | mkdir -p "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
88 | rsync -avr --copy-links --no-relative --exclude '*/.svn/*' --files-from="$RESOURCES_TO_COPY" / "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
89 | if [[ "${ACTION}" == "install" ]] && [[ "${SKIP_INSTALL}" == "NO" ]]; then
90 | mkdir -p "${INSTALL_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
91 | rsync -avr --copy-links --no-relative --exclude '*/.svn/*' --files-from="$RESOURCES_TO_COPY" / "${INSTALL_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
92 | fi
93 | rm -f "$RESOURCES_TO_COPY"
94 |
95 | if [[ -n "${WRAPPER_EXTENSION}" ]] && [ "`xcrun --find actool`" ] && [ -n "$XCASSET_FILES" ]
96 | then
97 | # Find all other xcassets (this unfortunately includes those of path pods and other targets).
98 | OTHER_XCASSETS=$(find "$PWD" -iname "*.xcassets" -type d)
99 | while read line; do
100 | if [[ $line != "${PODS_ROOT}*" ]]; then
101 | XCASSET_FILES+=("$line")
102 | fi
103 | done <<<"$OTHER_XCASSETS"
104 |
105 | printf "%s\0" "${XCASSET_FILES[@]}" | xargs -0 xcrun actool --output-format human-readable-text --notices --warnings --platform "${PLATFORM_NAME}" --minimum-deployment-target "${!DEPLOYMENT_TARGET_SETTING_NAME}" ${TARGET_DEVICE_ARGS} --compress-pngs --compile "${BUILT_PRODUCTS_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
106 | fi
107 |
--------------------------------------------------------------------------------
/Pods/Target Support Files/Pods-UI/Pods-UI-umbrella.h:
--------------------------------------------------------------------------------
1 | #ifdef __OBJC__
2 | #import
3 | #else
4 | #ifndef FOUNDATION_EXPORT
5 | #if defined(__cplusplus)
6 | #define FOUNDATION_EXPORT extern "C"
7 | #else
8 | #define FOUNDATION_EXPORT extern
9 | #endif
10 | #endif
11 | #endif
12 |
13 |
14 | FOUNDATION_EXPORT double Pods_UIVersionNumber;
15 | FOUNDATION_EXPORT const unsigned char Pods_UIVersionString[];
16 |
17 |
--------------------------------------------------------------------------------
/Pods/Target Support Files/Pods-UI/Pods-UI.debug.xcconfig:
--------------------------------------------------------------------------------
1 | FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/Layoutless"
2 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
3 | LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' '@executable_path/../../Frameworks'
4 | OTHER_CFLAGS = $(inherited) -iquote "${PODS_CONFIGURATION_BUILD_DIR}/Layoutless/Layoutless.framework/Headers"
5 | OTHER_LDFLAGS = $(inherited) -framework "Layoutless"
6 | OTHER_SWIFT_FLAGS = $(inherited) "-D" "COCOAPODS"
7 | PODS_BUILD_DIR = ${BUILD_DIR}
8 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
9 | PODS_PODFILE_DIR_PATH = ${SRCROOT}/..
10 | PODS_ROOT = ${SRCROOT}/../Pods
11 |
--------------------------------------------------------------------------------
/Pods/Target Support Files/Pods-UI/Pods-UI.modulemap:
--------------------------------------------------------------------------------
1 | framework module Pods_UI {
2 | umbrella header "Pods-UI-umbrella.h"
3 |
4 | export *
5 | module * { export * }
6 | }
7 |
--------------------------------------------------------------------------------
/Pods/Target Support Files/Pods-UI/Pods-UI.release.xcconfig:
--------------------------------------------------------------------------------
1 | FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/Layoutless"
2 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
3 | LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' '@executable_path/../../Frameworks'
4 | OTHER_CFLAGS = $(inherited) -iquote "${PODS_CONFIGURATION_BUILD_DIR}/Layoutless/Layoutless.framework/Headers"
5 | OTHER_LDFLAGS = $(inherited) -framework "Layoutless"
6 | OTHER_SWIFT_FLAGS = $(inherited) "-D" "COCOAPODS"
7 | PODS_BUILD_DIR = ${BUILD_DIR}
8 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
9 | PODS_PODFILE_DIR_PATH = ${SRCROOT}/..
10 | PODS_ROOT = ${SRCROOT}/../Pods
11 |
--------------------------------------------------------------------------------
/Pods/Target Support Files/Pods-UITests/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | ${EXECUTABLE_NAME}
9 | CFBundleIdentifier
10 | ${PRODUCT_BUNDLE_IDENTIFIER}
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | ${PRODUCT_NAME}
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | 1.0.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | ${CURRENT_PROJECT_VERSION}
23 | NSPrincipalClass
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/Pods/Target Support Files/Pods-UITests/Pods-UITests-acknowledgements.markdown:
--------------------------------------------------------------------------------
1 | # Acknowledgements
2 | This application makes use of the following third party libraries:
3 | Generated by CocoaPods - https://cocoapods.org
4 |
--------------------------------------------------------------------------------
/Pods/Target Support Files/Pods-UITests/Pods-UITests-acknowledgements.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PreferenceSpecifiers
6 |
7 |
8 | FooterText
9 | This application makes use of the following third party libraries:
10 | Title
11 | Acknowledgements
12 | Type
13 | PSGroupSpecifier
14 |
15 |
16 | FooterText
17 | Generated by CocoaPods - https://cocoapods.org
18 | Title
19 |
20 | Type
21 | PSGroupSpecifier
22 |
23 |
24 | StringsTable
25 | Acknowledgements
26 | Title
27 | Acknowledgements
28 |
29 |
30 |
--------------------------------------------------------------------------------
/Pods/Target Support Files/Pods-UITests/Pods-UITests-dummy.m:
--------------------------------------------------------------------------------
1 | #import
2 | @interface PodsDummy_Pods_UITests : NSObject
3 | @end
4 | @implementation PodsDummy_Pods_UITests
5 | @end
6 |
--------------------------------------------------------------------------------
/Pods/Target Support Files/Pods-UITests/Pods-UITests-umbrella.h:
--------------------------------------------------------------------------------
1 | #ifdef __OBJC__
2 | #import
3 | #else
4 | #ifndef FOUNDATION_EXPORT
5 | #if defined(__cplusplus)
6 | #define FOUNDATION_EXPORT extern "C"
7 | #else
8 | #define FOUNDATION_EXPORT extern
9 | #endif
10 | #endif
11 | #endif
12 |
13 |
14 | FOUNDATION_EXPORT double Pods_UITestsVersionNumber;
15 | FOUNDATION_EXPORT const unsigned char Pods_UITestsVersionString[];
16 |
17 |
--------------------------------------------------------------------------------
/Pods/Target Support Files/Pods-UITests/Pods-UITests.debug.xcconfig:
--------------------------------------------------------------------------------
1 | FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/Layoutless"
2 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
3 | LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks'
4 | OTHER_CFLAGS = $(inherited) -iquote "${PODS_CONFIGURATION_BUILD_DIR}/Layoutless/Layoutless.framework/Headers"
5 | PODS_BUILD_DIR = ${BUILD_DIR}
6 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
7 | PODS_PODFILE_DIR_PATH = ${SRCROOT}/..
8 | PODS_ROOT = ${SRCROOT}/../Pods
9 |
--------------------------------------------------------------------------------
/Pods/Target Support Files/Pods-UITests/Pods-UITests.modulemap:
--------------------------------------------------------------------------------
1 | framework module Pods_UITests {
2 | umbrella header "Pods-UITests-umbrella.h"
3 |
4 | export *
5 | module * { export * }
6 | }
7 |
--------------------------------------------------------------------------------
/Pods/Target Support Files/Pods-UITests/Pods-UITests.release.xcconfig:
--------------------------------------------------------------------------------
1 | FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/Layoutless"
2 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
3 | LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks'
4 | OTHER_CFLAGS = $(inherited) -iquote "${PODS_CONFIGURATION_BUILD_DIR}/Layoutless/Layoutless.framework/Headers"
5 | PODS_BUILD_DIR = ${BUILD_DIR}
6 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
7 | PODS_PODFILE_DIR_PATH = ${SRCROOT}/..
8 | PODS_ROOT = ${SRCROOT}/../Pods
9 |
--------------------------------------------------------------------------------
/Pods/Target Support Files/Pods-UITestsSupport/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | ${EXECUTABLE_NAME}
9 | CFBundleIdentifier
10 | ${PRODUCT_BUNDLE_IDENTIFIER}
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | ${PRODUCT_NAME}
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | 1.0.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | ${CURRENT_PROJECT_VERSION}
23 | NSPrincipalClass
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/Pods/Target Support Files/Pods-UITestsSupport/Pods-UITestsSupport-acknowledgements.markdown:
--------------------------------------------------------------------------------
1 | # Acknowledgements
2 | This application makes use of the following third party libraries:
3 | Generated by CocoaPods - https://cocoapods.org
4 |
--------------------------------------------------------------------------------
/Pods/Target Support Files/Pods-UITestsSupport/Pods-UITestsSupport-acknowledgements.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PreferenceSpecifiers
6 |
7 |
8 | FooterText
9 | This application makes use of the following third party libraries:
10 | Title
11 | Acknowledgements
12 | Type
13 | PSGroupSpecifier
14 |
15 |
16 | FooterText
17 | Generated by CocoaPods - https://cocoapods.org
18 | Title
19 |
20 | Type
21 | PSGroupSpecifier
22 |
23 |
24 | StringsTable
25 | Acknowledgements
26 | Title
27 | Acknowledgements
28 |
29 |
30 |
--------------------------------------------------------------------------------
/Pods/Target Support Files/Pods-UITestsSupport/Pods-UITestsSupport-dummy.m:
--------------------------------------------------------------------------------
1 | #import
2 | @interface PodsDummy_Pods_UITestsSupport : NSObject
3 | @end
4 | @implementation PodsDummy_Pods_UITestsSupport
5 | @end
6 |
--------------------------------------------------------------------------------
/Pods/Target Support Files/Pods-UITestsSupport/Pods-UITestsSupport-umbrella.h:
--------------------------------------------------------------------------------
1 | #ifdef __OBJC__
2 | #import
3 | #else
4 | #ifndef FOUNDATION_EXPORT
5 | #if defined(__cplusplus)
6 | #define FOUNDATION_EXPORT extern "C"
7 | #else
8 | #define FOUNDATION_EXPORT extern
9 | #endif
10 | #endif
11 | #endif
12 |
13 |
14 | FOUNDATION_EXPORT double Pods_UITestsSupportVersionNumber;
15 | FOUNDATION_EXPORT const unsigned char Pods_UITestsSupportVersionString[];
16 |
17 |
--------------------------------------------------------------------------------
/Pods/Target Support Files/Pods-UITestsSupport/Pods-UITestsSupport.debug.xcconfig:
--------------------------------------------------------------------------------
1 | FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/Layoutless"
2 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
3 | LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' '@executable_path/../../Frameworks'
4 | OTHER_CFLAGS = $(inherited) -iquote "${PODS_CONFIGURATION_BUILD_DIR}/Layoutless/Layoutless.framework/Headers"
5 | PODS_BUILD_DIR = ${BUILD_DIR}
6 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
7 | PODS_PODFILE_DIR_PATH = ${SRCROOT}/..
8 | PODS_ROOT = ${SRCROOT}/../Pods
9 |
--------------------------------------------------------------------------------
/Pods/Target Support Files/Pods-UITestsSupport/Pods-UITestsSupport.modulemap:
--------------------------------------------------------------------------------
1 | framework module Pods_UITestsSupport {
2 | umbrella header "Pods-UITestsSupport-umbrella.h"
3 |
4 | export *
5 | module * { export * }
6 | }
7 |
--------------------------------------------------------------------------------
/Pods/Target Support Files/Pods-UITestsSupport/Pods-UITestsSupport.release.xcconfig:
--------------------------------------------------------------------------------
1 | FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/Layoutless"
2 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
3 | LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' '@executable_path/../../Frameworks'
4 | OTHER_CFLAGS = $(inherited) -iquote "${PODS_CONFIGURATION_BUILD_DIR}/Layoutless/Layoutless.framework/Headers"
5 | PODS_BUILD_DIR = ${BUILD_DIR}
6 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
7 | PODS_PODFILE_DIR_PATH = ${SRCROOT}/..
8 | PODS_ROOT = ${SRCROOT}/../Pods
9 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 🏛 Feature Driven Architecture
2 | An iOS example project for a use case-driven architecture.
3 |
4 | ## ☝️ What is inside?
5 |
6 | The simple Blog application is featuring:
7 |
8 | - Feature-driven architecture
9 | - Modularization example
10 |
11 |
12 | ## ✌️ Who is it for?
13 |
14 | The project helps to understand:
15 |
16 | - How to stop building UI driven-apps
17 | - How to create an app from the composition of small independent blocks
18 | - How to describe an app flow in a natural, readable manner
19 | - How to split an app into modules/frameworks
20 |
21 | ## ⚙️ Modules
22 |
23 |
24 |
25 | ## 🌟 Features
26 | Features describe single use-case or presentation requirement
27 |
28 | Features:
29 |
30 | - Have one job
31 | - Independent of other features
32 | - Make decisions (if, switch, etc.)
33 | - Might have no UI or add UI later
34 | - Decide own lifetime
35 |
36 | Features describing *use cases*:
37 |
38 | - Login screen (Persistent session)
39 | - Logout button
40 | - Posts screen
41 | - Comments screen
42 | - Push notifications handling
43 |
44 | Features describing *presentation*:
45 |
46 | - Window
47 | - Tab Bar
48 | - Navigation Bar
49 |
50 | ## 🔱 Flows
51 |
52 | Flows describe the composition of features.
53 |
54 | Flows are:
55 |
56 | - Readable
57 | - Makes no decisions (if, switch, etc.)
58 | - Doesn’t make assumptions about Feature lifetimes
59 |
60 | 
61 |
--------------------------------------------------------------------------------
/UI/Resources/TestsInfo.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | BNDL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 |
22 |
23 |
--------------------------------------------------------------------------------
/UI/Sources/Components/StringsTableViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // StringsTableViewController.swift
3 | // UI
4 | //
5 | // Created by Bohdan Orlov on 26/03/2018.
6 | // Copyright © 2018 Bohdan Orlov. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import UIKit
11 | import Layoutless
12 |
13 | public protocol StringsRendering {
14 | var data: StringsData { get set }
15 | }
16 |
17 | public struct StringsData {
18 | let strings: [String]
19 |
20 | public init(strings: [String]) {
21 | self.strings = strings
22 | }
23 | }
24 |
25 | public typealias StringsRenderingViewController = StringsRendering & UIViewController
26 |
27 | public class StringsTableViewController: UIViewController, StringsRendering {
28 | public var data: StringsData = StringsData(strings: []) {
29 | didSet {
30 | self.tableView.reloadData()
31 | }
32 | }
33 |
34 | override public func viewDidLoad() {
35 | super.viewDidLoad()
36 | let layout = self.tableView.fillingParent()
37 | layout.layout(in: self.view)
38 | }
39 |
40 | private lazy var tableView: UITableView = {
41 | let tableView = UITableView()
42 | tableView.translatesAutoresizingMaskIntoConstraints = false
43 | tableView.dataSource = self
44 | tableView.rowHeight = 90
45 | return tableView
46 | }()
47 | }
48 |
49 | extension StringsTableViewController: UITableViewDataSource {
50 |
51 | public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
52 | return self.data.strings.count
53 | }
54 |
55 | public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
56 | // Note: no cell reuse, don't do this for production:
57 | let cell = UITableViewCell(style: .default, reuseIdentifier: nil)
58 | cell.textLabel?.numberOfLines = 3
59 | cell.textLabel?.text = self.data.strings[indexPath.row]
60 | return cell
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/UI/Sources/Presentation/RootViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ViewController.swift
3 | // Architecture
4 | //
5 | // Created by Bohdan Orlov on 27/02/2018.
6 | // Copyright © 2018 Bohdan Orlov. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | public class RootViewController: UIViewController {
12 | public var didAppear: (() -> Void)?
13 |
14 | public override func viewWillAppear(_ animated: Bool) {
15 | super.viewWillAppear(animated)
16 | }
17 |
18 | public override func viewDidAppear(_ animated: Bool) {
19 | super.viewDidAppear(animated)
20 | self.didAppear?()
21 | self.didAppear = nil
22 | }
23 |
24 | public override func didReceiveMemoryWarning() {
25 | super.didReceiveMemoryWarning()
26 | // Dispose of any resources that can be recreated.
27 | }
28 |
29 | }
30 |
31 |
--------------------------------------------------------------------------------
/UI/Sources/Presentation/UIWindowOwner.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIWindowOwner.swift
3 | // UI
4 | //
5 | // Created by Bohdan Orlov on 05/04/2018.
6 | // Copyright © 2018 Bohdan Orlov. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import UIKit
11 |
12 | public protocol UIWindowOwner: AnyObject {
13 | var windows: [String: UIWindow] { get set }
14 | }
15 |
--------------------------------------------------------------------------------
/UI/Sources/Presentation/ViewControllerPresenter.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ViewControllerPresenter.swift
3 | // Architecture
4 | //
5 | // Created by Bohdan Orlov on 01/03/2018.
6 | // Copyright © 2018 Bohdan Orlov. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import UIKit
11 |
12 | public protocol ViewControllerPresenting: AnyObject {
13 | func present(viewController: UIViewController, completion: @escaping () -> Void)
14 | func dismiss(viewController: UIViewController, completion: @escaping () -> Void)
15 | }
16 |
17 | public class ViewControllerPresenter: ViewControllerPresenting {
18 |
19 | private weak var rootViewController: UIViewController?
20 | private let application: UIApplication
21 |
22 | public init(rootViewController: UIViewController, application: UIApplication) {
23 | self.rootViewController = rootViewController
24 | self.application = application
25 | }
26 |
27 | public func present(viewController: UIViewController, completion: @escaping () -> Void) {
28 | if alreadyPresented(viewController) {
29 | self.rewind(to: viewController, completion: completion)
30 | return
31 | }
32 | guard let rootViewController = self.rootViewController else {
33 | assertionFailure()
34 | completion()
35 | return
36 | }
37 | present(rootViewController: rootViewController, viewController: viewController, completion)
38 | }
39 |
40 | public func dismiss(viewController: UIViewController, completion: @escaping () -> Void) {
41 | if let presentingViewController = viewController.presentingViewController {
42 | presentingViewController.dismiss(animated: true, completion: completion)
43 | } else {
44 | viewController.remove()
45 | }
46 | }
47 |
48 | private func alreadyPresented(_ viewController: UIViewController) -> Bool {
49 | return viewController.presentingViewController != nil || viewController.parent != nil
50 | }
51 |
52 | private func rewind(to viewController: UIViewController, completion: @escaping () -> Void) {
53 | if viewController.presentedViewController != nil {
54 | viewController.dismiss(animated: true, completion: completion)
55 | } else {
56 | completion()
57 | }
58 | }
59 |
60 | private func present(rootViewController: UIViewController,
61 | viewController: UIViewController,
62 | _ completion: @escaping () -> Void) {
63 | if let navigationController = rootViewController as? UINavigationController {
64 | navigationController.pushViewController(viewController, animated: navigationController.viewControllers.count > 0)
65 | completion()
66 | return
67 | }
68 | presentAsFullscreen(rootViewController: rootViewController, viewController: viewController, completion)
69 | }
70 |
71 | private func presentAsFullscreen(rootViewController: UIViewController,
72 | viewController: UIViewController,
73 | _ completion: @escaping () -> Void) {
74 | let viewControllerForModalPresentation = rootViewController.viewControllerForModalPresentation!
75 | if (viewControllerForModalPresentation.isEmpty) {
76 | viewControllerForModalPresentation.add(viewController)
77 | completion()
78 | } else {
79 | let appIsActive = (application.applicationState == .active)
80 | viewControllerForModalPresentation.present(viewController, animated: appIsActive, completion: completion)
81 | }
82 | }
83 | }
84 |
85 | extension UIViewController {
86 | public var isEmpty: Bool {
87 | return self.view.subviews.isEmpty && self.childViewControllers.isEmpty && self.presentedViewController == nil
88 | }
89 |
90 | public var viewControllerForModalPresentation: UIViewController? {
91 | if let presentedViewController = self.presentedViewController {
92 | return presentedViewController.viewControllerForModalPresentation
93 | } else {
94 | return self
95 | }
96 | }
97 | }
98 |
99 | extension UIViewController {
100 | public func add(_ child: UIViewController) {
101 | addChildViewController(child)
102 | view.addSubview(child.view)
103 | child.didMove(toParentViewController: self)
104 | child.view.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
105 | child.view.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
106 | child.view.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
107 | child.view.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
108 | }
109 |
110 | public func remove() {
111 | guard parent != nil else {
112 | return
113 | }
114 | willMove(toParentViewController: nil)
115 | removeFromParentViewController()
116 | view.removeFromSuperview()
117 | }
118 | }
119 |
--------------------------------------------------------------------------------
/UI/Sources/Presentation/ViewPresenter.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ViewPresenter.swift
3 | // Architecture
4 | //
5 | // Created by Bohdan Orlov on 03/03/2018.
6 | // Copyright © 2018 Bohdan Orlov. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import UIKit
11 |
12 | public protocol ViewPresenting: AnyObject {
13 | func present(view: UIView)
14 | func dismiss(view: UIView)
15 | }
16 |
17 | public class ViewPresenter: ViewPresenting {
18 |
19 | private weak var rootView: UIView?
20 |
21 | public init(rootView: UIView) {
22 | self.rootView = rootView
23 | }
24 |
25 | public func present(view: UIView) {
26 | guard let rootView = self.rootView else { return }
27 | rootView.addSubview(view)
28 | view.leftAnchor.constraint(equalTo: rootView.leftAnchor).isActive = true
29 | view.topAnchor.constraint(equalTo: rootView.topAnchor).isActive = true
30 | view.rightAnchor.constraint(equalTo: rootView.rightAnchor).isActive = true
31 | view.bottomAnchor.constraint(equalTo: rootView.bottomAnchor).isActive = true
32 | }
33 |
34 | public func dismiss(view: UIView) {
35 | view.removeFromSuperview()
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/UI/Sources/UIKitExtensions/UIControl+ClosureCallback.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIControl+ClosureCallback.swift
3 | // UI
4 | //
5 | // Created by Bohdan Orlov on 25/03/2018.
6 | // Copyright © 2018 Bohdan Orlov. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import UIKit
11 | import Core
12 |
13 | class ClosureBox {
14 | let closure: ()->()
15 |
16 | init (_ closure: @escaping ()->()) {
17 | self.closure = closure
18 | }
19 |
20 | @objc func invoke () {
21 | closure()
22 | }
23 |
24 | }
25 |
26 | extension UIControl {
27 | public func add (for controlEvents: UIControlEvents, _ closure: @escaping ()->()) {
28 | let closureBox = ClosureBox(closure)
29 | addTarget(closureBox, action: #selector(ClosureBox.invoke), for: controlEvents)
30 | self.retain(closureBox)
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/UI/UI.xcodeproj/xcshareddata/xcschemes/UI.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
31 |
32 |
34 |
40 |
41 |
42 |
43 |
44 |
50 |
51 |
52 |
53 |
54 |
55 |
66 |
67 |
73 |
74 |
75 |
76 |
77 |
78 |
84 |
85 |
91 |
92 |
93 |
94 |
96 |
97 |
100 |
101 |
102 |
--------------------------------------------------------------------------------
/flow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BohdanOrlov/Feature-Driven-Architecture/3881efb933296ce14a929664d7e5e92ca475344c/flow.png
--------------------------------------------------------------------------------
/modules.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BohdanOrlov/Feature-Driven-Architecture/3881efb933296ce14a929664d7e5e92ca475344c/modules.png
--------------------------------------------------------------------------------