diff --git a/.swiftlint.yml b/.swiftlint.yml index 4b54198..de471c3 100644 --- a/.swiftlint.yml +++ b/.swiftlint.yml @@ -1,11 +1,56 @@ excluded: - Pods - + - docs + - build + - scripts + disabled_rules: - - line_length - - todo + # metrics - nesting + - function_parameter_count + + # lint + - notification_center_detachment + - unused_closure_parameter + - weak_delegate + + # idiomatic + - fallthrough + - force_cast + - type_name + + # style + - identifier_name + - closure_parameter_position + +opt_in_rules: + # performance + - empty_count + - first_where + - sorted_first_last + - contains_over_first_not_nil + + # idiomatic + - fatal_error_message + + # style + - attributes + - number_separator + - operator_usage_whitespace + - sorted_imports + - vertical_parameter_alignment_on_call + + # lint + - overridden_super_call + +line_length: 200 +file_length: 600 + +type_body_length: 500 + +function_body_length: 250 + +cyclomatic_complexity: 15 -# limit whitespace only lines to 2 vertical_whitespace: max_empty_lines: 2 diff --git a/Brisk iOS/App/AppCoordinator.swift b/Brisk iOS/App/AppCoordinator.swift index bba32ca..c216a4a 100644 --- a/Brisk iOS/App/AppCoordinator.swift +++ b/Brisk iOS/App/AppCoordinator.swift @@ -1,8 +1,8 @@ -import UIKit -import InterfaceBacked -import Sonar import AcknowList +import InterfaceBacked import SafariServices +import Sonar +import UIKit final class AppCoordinator { diff --git a/Brisk iOS/App/StatusDisplay.swift b/Brisk iOS/App/StatusDisplay.swift index 3a92a2a..ec0c365 100644 --- a/Brisk iOS/App/StatusDisplay.swift +++ b/Brisk iOS/App/StatusDisplay.swift @@ -1,5 +1,5 @@ -import UIKit import SVProgressHUD +import UIKit protocol StatusDisplay { func showLoading() diff --git a/Brisk iOS/Dupe/DupeViewController.swift b/Brisk iOS/Dupe/DupeViewController.swift index fd9a2c1..a4d13f5 100644 --- a/Brisk iOS/Dupe/DupeViewController.swift +++ b/Brisk iOS/Dupe/DupeViewController.swift @@ -1,5 +1,5 @@ -import UIKit import InterfaceBacked +import UIKit protocol DupeViewDelegate: class { func controllerDidCancel(_ controller: DupeViewController) @@ -20,6 +20,7 @@ final class DupeViewController: UIViewController, StoryboardBacked, StatusDispla // MARK: - UIViewController Methods override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) if let content = UIPasteboard.general.string, content.isOpenRadar && number.isEmpty { numberField.text = content.extractRadarNumber() hintLabel.text = "Found \(content) on your clipboard" diff --git a/Brisk iOS/Login/LoginCoordinator.swift b/Brisk iOS/Login/LoginCoordinator.swift index 5d307dc..fc5e207 100644 --- a/Brisk iOS/Login/LoginCoordinator.swift +++ b/Brisk iOS/Login/LoginCoordinator.swift @@ -1,5 +1,5 @@ -import UIKit import SafariServices +import UIKit private let kAPIKeyURL = URL(string: "https://openradar.appspot.com/apikey")! private let kOpenRadarUsername = "openradar" diff --git a/Brisk iOS/Login/LoginViewController.swift b/Brisk iOS/Login/LoginViewController.swift index 312d0f2..daf2021 100644 --- a/Brisk iOS/Login/LoginViewController.swift +++ b/Brisk iOS/Login/LoginViewController.swift @@ -1,5 +1,5 @@ -import UIKit import InterfaceBacked +import UIKit protocol LoginViewDelegate: class { func submitTapped(user: User) @@ -20,6 +20,7 @@ final class LoginViewController: UIViewController, StoryboardBacked { // MARK: - UIViewController Methods override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) emailField.becomeFirstResponder() validateSubmitButton() } diff --git a/Brisk iOS/Login/OpenRadarViewController.swift b/Brisk iOS/Login/OpenRadarViewController.swift index cf9a274..b43a696 100644 --- a/Brisk iOS/Login/OpenRadarViewController.swift +++ b/Brisk iOS/Login/OpenRadarViewController.swift @@ -1,5 +1,5 @@ -import UIKit import InterfaceBacked +import UIKit protocol OpenRadarViewDelegate: class { func openSafariTapped() diff --git a/Brisk iOS/Menu/MenuViewController.swift b/Brisk iOS/Menu/MenuViewController.swift index defa5bf..d0ebd1d 100644 --- a/Brisk iOS/Menu/MenuViewController.swift +++ b/Brisk iOS/Menu/MenuViewController.swift @@ -1,5 +1,5 @@ -import UIKit import InterfaceBacked +import UIKit protocol MenuViewDelegate: class { func dupeTapped() diff --git a/Brisk iOS/Model/APIController.swift b/Brisk iOS/Model/APIController.swift index 049dd58..46915bb 100644 --- a/Brisk iOS/Model/APIController.swift +++ b/Brisk iOS/Model/APIController.swift @@ -1,6 +1,6 @@ +import Alamofire import Foundation import Sonar -import Alamofire protocol APIDelegate: class { func didStartLoading() diff --git a/Brisk iOS/Model/KeyboardObservable.swift b/Brisk iOS/Model/KeyboardObservable.swift index 1f98998..c573405 100644 --- a/Brisk iOS/Model/KeyboardObservable.swift +++ b/Brisk iOS/Model/KeyboardObservable.swift @@ -36,7 +36,7 @@ extension KeyboardObservable where Self: UITextView { } private var textHeight: CGFloat { guard let font = font else { - fatalError() + fatalError("Can't calculate text height without a font.") } let size = (text ?? "").size(with: font, constrainedToWidth: frame.width) return size.height @@ -88,18 +88,18 @@ extension KeyboardObservable where Self: UITextView { let window = superview.window, let font = font, let userInfo = notification.userInfo else { - fatalError() + fatalError("Missing required superview, window, font & userInfo.") } let keyboardScreenFrame: CGRect switch state { case .show: guard let frame = userInfo[UIKeyboardFrameEndUserInfoKey] as? CGRect else { - fatalError() + fatalError("No frame in userInfo found for key UIKeyboardFrameEndUserInfoKey") } keyboardScreenFrame = frame case .hide: guard let frame = userInfo[UIKeyboardFrameBeginUserInfoKey] as? CGRect else { - fatalError() + fatalError("No frame in userInfo found for key UIKeyboardFrameBeginUserInfoKey") } keyboardScreenFrame = frame } diff --git a/Brisk iOS/Radar/EnterDetailsViewController.swift b/Brisk iOS/Radar/EnterDetailsViewController.swift index 5c23d90..430a8aa 100644 --- a/Brisk iOS/Radar/EnterDetailsViewController.swift +++ b/Brisk iOS/Radar/EnterDetailsViewController.swift @@ -1,5 +1,5 @@ -import UIKit import InterfaceBacked +import UIKit final class TextView: UITextView, KeyboardObservable { private var observers: [Any]? @@ -33,6 +33,7 @@ final class EnterDetailsViewController: UIViewController, StoryboardBacked { // MARK: - UIViewController Methods override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) if placeholder.isNotEmpty && prefilledContent.isEmpty { applyStyling(.placeholder) textView.text = placeholder @@ -43,10 +44,12 @@ final class EnterDetailsViewController: UIViewController, StoryboardBacked { } override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) textView.becomeFirstResponder() } override func viewWillDisappear(_ animated: Bool) { + super.viewWillDisappear(animated) if placeholder.isEmpty || textView.text != placeholder { onDisappear(textView.text) diff --git a/Brisk iOS/Radar/RadarCoordinator.swift b/Brisk iOS/Radar/RadarCoordinator.swift index 5ca21aa..9169bdc 100644 --- a/Brisk iOS/Radar/RadarCoordinator.swift +++ b/Brisk iOS/Radar/RadarCoordinator.swift @@ -1,8 +1,6 @@ import Foundation -import UIKit import Sonar - -// FIXME: This grew fast. Extract stuff... +import UIKit final class RadarCoordinator: NSObject { @@ -127,7 +125,8 @@ final class RadarCoordinator: NSObject { } - @objc fileprivate func enterDetailsDidFinish() { + @objc + fileprivate func enterDetailsDidFinish() { root.presentedViewController?.dismiss(animated: true) if let selected = radarViewController?.tableView.indexPathForSelectedRow { radarViewController?.tableView.deselectRow(at: selected, animated: true) diff --git a/Brisk iOS/Radar/RadarViewController.swift b/Brisk iOS/Radar/RadarViewController.swift index 426c6f8..a173850 100644 --- a/Brisk iOS/Radar/RadarViewController.swift +++ b/Brisk iOS/Radar/RadarViewController.swift @@ -1,6 +1,6 @@ -import UIKit import InterfaceBacked import Sonar +import UIKit protocol RadarViewDelegate: class { func productTapped() @@ -72,7 +72,6 @@ final class RadarViewController: UITableViewController, StoryboardBacked, Status } // swiftlint:disable cyclomatic_complexity - // swiftlint:disable:next function_body_length override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { guard let cell = tableView.dequeueReusableCell(withIdentifier: "Cell"), let radar = self.radar else { preconditionFailure() } var left = "" diff --git a/Brisk iOS/Radar/SingleChoiceTableViewController.swift b/Brisk iOS/Radar/SingleChoiceTableViewController.swift index 1ed1b03..59b52ad 100644 --- a/Brisk iOS/Radar/SingleChoiceTableViewController.swift +++ b/Brisk iOS/Radar/SingleChoiceTableViewController.swift @@ -1,6 +1,6 @@ -import UIKit import InterfaceBacked import Sonar +import UIKit final class SingleChoiceViewController: UITableViewController { @@ -14,6 +14,7 @@ final class SingleChoiceViewController: UITableViewController { // MARK: - UIViewController Methods override func viewDidLoad() { + super.viewDidLoad() precondition(all != nil, "All choices must be set before the view controller is presented") precondition(all!.isNotEmpty, "All choices must not be empty") tableView.register(UITableViewCell.self, forCellReuseIdentifier: "Cell") diff --git a/Brisk iOS/Settings/SettingsViewController.swift b/Brisk iOS/Settings/SettingsViewController.swift index dbb9aad..83ab2aa 100644 --- a/Brisk iOS/Settings/SettingsViewController.swift +++ b/Brisk iOS/Settings/SettingsViewController.swift @@ -1,5 +1,5 @@ -import UIKit import InterfaceBacked +import UIKit protocol SettingsDelegate: class { func doneTapped() @@ -27,6 +27,7 @@ final class SettingsViewController: UITableViewController, StoryboardBacked { // MARK: - UIViewController Methods override func viewDidLoad() { + super.viewDidLoad() configureVersionLabel() } diff --git a/Brisk iOSTests/EmailTests.swift b/Brisk iOSTests/EmailTests.swift index 60135c4..a93059b 100644 --- a/Brisk iOSTests/EmailTests.swift +++ b/Brisk iOSTests/EmailTests.swift @@ -1,5 +1,5 @@ -import XCTest @testable import Brisk_iOS +import XCTest class EmailTests: XCTestCase { diff --git a/Brisk iOSTests/RadarNumberEtractionTests.swift b/Brisk iOSTests/RadarNumberEtractionTests.swift index f873302..211c85a 100644 --- a/Brisk iOSTests/RadarNumberEtractionTests.swift +++ b/Brisk iOSTests/RadarNumberEtractionTests.swift @@ -1,5 +1,5 @@ -import XCTest @testable import Brisk_iOS +import XCTest class RadarNumberEtractionTests: XCTestCase { diff --git a/Brisk iOSTests/SubmitRadarTests.swift b/Brisk iOSTests/SubmitRadarTests.swift index b62291c..a6301d5 100644 --- a/Brisk iOSTests/SubmitRadarTests.swift +++ b/Brisk iOSTests/SubmitRadarTests.swift @@ -1,6 +1,6 @@ -import XCTest @testable import Brisk_iOS @testable import Sonar +import XCTest final class MockDisplay: StatusDisplay { var didShowLoading = false diff --git a/Readme.md b/Readme.md index 311ac84..19e5c42 100644 --- a/Readme.md +++ b/Readme.md @@ -1,4 +1,3 @@ - # Brisk [![Build status](https://badge.buildkite.com/672895d57514714220c79243148e0423061d2f04ca6ff5f6e8.svg)](https://buildkite.com/florianbuerger/brisk) @@ -25,24 +24,28 @@ Posting to OpenRadar only works when you entered your API key in settings. Re-en ## URL Scheme -Brisk supports an URL scheme `brisk-rdar` to integrate with other apps. +Brisk supports an URL scheme `brisk-rdar` to integrate with other apps. Pass an OpenRadar number: `brisk-rdar://radar/123456`. Per default, Brisk searches for the number with user interaction. If you don't want that, use `brisk-radar://radar/123456?submit=false`. ## Installation -iOS apps have to be signed in order to be installed on a device. To let Xcode handle everything for you change the `Bundle Identifier` to something unique, `com..brisk` for example and select your development team in Xcode. +iOS apps have to be signed in order to be installed on a device. To let Xcode handle everything for you change the `Bundle Identifier` to something unique, `com..brisk` for example and select your development team in Xcode. 1. Open `Brisk iOS.xcworkspace` 2. Go to Xcode > Preferences > Accounts and add an active iOS developer account if you haven't done that already -3. Change the `Bundle identifier` to `com..brisk` +3. Change the `Bundle identifier` to `com..brisk` 4. Select the project in Xcode's sidebar, select the `Brisk iOS` target and select your Apple ID in `Signing > Team` Feel free to [contact me](mailto:hi@florianbuerger.com) if you need help or open a new issue. ## Development -The app is built with Swift 4.0 and everything should work after you clone the project. +The app is built with Swift 4 using Xcode 9. It requires [swiftlint](https://github.com/realm/SwiftLint) to be installed in your `$PATH`. If you haven't yet, please install it using [Homebrew](https://brew.sh/): + +``` +$ brew install swiftlint +``` ## Credits