diff --git a/Podfile b/Podfile index 90c9fb3..f3a904c 100644 --- a/Podfile +++ b/Podfile @@ -2,19 +2,29 @@ platform :ios, '8.0' target 'RxTodo' do use_frameworks! + inhibit_all_warnings! # Rx - pod 'RxSwift', '2.5.0' - pod 'RxCocoa', '2.5.0' - pod 'RxDataSources', '0.8.1' + pod 'RxSwift', '3.3.1' + pod 'RxCocoa', '3.3.1' + pod 'RxDataSources', '1.0.3' + pod 'RxSwiftExt', '2.1.0' # UI - pod 'SnapKit', '0.21.1' - pod 'ManualLayout', '1.2.1' + pod 'SnapKit', '3.2.0' + pod 'ManualLayout', '1.3.0' # Misc. - pod 'Then', '1.0.3' - pod 'ReusableKit', '0.3.0' - pod 'CGFloatLiteral', '0.1.0' + pod 'Then', '2.1.0' + pod 'ReusableKit', '1.1.0' + pod 'CGFloatLiteral', '0.3.0' end + +post_install do |installer| + installer.pods_project.targets.each do |target| + target.build_configurations.each do |config| + config.build_settings['SWIFT_VERSION'] = '3.0' + end + end +end diff --git a/RxTodo.xcodeproj/project.pbxproj b/RxTodo.xcodeproj/project.pbxproj index bff9120..1628d86 100644 --- a/RxTodo.xcodeproj/project.pbxproj +++ b/RxTodo.xcodeproj/project.pbxproj @@ -229,11 +229,12 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0730; - LastUpgradeCheck = 0730; + LastUpgradeCheck = 0820; ORGANIZATIONNAME = "Suyeol Jeon"; TargetAttributes = { 03D0C0AE1D269F7900EE93D5 = { CreatedOnToolsVersion = 7.3.1; + LastSwiftMigration = 0820; }; }; }; @@ -367,8 +368,10 @@ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; @@ -412,8 +415,10 @@ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; @@ -432,6 +437,7 @@ IPHONEOS_DEPLOYMENT_TARGET = 8.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; VALIDATE_PRODUCT = YES; }; name = Release; @@ -440,11 +446,13 @@ isa = XCBuildConfiguration; baseConfigurationReference = 92FD2F879471E3FA186E82B7 /* Pods-RxTodo.debug.xcconfig */; buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; INFOPLIST_FILE = "$(SRCROOT)/RxTodo/Support/Info.plist"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = kr.xoul.RxTodo; PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 3.0; }; name = Debug; }; @@ -452,11 +460,13 @@ isa = XCBuildConfiguration; baseConfigurationReference = 2C8D148321B73000A720563F /* Pods-RxTodo.release.xcconfig */; buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; INFOPLIST_FILE = "$(SRCROOT)/RxTodo/Support/Info.plist"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = kr.xoul.RxTodo; PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 3.0; }; name = Release; }; diff --git a/RxTodo/Sources/AppDelegate.swift b/RxTodo/Sources/AppDelegate.swift index 75d1c38..497b71e 100644 --- a/RxTodo/Sources/AppDelegate.swift +++ b/RxTodo/Sources/AppDelegate.swift @@ -19,10 +19,10 @@ class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? - func application(application: UIApplication, - didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { - let window = UIWindow(frame: UIScreen.mainScreen().bounds) - window.backgroundColor = .whiteColor() + func application(_ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { + let window = UIWindow(frame: UIScreen.main.bounds) + window.backgroundColor = .white window.makeKeyAndVisible() let viewModel = TaskListViewModel() diff --git a/RxTodo/Sources/Models/ModelType.swift b/RxTodo/Sources/Models/ModelType.swift index ade2811..60a1718 100644 --- a/RxTodo/Sources/Models/ModelType.swift +++ b/RxTodo/Sources/Models/ModelType.swift @@ -16,10 +16,10 @@ protocol Identifiable { protocol ModelType: Then { } -extension CollectionType where Generator.Element: Identifiable { +extension Collection where Iterator.Element: Identifiable { - func indexOf(element: Self.Generator.Element) -> Self.Index? { - return self.indexOf { $0.id == element.id } + func indexOf(_ element: Self.Iterator.Element) -> Self.Index? { + return self.index { $0.id == element.id } } } diff --git a/RxTodo/Sources/Models/Task.swift b/RxTodo/Sources/Models/Task.swift index 078ba54..4ecc55f 100644 --- a/RxTodo/Sources/Models/Task.swift +++ b/RxTodo/Sources/Models/Task.swift @@ -8,9 +8,9 @@ import Foundation -struct Task: ModelType, Identifiable { +class Task: ModelType, Identifiable { - var id: String = NSUUID().UUIDString + var id: String = UUID().uuidString var title: String var memo: String? diff --git a/RxTodo/Sources/Rx/RxOperators.swift b/RxTodo/Sources/Rx/RxOperators.swift index 4db49e0..fa44b31 100644 --- a/RxTodo/Sources/Rx/RxOperators.swift +++ b/RxTodo/Sources/Rx/RxOperators.swift @@ -9,6 +9,7 @@ import Foundation #if !RX_NO_MODULE import RxSwift +import RxSwiftExt import RxCocoa #endif @@ -16,49 +17,27 @@ import UIKit // Two way binding operator between control property and variable, that's all it takes { -infix operator <-> { -} - -func nonMarkedText(textInput: UITextInput) -> String? { - let start = textInput.beginningOfDocument - let end = textInput.endOfDocument - - guard let rangeAll = textInput.textRangeFromPosition(start, toPosition: end), - text = textInput.textInRange(rangeAll) else { - return nil - } - - guard let markedTextRange = textInput.markedTextRange else { - return text - } - - guard let startRange = textInput.textRangeFromPosition(start, toPosition: markedTextRange.start), - endRange = textInput.textRangeFromPosition(markedTextRange.end, toPosition: end) else { - return text - } - - return (textInput.textInRange(startRange) ?? "") + (textInput.textInRange(endRange) ?? "") -} +infix operator <-> -func <-> (textInput: RxTextInput, variable: Variable) -> Disposable { +func <-> (textInput: UITextField, variable: Variable) -> Disposable { let bindToUIDisposable = variable.asObservable() - .bindTo(textInput.rx_text) - let bindToVariable = textInput.rx_text + .bindTo(textInput.rx.text) + let bindToVariable = textInput.rx.text .subscribe(onNext: { [weak textInput] n in guard let textInput = textInput else { return } - let nonMarkedTextValue = nonMarkedText(textInput) + let inputText = textInput.text - if nonMarkedTextValue != variable.value { - variable.value = nonMarkedTextValue ?? "" + if inputText != variable.value { + variable.value = inputText ?? "" } }, onCompleted: { bindToUIDisposable.dispose() }) - return StableCompositeDisposable.create(bindToUIDisposable, bindToVariable) + return CompositeDisposable(bindToUIDisposable, bindToVariable) } func <-> (property: ControlProperty, variable: Variable) -> Disposable { @@ -81,7 +60,7 @@ func <-> (property: ControlProperty, variable: Variable) -> Disposable bindToUIDisposable.dispose() }) - return StableCompositeDisposable.create(bindToUIDisposable, bindToVariable) + return CompositeDisposable(bindToUIDisposable, bindToVariable) } // } diff --git a/RxTodo/Sources/Services/ModelService.swift b/RxTodo/Sources/Services/ModelService.swift index 1edd4b9..39f9137 100644 --- a/RxTodo/Sources/Services/ModelService.swift +++ b/RxTodo/Sources/Services/ModelService.swift @@ -10,14 +10,14 @@ import RxSwift private var _instances = [String: Any]() -struct ModelService { +class ModelService { let didCreate = PublishSubject() let didUpdate = PublishSubject() let didDelete = PublishSubject() - static func instance(modelClass: Model.Type) -> ModelService { - let key = String(modelClass) + static func instance(_ modelClass: Model.Type) -> ModelService { + let key = String(describing: modelClass) if let stream = _instances[key] as? ModelService { return stream } @@ -31,15 +31,15 @@ struct ModelService { extension ModelType { static var didCreate: PublishSubject { - return ModelService.instance(Self).didCreate + return ModelService.instance(Self.self).didCreate } static var didUpdate: PublishSubject { - return ModelService.instance(Self).didUpdate + return ModelService.instance(Self.self).didUpdate } static var didDelete: PublishSubject { - return ModelService.instance(Self).didDelete + return ModelService.instance(Self.self).didDelete } } diff --git a/RxTodo/Sources/Utils/Snap.swift b/RxTodo/Sources/Utils/Snap.swift index bc22c21..f6b355e 100644 --- a/RxTodo/Sources/Utils/Snap.swift +++ b/RxTodo/Sources/Utils/Snap.swift @@ -9,19 +9,19 @@ import UIKit /// Ceil to snap pixel -func snap(x: CGFloat) -> CGFloat { - let scale = UIScreen.mainScreen().scale +func snap(_ x: CGFloat) -> CGFloat { + let scale = UIScreen.main.scale return ceil(x * scale) / scale } -func snap(point: CGPoint) -> CGPoint { +func snap(_ point: CGPoint) -> CGPoint { return CGPoint(x: snap(point.x), y: snap(point.y)) } -func snap(size: CGSize) -> CGSize { +func snap(_ size: CGSize) -> CGSize { return CGSize(width: snap(size.width), height: snap(size.height)) } -func snap(rect: CGRect) -> CGRect { +func snap(_ rect: CGRect) -> CGRect { return CGRect(origin: snap(rect.origin), size: snap(rect.size)) } diff --git a/RxTodo/Sources/Utils/String+BoundingRect.swift b/RxTodo/Sources/Utils/String+BoundingRect.swift index 3ce29b4..a9d6d87 100644 --- a/RxTodo/Sources/Utils/String+BoundingRect.swift +++ b/RxTodo/Sources/Utils/String+BoundingRect.swift @@ -10,12 +10,12 @@ import UIKit extension String { - func boundingRectWithSize(size: CGSize, attributes: [String: AnyObject]) -> CGRect { - let options: NSStringDrawingOptions = [.UsesLineFragmentOrigin, .UsesFontLeading] - return snap(self.boundingRectWithSize(size, options: options, attributes: attributes, context: nil)) + func boundingRectWithSize(_ size: CGSize, attributes: [String: AnyObject]) -> CGRect { + let options: NSStringDrawingOptions = [.usesLineFragmentOrigin, .usesFontLeading] + return snap(self.boundingRect(with: size, options: options, attributes: attributes, context: nil)) } - func sizeThatFits(size: CGSize, font: UIFont, maximumNumberOfLines: Int = 0) -> CGSize { + func sizeThatFits(_ size: CGSize, font: UIFont, maximumNumberOfLines: Int = 0) -> CGSize { let attributes = [NSFontAttributeName: font] var size = self.boundingRectWithSize(size, attributes: attributes).size if maximumNumberOfLines > 0 { @@ -24,13 +24,13 @@ extension String { return snap(size) } - func widthWithFont(font: UIFont, maximumNumberOfLines: Int = 0) -> CGFloat { - let size = CGSize(width: CGFloat.max, height: CGFloat.max) + func widthWithFont(_ font: UIFont, maximumNumberOfLines: Int = 0) -> CGFloat { + let size = CGSize(width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude) return snap(self.sizeThatFits(size, font: font, maximumNumberOfLines: maximumNumberOfLines).width) } - func heightThatFitsWidth(width: CGFloat, font: UIFont, maximumNumberOfLines: Int = 0) -> CGFloat { - let size = CGSize(width: width, height: CGFloat.max) + func heightThatFitsWidth(_ width: CGFloat, font: UIFont, maximumNumberOfLines: Int = 0) -> CGFloat { + let size = CGSize(width: width, height: CGFloat.greatestFiniteMagnitude) return snap(self.sizeThatFits(size, font: font, maximumNumberOfLines: maximumNumberOfLines).height) } diff --git a/RxTodo/Sources/ViewControllers/BaseViewController.swift b/RxTodo/Sources/ViewControllers/BaseViewController.swift index 3a16f76..dddf10f 100644 --- a/RxTodo/Sources/ViewControllers/BaseViewController.swift +++ b/RxTodo/Sources/ViewControllers/BaseViewController.swift @@ -30,7 +30,7 @@ class BaseViewController: UIViewController { // MARK: Layout Constraints - private(set) var didSetupConstraints = false + fileprivate(set) var didSetupConstraints = false override func viewDidLoad() { self.view.setNeedsUpdateConstraints() diff --git a/RxTodo/Sources/ViewControllers/TaskEditViewController.swift b/RxTodo/Sources/ViewControllers/TaskEditViewController.swift index 2e617d2..c905232 100644 --- a/RxTodo/Sources/ViewControllers/TaskEditViewController.swift +++ b/RxTodo/Sources/ViewControllers/TaskEditViewController.swift @@ -7,6 +7,7 @@ // import UIKit +import RxSwiftExt final class TaskEditViewController: BaseViewController { @@ -15,35 +16,37 @@ final class TaskEditViewController: BaseViewController { struct Metric { static let padding = 15.f static let titleInputCornerRadius = 5.f - static let titleInputBorderWidth = 1 / UIScreen.mainScreen().scale + static let titleInputBorderWidth = 1 / UIScreen.main.scale } struct Font { - static let titleLabel = UIFont.systemFontOfSize(14) + static let titleLabel = UIFont.systemFont(ofSize: 14) } struct Color { - static let titleInputBorder = UIColor.lightGrayColor() + static let titleInputBorder = UIColor.lightGray } // MARK: Properties - - let cancelBarButtonItem = UIBarButtonItem(barButtonSystemItem: .Cancel, target: nil, action: Selector()) - let doneBarButtonItem = UIBarButtonItem(barButtonSystemItem: .Done, target: nil, action: Selector()) + let viewModel: TaskEditViewModelType + let cancelBarButtonItem = UIBarButtonItem(barButtonSystemItem: .cancel, target: nil, action: nil) + let doneBarButtonItem = UIBarButtonItem(barButtonSystemItem: .done, target: nil, action: nil) let titleInput = UITextField().then { - $0.autocorrectionType = .No + $0.autocorrectionType = .no $0.font = Font.titleLabel $0.layer.cornerRadius = Metric.titleInputCornerRadius $0.layer.borderWidth = Metric.titleInputBorderWidth - $0.layer.borderColor = Color.titleInputBorder.CGColor + $0.layer.borderColor = Color.titleInputBorder.cgColor } // MARK: Initializing init(viewModel: TaskEditViewModelType) { + self.viewModel = viewModel super.init() + self.navigationItem.leftBarButtonItem = self.cancelBarButtonItem self.navigationItem.rightBarButtonItem = self.doneBarButtonItem self.configure(viewModel) @@ -58,13 +61,13 @@ final class TaskEditViewController: BaseViewController { override func viewDidLoad() { super.viewDidLoad() - self.view.backgroundColor = .whiteColor() + self.view.backgroundColor = .white self.view.addSubview(self.titleInput) self.titleInput.becomeFirstResponder() } override func setupConstraints() { - self.titleInput.snp_makeConstraints { make in + self.titleInput.snp.makeConstraints { make in make.top.equalTo(20 + 44 + Metric.padding) make.left.equalTo(Metric.padding) make.right.equalTo(-Metric.padding) @@ -74,53 +77,53 @@ final class TaskEditViewController: BaseViewController { // MARK: Configuring - private func configure(viewModel: TaskEditViewModelType) { + fileprivate func configure(_ viewModel: TaskEditViewModelType) { // 2-Way Binding - (self.titleInput.rx_text <-> viewModel.title) + (self.titleInput <-> viewModel.title) .addDisposableTo(self.disposeBag) // Input - self.cancelBarButtonItem.rx_tap + self.cancelBarButtonItem.rx.tap .bindTo(viewModel.cancelButtonDidTap) .addDisposableTo(self.disposeBag) - self.doneBarButtonItem.rx_tap + self.doneBarButtonItem.rx.tap .bindTo(viewModel.doneButtonDidTap) .addDisposableTo(self.disposeBag) // Output viewModel.navigationBarTitle - .drive(self.navigationItem.rx_title) + .drive(self.navigationItem.rx.title) .addDisposableTo(self.disposeBag) viewModel.doneButtonEnabled - .drive(self.doneBarButtonItem.rx_enabled) + .drive(self.doneBarButtonItem.rx.isEnabled) .addDisposableTo(self.disposeBag) viewModel.presentCancelAlert - .driveNext { [weak self] title, message, leaveTitle, stayTitle in + .drive(onNext: { [weak self] title, message, leaveTitle, stayTitle in guard let `self` = self else { return } - let alertController = UIAlertController(title: title, message: message, preferredStyle: .Alert) + let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert) let actions = [ - UIAlertAction(title: leaveTitle, style: .Destructive) { _ in + UIAlertAction(title: leaveTitle, style: .destructive) { _ in viewModel.alertLeaveButtonDidTap.onNext() }, - UIAlertAction(title: stayTitle, style: .Default) { _ in + UIAlertAction(title: stayTitle, style: .default) { _ in self.titleInput.becomeFirstResponder() viewModel.alertStayButtonDidTap.onNext() } ] actions.forEach(alertController.addAction) self.view.endEditing(true) - self.presentViewController(alertController, animated: true, completion: nil) - } + self.present(alertController, animated: true, completion: nil) + }) .addDisposableTo(self.disposeBag) viewModel.dismissViewController - .driveNext { [weak self] in + .drive(onNext: { [weak self] in self?.view.endEditing(true) - self?.dismissViewControllerAnimated(true, completion: nil) - } + self?.dismiss(animated: true, completion: nil) + }) .addDisposableTo(self.disposeBag) } diff --git a/RxTodo/Sources/ViewControllers/TaskEditViewModel.swift b/RxTodo/Sources/ViewControllers/TaskEditViewModel.swift index 9207210..35e3251 100644 --- a/RxTodo/Sources/ViewControllers/TaskEditViewModel.swift +++ b/RxTodo/Sources/ViewControllers/TaskEditViewModel.swift @@ -10,8 +10,8 @@ import RxCocoa import RxSwift enum TaskEditViewMode { - case New - case Edit(Task) + case new + case edit(Task) } protocol TaskEditViewModelType { @@ -62,11 +62,11 @@ struct TaskEditViewModel: TaskEditViewModelType { init(mode: TaskEditViewMode) { switch mode { - case .New: + case .new: self.navigationBarTitle = .just("New") self.title = Variable("") - case .Edit(let task): + case .edit(let task): self.navigationBarTitle = .just("Edit") self.title = Variable(task.title) } @@ -80,8 +80,8 @@ struct TaskEditViewModel: TaskEditViewModelType { .withLatestFrom(self.title.asDriver()) .map { title -> Bool in switch mode { - case .New: return !title.isEmpty - case .Edit(let task): return title != task.title + case .new: return !title.isEmpty + case .edit(let task): return title != task.title } } @@ -98,11 +98,11 @@ struct TaskEditViewModel: TaskEditViewModelType { .withLatestFrom(self.title.asDriver()) .map { title in switch mode { - case .New: + case .new: let newTask = Task(title: title) Task.didCreate.onNext(newTask) - case .Edit(let task): + case .edit(let task): let newTask = task.then { $0.title = title } diff --git a/RxTodo/Sources/ViewControllers/TaskListViewController.swift b/RxTodo/Sources/ViewControllers/TaskListViewController.swift index fed605b..0397ab0 100644 --- a/RxTodo/Sources/ViewControllers/TaskListViewController.swift +++ b/RxTodo/Sources/ViewControllers/TaskListViewController.swift @@ -23,19 +23,21 @@ final class TaskListViewController: BaseViewController { // MARK: Properties - + let viewModel: TaskListViewModelType let dataSource = RxTableViewSectionedReloadDataSource() - let addBarButtonItem = UIBarButtonItem(barButtonSystemItem: .Add, target: nil, action: Selector()) + let addBarButtonItem = UIBarButtonItem(barButtonSystemItem: .add, target: nil, action: nil) let tableView = UITableView().then { - $0.registerCell(Reusable.taskCell) + $0.register(Reusable.taskCell) } // MARK: Initializing init(viewModel: TaskListViewModelType) { + self.viewModel = viewModel super.init() + self.navigationItem.rightBarButtonItem = self.addBarButtonItem self.configure(viewModel) } @@ -49,14 +51,14 @@ final class TaskListViewController: BaseViewController { override func viewDidLoad() { super.viewDidLoad() - self.view.backgroundColor = .whiteColor() - self.tableView.rx_setDelegate(self) + self.view.backgroundColor = .white + _ = self.tableView.rx.setDelegate(self) self.view.addSubview(self.tableView) } override func setupConstraints() { super.setupConstraints() - self.tableView.snp_makeConstraints { make in + self.tableView.snp.makeConstraints { make in make.edges.equalTo(0) } } @@ -64,42 +66,42 @@ final class TaskListViewController: BaseViewController { // MARK: Configuring - private func configure(viewModel: TaskListViewModelType) { + fileprivate func configure(_ viewModel: TaskListViewModelType) { self.dataSource.configureCell = { _, tableView, indexPath, viewModel in - let cell = tableView.dequeueCell(Reusable.taskCell, forIndexPath: indexPath) + let cell = tableView.dequeue(Reusable.taskCell, for: indexPath) cell.configure(viewModel) return cell } // Input - self.addBarButtonItem.rx_tap + self.addBarButtonItem.rx.tap .bindTo(viewModel.addButtonDidTap) .addDisposableTo(self.disposeBag) - self.tableView.rx_itemSelected + self.tableView.rx.itemSelected .bindTo(viewModel.itemDidSelect) .addDisposableTo(self.disposeBag) - self.tableView.rx_itemDeleted + self.tableView.rx.itemDeleted .bindTo(viewModel.itemDeleted) .addDisposableTo(self.disposeBag) // Ouput viewModel.navigationBarTitle - .drive(self.navigationItem.rx_title) + .drive(self.navigationItem.rx.title) .addDisposableTo(self.disposeBag) viewModel.sections - .drive(self.tableView.rx_itemsWithDataSource(self.dataSource)) + .drive(self.tableView.rx.items(dataSource: self.dataSource)) .addDisposableTo(self.disposeBag) viewModel.presentTaskEditViewModel - .driveNext { [weak self] viewModel in + .drive(onNext: { [weak self] viewModel in guard let `self` = self else { return } let viewController = TaskEditViewController(viewModel: viewModel) let navigationController = UINavigationController(rootViewController: viewController) - self.presentViewController(navigationController, animated: true, completion: nil) - } + self.present(navigationController, animated: true, completion: nil) + }) .addDisposableTo(self.disposeBag) } @@ -110,13 +112,15 @@ final class TaskListViewController: BaseViewController { extension TaskListViewController: UITableViewDelegate { - func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat { - let viewModel = self.dataSource.itemAtIndexPath(indexPath) - return TaskCell.cellHeightThatFitsWidth(tableView.width, viewModel: viewModel) + func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { + if let viewModel = try! self.dataSource.model(at: indexPath) as? TaskCellModelType { + return TaskCell.cellHeightThatFitsWidth(tableView.width, viewModel: viewModel) + } + return 0 } - func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) { - tableView.deselectRowAtIndexPath(indexPath, animated: true) + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + tableView.deselectRow(at: indexPath, animated: true) } } diff --git a/RxTodo/Sources/ViewControllers/TaskListViewModel.swift b/RxTodo/Sources/ViewControllers/TaskListViewModel.swift index cb96554..77efea6 100644 --- a/RxTodo/Sources/ViewControllers/TaskListViewModel.swift +++ b/RxTodo/Sources/ViewControllers/TaskListViewModel.swift @@ -16,36 +16,36 @@ protocol TaskListViewModelType { // Input var addButtonDidTap: PublishSubject { get } - var itemDidSelect: PublishSubject { get } - var itemDeleted: PublishSubject { get } + var itemDidSelect: PublishSubject { get } + var itemDeleted: PublishSubject { get } // Output var navigationBarTitle: Driver { get } var sections: Driver<[TaskListSection]> { get } - var presentTaskEditViewModel: Driver { get } + var presentTaskEditViewModel: Driver { get } } -struct TaskListViewModel: TaskListViewModelType { +class TaskListViewModel: TaskListViewModelType { // MARK: Input let addButtonDidTap = PublishSubject() - let itemDidSelect = PublishSubject() - var itemDeleted = PublishSubject() + let itemDidSelect = PublishSubject() + var itemDeleted = PublishSubject() // MARK: Output let navigationBarTitle: Driver let sections: Driver<[TaskListSection]> - let presentTaskEditViewModel: Driver + let presentTaskEditViewModel: Driver // MARK: Private - private let disposeBag = DisposeBag() - private var tasks: Variable<[Task]> + fileprivate let disposeBag = DisposeBag() + fileprivate var tasks: Variable<[Task]> init() { let defaultTasks = [ @@ -65,22 +65,22 @@ struct TaskListViewModel: TaskListViewModelType { .asDriver(onErrorJustReturn: []) self.itemDeleted - .subscribeNext { indexPath in + .subscribe(onNext: { indexPath in let task = tasks.value[indexPath.row] Task.didDelete.onNext(task) - } + }) .addDisposableTo(self.disposeBag) // // View Controller Navigations // - let presentAddViewModel: Driver = self.addButtonDidTap.asDriver() - .map { TaskEditViewModel(mode: .New) } + let presentAddViewModel: Driver = self.addButtonDidTap.asDriver() + .map { TaskEditViewModel(mode: .new) } - let presentEditViewModel: Driver = self.itemDidSelect + let presentEditViewModel: Driver = self.itemDidSelect .map { indexPath in let task = tasks.value[indexPath.row] - return TaskEditViewModel(mode: .Edit(task)) + return TaskEditViewModel(mode: .edit(task)) } .asDriver(onErrorDriveWith: .never()) @@ -90,25 +90,25 @@ struct TaskListViewModel: TaskListViewModelType { // Model Service // Task.didCreate - .subscribeNext { task in - self.tasks.value.insert(task, atIndex: 0) - } + .subscribe(onNext: { [unowned self] task in + self.tasks.value.insert(task, at: 0) + }) .addDisposableTo(self.disposeBag) Task.didUpdate - .subscribeNext { task in + .subscribe(onNext: { [unowned self] task in if let index = self.tasks.value.indexOf(task) { self.tasks.value[index] = task } - } + }) .addDisposableTo(self.disposeBag) Task.didDelete - .subscribeNext { task in + .subscribe(onNext: { [unowned self] task in if let index = self.tasks.value.indexOf(task) { - self.tasks.value.removeAtIndex(index) + self.tasks.value.remove(at: index) } - } + }) .addDisposableTo(self.disposeBag) } diff --git a/RxTodo/Sources/Views/TaskCell.swift b/RxTodo/Sources/Views/TaskCell.swift index ce29e86..c287860 100644 --- a/RxTodo/Sources/Views/TaskCell.swift +++ b/RxTodo/Sources/Views/TaskCell.swift @@ -23,11 +23,11 @@ final class TaskCell: BaseTableViewCell { } struct Font { - static let titleLabel = UIFont.systemFontOfSize(14) + static let titleLabel = UIFont.systemFont(ofSize: 14) } struct Color { - static let titleLabelText = UIColor.blackColor() + static let titleLabelText = UIColor.black } @@ -49,7 +49,7 @@ final class TaskCell: BaseTableViewCell { // MARK: Configuring - func configure(viewModel: TaskCellModelType) { + func configure(_ viewModel: TaskCellModelType) { self.titleLabel.text = viewModel.title } @@ -68,7 +68,7 @@ final class TaskCell: BaseTableViewCell { // MARK: Cell Height - class func cellHeightThatFitsWidth(width: CGFloat, viewModel: TaskCellModelType) -> CGFloat { + class func cellHeightThatFitsWidth(_ width: CGFloat, viewModel: TaskCellModelType) -> CGFloat { let height = viewModel.title.heightThatFitsWidth(width - Metric.cellPadding * 2, font: Font.titleLabel, maximumNumberOfLines: Constant.titleLabelNumberOfLines)