├── .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 | ![UI Flow](flow.png) 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 --------------------------------------------------------------------------------