├── KeyboardLayoutGuide
├── Assets.xcassets
│ ├── Contents.json
│ ├── Triangle.png
│ ├── Triangle@2x.png
│ ├── Triangle@3x.png
│ ├── Box.imageset
│ │ ├── Box.png
│ │ ├── Box@2x.png
│ │ ├── Box@3x.png
│ │ └── Contents.json
│ ├── Circle.imageset
│ │ ├── Circle.png
│ │ ├── Circle@2x.png
│ │ ├── Circle@3x.png
│ │ └── Contents.json
│ ├── Spiral.imageset
│ │ ├── Spiral.png
│ │ ├── Spiral@2x.png
│ │ ├── Spiral@3x.png
│ │ └── Contents.json
│ ├── Triangle.imageset
│ │ ├── Triangle.png
│ │ ├── Triangle@2x.png
│ │ ├── Triangle@3x.png
│ │ └── Contents.json
│ └── AppIcon.appiconset
│ │ └── Contents.json
├── Cases
│ ├── UITableView
│ │ ├── Car.swift
│ │ ├── CarTableViewCell.swift
│ │ ├── TableViewCaseView.swift
│ │ └── TableViewCaseViewController.swift
│ ├── UIView
│ │ ├── ViewCaseViewController.swift
│ │ └── ViewCaseView.swift
│ ├── Closures
│ │ ├── ClosuresCaseView.swift
│ │ └── ClosuresCaseViewController.swift
│ └── UIScrollView
│ │ ├── ScrollViewCaseViewController.swift
│ │ ├── RulerView.swift
│ │ ├── RulerPart.swift
│ │ └── ScrollViewCaseView.swift
├── AppDelegate.swift
├── SceneDelegate.swift
├── KeyboardLayoutGuide
│ ├── NSLayoutConstraint.swift
│ ├── ScrollView.swift
│ ├── View.swift
│ └── ViewController.swift
├── Common
│ └── InsetsTextField.swift
├── Base.lproj
│ └── LaunchScreen.storyboard
├── Info.plist
├── DemoTableViewController.swift
└── car-list.json
├── KeyboardLayoutGuide.xcodeproj
├── project.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
└── project.pbxproj
├── .gitignore
├── README.md
└── LICENSE
/KeyboardLayoutGuide/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/KeyboardLayoutGuide/Assets.xcassets/Triangle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PatrykKaczmarek/KeyboardLayoutGuide/HEAD/KeyboardLayoutGuide/Assets.xcassets/Triangle.png
--------------------------------------------------------------------------------
/KeyboardLayoutGuide/Assets.xcassets/Triangle@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PatrykKaczmarek/KeyboardLayoutGuide/HEAD/KeyboardLayoutGuide/Assets.xcassets/Triangle@2x.png
--------------------------------------------------------------------------------
/KeyboardLayoutGuide/Assets.xcassets/Triangle@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PatrykKaczmarek/KeyboardLayoutGuide/HEAD/KeyboardLayoutGuide/Assets.xcassets/Triangle@3x.png
--------------------------------------------------------------------------------
/KeyboardLayoutGuide/Assets.xcassets/Box.imageset/Box.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PatrykKaczmarek/KeyboardLayoutGuide/HEAD/KeyboardLayoutGuide/Assets.xcassets/Box.imageset/Box.png
--------------------------------------------------------------------------------
/KeyboardLayoutGuide/Assets.xcassets/Box.imageset/Box@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PatrykKaczmarek/KeyboardLayoutGuide/HEAD/KeyboardLayoutGuide/Assets.xcassets/Box.imageset/Box@2x.png
--------------------------------------------------------------------------------
/KeyboardLayoutGuide/Assets.xcassets/Box.imageset/Box@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PatrykKaczmarek/KeyboardLayoutGuide/HEAD/KeyboardLayoutGuide/Assets.xcassets/Box.imageset/Box@3x.png
--------------------------------------------------------------------------------
/KeyboardLayoutGuide/Assets.xcassets/Circle.imageset/Circle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PatrykKaczmarek/KeyboardLayoutGuide/HEAD/KeyboardLayoutGuide/Assets.xcassets/Circle.imageset/Circle.png
--------------------------------------------------------------------------------
/KeyboardLayoutGuide/Assets.xcassets/Spiral.imageset/Spiral.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PatrykKaczmarek/KeyboardLayoutGuide/HEAD/KeyboardLayoutGuide/Assets.xcassets/Spiral.imageset/Spiral.png
--------------------------------------------------------------------------------
/KeyboardLayoutGuide/Assets.xcassets/Circle.imageset/Circle@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PatrykKaczmarek/KeyboardLayoutGuide/HEAD/KeyboardLayoutGuide/Assets.xcassets/Circle.imageset/Circle@2x.png
--------------------------------------------------------------------------------
/KeyboardLayoutGuide/Assets.xcassets/Circle.imageset/Circle@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PatrykKaczmarek/KeyboardLayoutGuide/HEAD/KeyboardLayoutGuide/Assets.xcassets/Circle.imageset/Circle@3x.png
--------------------------------------------------------------------------------
/KeyboardLayoutGuide/Assets.xcassets/Spiral.imageset/Spiral@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PatrykKaczmarek/KeyboardLayoutGuide/HEAD/KeyboardLayoutGuide/Assets.xcassets/Spiral.imageset/Spiral@2x.png
--------------------------------------------------------------------------------
/KeyboardLayoutGuide/Assets.xcassets/Spiral.imageset/Spiral@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PatrykKaczmarek/KeyboardLayoutGuide/HEAD/KeyboardLayoutGuide/Assets.xcassets/Spiral.imageset/Spiral@3x.png
--------------------------------------------------------------------------------
/KeyboardLayoutGuide/Assets.xcassets/Triangle.imageset/Triangle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PatrykKaczmarek/KeyboardLayoutGuide/HEAD/KeyboardLayoutGuide/Assets.xcassets/Triangle.imageset/Triangle.png
--------------------------------------------------------------------------------
/KeyboardLayoutGuide/Assets.xcassets/Triangle.imageset/Triangle@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PatrykKaczmarek/KeyboardLayoutGuide/HEAD/KeyboardLayoutGuide/Assets.xcassets/Triangle.imageset/Triangle@2x.png
--------------------------------------------------------------------------------
/KeyboardLayoutGuide/Assets.xcassets/Triangle.imageset/Triangle@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PatrykKaczmarek/KeyboardLayoutGuide/HEAD/KeyboardLayoutGuide/Assets.xcassets/Triangle.imageset/Triangle@3x.png
--------------------------------------------------------------------------------
/KeyboardLayoutGuide.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/KeyboardLayoutGuide.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/KeyboardLayoutGuide/Cases/UITableView/Car.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Car.swift
3 | // KeyboardLayoutGuide
4 | //
5 | // Created by Patryk Kaczmarek on 03/01/2020.
6 | // Copyright © 2020 Patryk Kaczmarek. All rights reserved.
7 | //
8 |
9 | struct Car: Decodable {
10 |
11 | // MARK: Properties
12 |
13 | let brand: String
14 |
15 | let models: [String]
16 | }
17 |
--------------------------------------------------------------------------------
/KeyboardLayoutGuide/Assets.xcassets/Box.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "Box.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "filename" : "Box@2x.png",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "universal",
15 | "filename" : "Box@3x.png",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "version" : 1,
21 | "author" : "xcode"
22 | },
23 | "properties" : {
24 | "template-rendering-intent" : "template"
25 | }
26 | }
--------------------------------------------------------------------------------
/KeyboardLayoutGuide/Assets.xcassets/Circle.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "Circle.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "filename" : "Circle@2x.png",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "universal",
15 | "filename" : "Circle@3x.png",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "version" : 1,
21 | "author" : "xcode"
22 | },
23 | "properties" : {
24 | "template-rendering-intent" : "template"
25 | }
26 | }
--------------------------------------------------------------------------------
/KeyboardLayoutGuide/Assets.xcassets/Spiral.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "Spiral.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "filename" : "Spiral@2x.png",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "universal",
15 | "filename" : "Spiral@3x.png",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "version" : 1,
21 | "author" : "xcode"
22 | },
23 | "properties" : {
24 | "template-rendering-intent" : "template"
25 | }
26 | }
--------------------------------------------------------------------------------
/KeyboardLayoutGuide/Assets.xcassets/Triangle.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "Triangle.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "filename" : "Triangle@2x.png",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "universal",
15 | "filename" : "Triangle@3x.png",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "version" : 1,
21 | "author" : "xcode"
22 | },
23 | "properties" : {
24 | "template-rendering-intent" : "template"
25 | }
26 | }
--------------------------------------------------------------------------------
/KeyboardLayoutGuide/Cases/UITableView/CarTableViewCell.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CarTableViewCell.swift
3 | // KeyboardLayoutGuide
4 | //
5 | // Created by Patryk Kaczmarek on 03/01/2020.
6 | // Copyright © 2020 Patryk Kaczmarek. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | final class CarTableViewCell: UITableViewCell {
12 |
13 | // MARK: Overrides
14 |
15 | override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
16 | super.init(style: .subtitle, reuseIdentifier: reuseIdentifier)
17 | }
18 |
19 | required init?(coder: NSCoder) {
20 | fatalError("init(coder:) has not been implemented")
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/KeyboardLayoutGuide/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // KeyboardLayoutGuide
4 | //
5 | // Created by Patryk Kaczmarek on 15/04/2020.
6 | // Copyright © 2020 Netguru. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | @UIApplicationMain
12 | class AppDelegate: UIResponder, UIApplicationDelegate {
13 |
14 | // MARK: UISceneSession Lifecycle
15 |
16 | func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
17 | UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | #
2 | # .gitignore
3 | #
4 |
5 | .DS_Store
6 | .Trashes
7 | .localized
8 |
9 | build
10 | .build
11 | xcuserdata
12 | DerivedData
13 |
14 | *.mode1v3
15 | *.mode2v3
16 | *.perspectivev3
17 | *.pbxuser
18 | *.xccheckout
19 | *.xcuserstate
20 | *.xcscmblueprint
21 | *.moved-aside
22 | *.hmap
23 | *.o
24 | *.hmap
25 | *.ipa
26 | *.dSYM.zip
27 |
28 | timeline.xctimeline
29 | playground.xcworkspace
30 |
31 | .idea
32 | *.iml
33 |
34 | Pods
35 | Carthage
36 | Packages
37 |
38 | # Prevent against commiting firebase config
39 | Configuration/Firebase/
40 | Nodus/GoogleService-Info.plist
41 |
42 | fastlane/report.xml
43 | fastlane/Preview.html
44 | fastlane/screenshots
45 | fastlane/test_output
46 |
47 | .env
48 |
--------------------------------------------------------------------------------
/KeyboardLayoutGuide/SceneDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SceneDelegate.swift
3 | // KeyboardLayoutGuide
4 | //
5 | // Created by Patryk Kaczmarek on 19/12/2019.
6 | // Copyright © 2020 Patryk Kaczmarek. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | final class SceneDelegate: UIResponder, UIWindowSceneDelegate {
12 |
13 | var window: UIWindow?
14 |
15 | func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
16 | guard let windowScene = (scene as? UIWindowScene) else { return }
17 |
18 | window = UIWindow(frame: windowScene.coordinateSpace.bounds)
19 | window?.windowScene = windowScene
20 | window?.rootViewController = UINavigationController(rootViewController: DemoTableViewController())
21 | window?.makeKeyAndVisible()
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/KeyboardLayoutGuide/KeyboardLayoutGuide/NSLayoutConstraint.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NSLayoutConstraint.swift
3 | // KeyboardLayoutGuide
4 | //
5 | // Created by Patryk Kaczmarek on 03/01/2020.
6 | // Copyright © 2020 Patryk Kaczmarek. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | extension NSLayoutConstraint {
12 |
13 | /// Sets a constraint priority.
14 | ///
15 | /// - Parameter priority: A new constraint priority.
16 | /// - Returns: Constraint which priority did change.
17 | @discardableResult
18 | func with(priority: UILayoutPriority) -> Self {
19 | self.priority = priority
20 | return self
21 | }
22 |
23 | /// Sets a constraint priority.
24 | ///
25 | /// - Parameter priority: A new constraint priority.
26 | /// - Returns: Constraint which priority did change.
27 | @discardableResult
28 | func with(priority: Float) -> Self {
29 | with(priority: UILayoutPriority(priority))
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/KeyboardLayoutGuide/Cases/UIView/ViewCaseViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ViewCaseViewController.swift
3 | // KeyboardLayoutGuide
4 | //
5 | // Created by Patryk Kaczmarek on 20/12/2019.
6 | // Copyright © 2020 Patryk Kaczmarek. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | final class ViewCaseViewController: ViewController {
12 |
13 | // MARK: Initializer
14 |
15 | init() {
16 | super.init(view: ViewCaseView())
17 | }
18 |
19 | // MARK: Overrides
20 |
21 | override func viewDidLoad() {
22 | super.viewDidLoad()
23 |
24 | customView.emailTextField.delegate = self
25 | customView.passwordTextField.delegate = self
26 | automaticallyAdjustKeyboardLayoutGuide = true
27 | }
28 | }
29 |
30 | // MARK: UITextFieldDelegate
31 |
32 | extension ViewCaseViewController: UITextFieldDelegate {
33 |
34 | func textFieldShouldReturn(_ textField: UITextField) -> Bool {
35 | view.endEditing(true)
36 | return true
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/KeyboardLayoutGuide/Common/InsetsTextField.swift:
--------------------------------------------------------------------------------
1 | //
2 | // InsetsTextField.swift
3 | // KeyboardLayoutGuide
4 | //
5 | // Created by Patryk Kaczmarek on 03/01/2020.
6 | // Copyright © 2020 Patryk Kaczmarek. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | final class InsetsTextField: UITextField {
12 |
13 | let insets: UIEdgeInsets
14 |
15 | init(insets: UIEdgeInsets = UIEdgeInsets(top: 5, left: 10, bottom: 5, right: 10)) {
16 | self.insets = insets
17 | super.init(frame: .zero)
18 | }
19 |
20 | @available(*, unavailable, message: "Use init(insets:) method instead.")
21 | required init?(coder aDecoder: NSCoder) {
22 | fatalError("init(coder:) has not been implemented")
23 | }
24 |
25 | override func placeholderRect(forBounds: CGRect) -> CGRect {
26 | return bounds.inset(by: insets)
27 | }
28 |
29 | override func editingRect(forBounds bounds: CGRect) -> CGRect {
30 | return bounds.inset(by: insets)
31 | }
32 |
33 | override func textRect(forBounds bounds: CGRect) -> CGRect {
34 | return bounds.inset(by: insets)
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # KeyboardLayoutGuide
2 |
3 |
4 | > [!IMPORTANT]
5 | > Apple introduced in iOS 15 own version of [KeyboardLayoutGuide](https://developer.apple.com/documentation/uikit/keyboards_and_input/adjusting_your_layout_with_keyboard_layout_guide). Please use it instead. This approach supports at least iOS 11+ and was developed much earlier than the official API.
6 |
7 | **This repository is part of [Introduction To Missing Keyboard Layout Guide](https://www.netguru.com/codestories/introduction-to-missing-keyboard-layout-guide) code story. Read it for more information.**
8 |
9 |
10 | ### Content
11 |
12 | The repository contains a surprisingly simple solution used by me for the keyboard handling issue. It consists of [source files](https://github.com/PatrykKaczmarek/KeyboardLayoutGuide/tree/master/KeyboardLayoutGuide/KeyboardLayoutGuide) and 4 demo cases for:
13 |
14 | - A sign-in screen: has a simple view hierarchy
15 | - A ruler screen: contains `UIScrollView` with long content size
16 | - A catalog screen: contains `UITableView` with content filtering
17 | - A 🎉 screen: reacts on the keyboard occurrence
18 |
19 | Happy coding 👩💻🧑💻!
20 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Patryk Kaczmarek
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/KeyboardLayoutGuide/Cases/Closures/ClosuresCaseView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ClosuresCaseView.swift
3 | // KeyboardLayoutGuide
4 | //
5 | // Created by Patryk Kaczmarek on 03/01/2020.
6 | // Copyright © 2020 Patryk Kaczmarek. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | final class ClosuresCaseView: View {
12 |
13 | // MARK: Properties
14 |
15 | lazy var textField: UITextField = {
16 | let textField = InsetsTextField()
17 | textField.translatesAutoresizingMaskIntoConstraints = false
18 | textField.placeholder = "Tap me!"
19 | return textField
20 | }()
21 |
22 | // MARK: Overrides
23 |
24 | override init() {
25 | super.init()
26 |
27 | backgroundColor = .white
28 | addSubview(textField)
29 |
30 | NSLayoutConstraint.activate([
31 | textField.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 20),
32 | textField.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -20),
33 | textField.topAnchor.constraint(equalTo: safeAreaLayoutGuide.topAnchor, constant: 20),
34 | textField.heightAnchor.constraint(equalToConstant: 44),
35 | ])
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/KeyboardLayoutGuide/Cases/UIScrollView/ScrollViewCaseViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ScrollViewCaseViewController.swift
3 | // KeyboardLayoutGuide
4 | //
5 | // Created by Patryk Kaczmarek on 03/01/2020.
6 | // Copyright © 2020 Patryk Kaczmarek. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | final class ScrollViewCaseViewController: ViewController {
12 |
13 | init() {
14 | super.init(view: ScrollViewCaseView())
15 | }
16 |
17 | override func viewDidLoad() {
18 | super.viewDidLoad()
19 |
20 | customView.nameTextField.delegate = self
21 | customView.goButton.addTarget(self, action: #selector(goButtonWereTapped), for: .touchUpInside)
22 | automaticallyAdjustKeyboardLayoutGuide = true
23 | }
24 |
25 | @objc private func goButtonWereTapped() {
26 | scroll()
27 | }
28 | }
29 |
30 | extension ScrollViewCaseViewController: UITextFieldDelegate {
31 |
32 | func textFieldShouldReturn(_ textField: UITextField) -> Bool {
33 | scroll()
34 | view.endEditing(true)
35 | return true
36 | }
37 | }
38 |
39 | private extension ScrollViewCaseViewController {
40 |
41 | func scroll() {
42 | if let text = customView.nameTextField.text, let position = Int(text) {
43 | customView.scroll(to: position)
44 | }
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/KeyboardLayoutGuide/Base.lproj/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/KeyboardLayoutGuide/Cases/UITableView/TableViewCaseView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TableViewCaseView.swift
3 | // KeyboardLayoutGuide
4 | //
5 | // Created by Patryk Kaczmarek on 03/01/2020.
6 | // Copyright © 2020 Patryk Kaczmarek. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | final class TableViewCaseView: View {
12 |
13 | // MARK: Properties
14 |
15 | lazy var searchField: UITextField = {
16 | let textField = InsetsTextField()
17 | textField.translatesAutoresizingMaskIntoConstraints = false
18 | textField.placeholder = "Search"
19 | return textField
20 | }()
21 |
22 | lazy var tableView: UITableView = {
23 | let tableView = UITableView()
24 | tableView.translatesAutoresizingMaskIntoConstraints = false
25 | return tableView
26 | }()
27 |
28 | // MARK: Overrides
29 |
30 | override init() {
31 | super.init()
32 |
33 | backgroundColor = .white
34 | addSubview(searchField)
35 | addSubview(tableView)
36 |
37 | NSLayoutConstraint.activate([
38 | searchField.leadingAnchor.constraint(equalTo: leadingAnchor),
39 | searchField.trailingAnchor.constraint(equalTo: trailingAnchor),
40 | searchField.topAnchor.constraint(equalTo: safeAreaLayoutGuide.topAnchor),
41 | searchField.heightAnchor.constraint(equalToConstant: 44),
42 |
43 | tableView.leadingAnchor.constraint(equalTo: leadingAnchor),
44 | tableView.trailingAnchor.constraint(equalTo: trailingAnchor),
45 | tableView.topAnchor.constraint(equalTo: searchField.bottomAnchor),
46 | tableView.bottomAnchor.constraint(equalTo: keyboardLayoutGuideBackport.topAnchor)
47 | ])
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/KeyboardLayoutGuide/KeyboardLayoutGuide/ScrollView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ScrollView.swift
3 | // KeyboardLayoutGuide
4 | //
5 | // Created by Patryk Kaczmarek on 03/01/2020.
6 | // Copyright © 2020 Patryk Kaczmarek. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | /// This class embeds its own content view which nicely resolves all
12 | /// UIScrollView scrollable content size ambiguities.
13 | final class ScrollView: UIScrollView {
14 |
15 | // MARK: Properties
16 |
17 | /// Default superview for content displayed by the scroll view.
18 | /// If you want to customize scroll view by adding additional views,
19 | /// you should add them to the content view so they will be positioned appropriately.
20 | private(set) lazy var contentView: UIView = {
21 | let view = UIView()
22 | view.translatesAutoresizingMaskIntoConstraints = false
23 | return view
24 | }()
25 |
26 | // MARK: Overrides
27 |
28 | /// - SeeAlso: UIScrollView.init(frame:)
29 | override init(frame: CGRect) {
30 | super.init(frame: frame)
31 |
32 | addSubview(contentView)
33 | NSLayoutConstraint.activate([
34 | contentView.bottomAnchor.constraint(equalTo: bottomAnchor).with(priority: .defaultLow),
35 | contentView.topAnchor.constraint(equalTo: topAnchor),
36 | contentView.leadingAnchor.constraint(equalTo: leadingAnchor),
37 | contentView.trailingAnchor.constraint(equalTo: trailingAnchor),
38 | contentView.centerYAnchor.constraint(equalTo: centerYAnchor).with(priority: .defaultLow),
39 | contentView.centerXAnchor.constraint(equalTo: centerXAnchor)
40 | ])
41 | }
42 |
43 | @available(*, unavailable, message: "Use init(frame:) method instead.")
44 | required init?(coder aDecoder: NSCoder) {
45 | fatalError("init(coder:) has not been implemented")
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/KeyboardLayoutGuide/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 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 | LSRequiresIPhoneOS
22 |
23 | UIApplicationSceneManifest
24 |
25 | UIApplicationSupportsMultipleScenes
26 |
27 | UISceneConfigurations
28 |
29 | UIWindowSceneSessionRoleApplication
30 |
31 |
32 | UISceneConfigurationName
33 | Default Configuration
34 | UISceneDelegateClassName
35 | $(PRODUCT_MODULE_NAME).SceneDelegate
36 |
37 |
38 |
39 |
40 | UILaunchStoryboardName
41 | LaunchScreen
42 | UIRequiredDeviceCapabilities
43 |
44 | armv7
45 |
46 | UISupportedInterfaceOrientations
47 |
48 | UIInterfaceOrientationPortrait
49 |
50 | UISupportedInterfaceOrientations~ipad
51 |
52 | UIInterfaceOrientationPortrait
53 | UIInterfaceOrientationPortraitUpsideDown
54 | UIInterfaceOrientationLandscapeLeft
55 | UIInterfaceOrientationLandscapeRight
56 |
57 |
58 |
59 |
--------------------------------------------------------------------------------
/KeyboardLayoutGuide/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "size" : "20x20",
6 | "scale" : "2x"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "size" : "20x20",
11 | "scale" : "3x"
12 | },
13 | {
14 | "idiom" : "iphone",
15 | "size" : "29x29",
16 | "scale" : "2x"
17 | },
18 | {
19 | "idiom" : "iphone",
20 | "size" : "29x29",
21 | "scale" : "3x"
22 | },
23 | {
24 | "idiom" : "iphone",
25 | "size" : "40x40",
26 | "scale" : "2x"
27 | },
28 | {
29 | "idiom" : "iphone",
30 | "size" : "40x40",
31 | "scale" : "3x"
32 | },
33 | {
34 | "idiom" : "iphone",
35 | "size" : "60x60",
36 | "scale" : "2x"
37 | },
38 | {
39 | "idiom" : "iphone",
40 | "size" : "60x60",
41 | "scale" : "3x"
42 | },
43 | {
44 | "idiom" : "ipad",
45 | "size" : "20x20",
46 | "scale" : "1x"
47 | },
48 | {
49 | "idiom" : "ipad",
50 | "size" : "20x20",
51 | "scale" : "2x"
52 | },
53 | {
54 | "idiom" : "ipad",
55 | "size" : "29x29",
56 | "scale" : "1x"
57 | },
58 | {
59 | "idiom" : "ipad",
60 | "size" : "29x29",
61 | "scale" : "2x"
62 | },
63 | {
64 | "idiom" : "ipad",
65 | "size" : "40x40",
66 | "scale" : "1x"
67 | },
68 | {
69 | "idiom" : "ipad",
70 | "size" : "40x40",
71 | "scale" : "2x"
72 | },
73 | {
74 | "idiom" : "ipad",
75 | "size" : "76x76",
76 | "scale" : "1x"
77 | },
78 | {
79 | "idiom" : "ipad",
80 | "size" : "76x76",
81 | "scale" : "2x"
82 | },
83 | {
84 | "idiom" : "ipad",
85 | "size" : "83.5x83.5",
86 | "scale" : "2x"
87 | },
88 | {
89 | "idiom" : "ios-marketing",
90 | "size" : "1024x1024",
91 | "scale" : "1x"
92 | }
93 | ],
94 | "info" : {
95 | "version" : 1,
96 | "author" : "xcode"
97 | }
98 | }
--------------------------------------------------------------------------------
/KeyboardLayoutGuide/KeyboardLayoutGuide/View.swift:
--------------------------------------------------------------------------------
1 | //
2 | // View.swift
3 | // KeyboardLayoutGuide
4 | //
5 | // Created by Patryk Kaczmarek on 19/12/2019.
6 | // Copyright © 2020 Patryk Kaczmarek. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class View: UIView {
12 |
13 | // MARK: Properties
14 |
15 | /// A constraint representing height of the keyboard.
16 | /// Automatically handled by ViewController when
17 | /// automaticallyAdjustKeyboardLayoutGuide is set to true.
18 | ///
19 | /// - SeeAlso: View.keyboardLayoutGuide
20 | /// - SeeAlso: ViewController.automaticallyAdjustKeyboardLayoutGuide
21 | private(set) lazy var keyboardHeightConstraint = keyboardLayoutGuideBackport.heightAnchor.constraint(equalToConstant: 0)
22 |
23 | /// Layout guide representing top of the keyboard.
24 | /// Equal to bottom layout guide of view when keyboard is not visible.
25 | /// This guide changes its position with animation on keyboard transition
26 | /// only when ViewController.automaticallyAdjustKeyboardLayoutGuide is set to true.
27 | ///
28 | /// - SeeAlso: View.keyboardHeightConstraint
29 | /// - SeeAlso: ViewController.automaticallyAdjustKeyboardLayoutGuide
30 | let keyboardLayoutGuideBackport = UILayoutGuide()
31 |
32 | // MARK: Initializer
33 |
34 | /// Initializes view for auto layout and sets default `.white` background color.
35 | init() {
36 | super.init(frame: .zero)
37 | translatesAutoresizingMaskIntoConstraints = false
38 | backgroundColor = .white
39 |
40 | addLayoutGuide(keyboardLayoutGuideBackport)
41 | NSLayoutConstraint.activate([
42 | keyboardHeightConstraint,
43 | keyboardLayoutGuideBackport.bottomAnchor.constraint(equalTo: bottomAnchor)
44 | ])
45 | }
46 |
47 | @available(*, unavailable, message: "Use init() method instead.")
48 | required init?(coder aDecoder: NSCoder) {
49 | fatalError("init(coder:) has not been implemented")
50 | }
51 |
52 | // MARK: Overrides
53 |
54 | /// - SeeAlso: UIView.requiresConstraintBasedLayout
55 | override static var requiresConstraintBasedLayout: Bool {
56 | true
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/KeyboardLayoutGuide/Cases/UIScrollView/RulerView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RulerView.swift
3 | // KeyboardLayoutGuide
4 | //
5 | // Created by Patryk Kaczmarek on 02/04/2020.
6 | // Copyright © 2020 Patryk Kaczmarek. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | final class RulerView: UIView {
12 |
13 | // MARK: Properties
14 |
15 | let length: Int
16 |
17 | let divider: Int
18 |
19 | var partHeight: CGFloat {
20 | frame.height * CGFloat(divider) / CGFloat(length)
21 | }
22 |
23 | private lazy var stackView: UIStackView = {
24 | let stackView = UIStackView()
25 | stackView.translatesAutoresizingMaskIntoConstraints = false
26 | stackView.axis = .vertical
27 | return stackView
28 | }()
29 |
30 | // MARK: Initializer
31 |
32 | init(length: Int, divider: Int) {
33 | self.length = length
34 | self.divider = divider
35 | super.init(frame: .zero)
36 |
37 | backgroundColor = .yellow
38 | addSubview(stackView)
39 | (0...(length / divider)).forEach { i in
40 | let arrangedSubview = RulerPart(
41 | text: "\(i * divider) cm",
42 | isFirst: i == 0,
43 | isLast: i * divider == length
44 | )
45 | stackView.addArrangedSubview(arrangedSubview)
46 | }
47 |
48 | NSLayoutConstraint.activate([
49 | stackView.topAnchor.constraint(equalTo: topAnchor),
50 | stackView.bottomAnchor.constraint(equalTo: bottomAnchor),
51 | stackView.leadingAnchor.constraint(equalTo: leadingAnchor),
52 | stackView.trailingAnchor.constraint(equalTo: trailingAnchor),
53 | ])
54 | }
55 |
56 | @available(*, unavailable, message: "Use init(length:divider:) method instead.")
57 | required init?(coder: NSCoder) {
58 | fatalError("init(coder:) has not been implemented")
59 | }
60 |
61 | // MARK: API
62 |
63 | func highlightPart(at index: Int) {
64 | stackView.arrangedSubviews.enumerated().forEach { subviewIndex, arrangedSubview in
65 | (arrangedSubview as? RulerPart)?.isHighlighted = index == subviewIndex
66 | }
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/KeyboardLayoutGuide/Cases/UIScrollView/RulerPart.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RulerPart.swift
3 | // KeyboardLayoutGuide
4 | //
5 | // Created by Patryk Kaczmarek on 02/04/2020.
6 | // Copyright © 2020 Patryk Kaczmarek. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | final class RulerPart: UIView {
12 |
13 | // MARK: Properties
14 |
15 | var isHighlighted: Bool = false {
16 | didSet {
17 | backgroundColor = isHighlighted ? UIColor.red.withAlphaComponent(0.1) : UIColor.clear
18 | }
19 | }
20 |
21 | private lazy var label: UILabel = {
22 | let label = UILabel()
23 | label.translatesAutoresizingMaskIntoConstraints = false
24 | return label
25 | }()
26 |
27 | private let isFirst: Bool
28 |
29 | private let isLast: Bool
30 |
31 | // MARK: Initializer
32 |
33 | init(text: String, isFirst: Bool, isLast: Bool) {
34 | self.isFirst = isFirst
35 | self.isLast = isLast
36 | super.init(frame: .zero)
37 |
38 | addSubview(label)
39 | backgroundColor = .clear
40 | label.text = text
41 |
42 | NSLayoutConstraint.activate([
43 | label.topAnchor.constraint(equalTo: topAnchor),
44 | label.bottomAnchor.constraint(equalTo: bottomAnchor),
45 | label.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 40),
46 | label.trailingAnchor.constraint(equalTo: trailingAnchor),
47 | ])
48 | }
49 |
50 | @available(*, unavailable, message: "Use init(text:isFirst:isLast:) method instead.")
51 | required init?(coder: NSCoder) {
52 | fatalError("init(coder:) has not been implemented")
53 | }
54 |
55 | // MARK: Overrides
56 |
57 | override var intrinsicContentSize: CGSize {
58 | CGSize(width: UIView.noIntrinsicMetric, height: 40)
59 | }
60 |
61 | override func draw(_ rect: CGRect) {
62 | super.draw(rect)
63 |
64 | guard let context = UIGraphicsGetCurrentContext() else {
65 | return
66 | }
67 | context.setStrokeColor(UIColor.black.cgColor)
68 |
69 | if !isFirst {
70 | context.setLineWidth(1.0)
71 | context.move(to: CGPoint(x: 0, y: bounds.minY))
72 | context.addLine(to: CGPoint(x: 10, y: bounds.minY))
73 | }
74 |
75 | context.setLineWidth(2.0)
76 | context.move(to: CGPoint(x: 0, y: bounds.midY))
77 | context.addLine(to: CGPoint(x: 20, y: bounds.midY))
78 |
79 | if !isLast {
80 | context.setLineWidth(1.0)
81 | context.move(to: CGPoint(x: 0, y: bounds.maxY))
82 | context.addLine(to: CGPoint(x: 10, y: bounds.maxY))
83 | }
84 |
85 | context.strokePath()
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/KeyboardLayoutGuide/DemoTableViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DemoTableViewController.swift
3 | // KeyboardLayoutGuide
4 | //
5 | // Created by Patryk Kaczmarek on 20/12/2019.
6 | // Copyright © 2020 Patryk Kaczmarek. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | final class DemoTableViewController: UITableViewController {
12 |
13 | enum Kind: Int, CaseIterable {
14 | case view = 0, scrollView, tableView, closures
15 | }
16 |
17 | // MARK: Properties
18 |
19 | private let tableViewCellIdentifier = "\(String(describing: DemoTableViewController.self)).cellIdentifier"
20 |
21 | // MARK: Initalizer
22 |
23 | init() {
24 | super.init(nibName: nil, bundle: nil)
25 | }
26 |
27 | required init?(coder: NSCoder) {
28 | fatalError("init(coder:) has not been implemented")
29 | }
30 |
31 | // MARK: Overrides
32 |
33 | override func viewDidLoad() {
34 | super.viewDidLoad()
35 |
36 | title = "Demo"
37 | tableView.delegate = self
38 | tableView.dataSource = self
39 | tableView.register(UITableViewCell.self, forCellReuseIdentifier: tableViewCellIdentifier)
40 | }
41 |
42 | /// - SeeAlso: UITableViewDataSource.tableView(_:numberOfRowsInSection:)
43 | override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
44 | Kind.allCases.count
45 | }
46 |
47 | /// - SeeAlso: UITableViewDataSource.tableView(_:cellForRowAt:)
48 | override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
49 | let cell = tableView.dequeueReusableCell(withIdentifier: tableViewCellIdentifier, for: indexPath)
50 | cell.textLabel?.text = Kind.allCases[indexPath.row].title
51 | return cell
52 | }
53 |
54 | // MARK: UITableViewDelegate
55 |
56 | /// - SeeAlso: UITableViewDelegate.tableView(_:didSelectRowAt:)
57 | override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
58 | show(Kind.allCases[indexPath.row].viewControllerToPush, sender: nil)
59 | }
60 | }
61 |
62 | private extension DemoTableViewController.Kind {
63 |
64 | var title: String {
65 | switch self {
66 | case .view:
67 | return "UIView case"
68 | case .scrollView:
69 | return "UIScrollView case"
70 | case .tableView:
71 | return "UITableView case"
72 | case .closures:
73 | return "Closures case"
74 | }
75 | }
76 |
77 | var viewControllerToPush: UIViewController {
78 | switch self {
79 | case .view:
80 | return ViewCaseViewController()
81 | case .scrollView:
82 | return ScrollViewCaseViewController()
83 | case .tableView:
84 | return TableViewCaseViewController()
85 | case .closures:
86 | return ClosuresCaseViewController()
87 | }
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/KeyboardLayoutGuide/Cases/UIView/ViewCaseView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ViewCaseView.swift
3 | // KeyboardLayoutGuide
4 | //
5 | // Created by Patryk Kaczmarek on 20/12/2019.
6 | // Copyright © 2020 Patryk Kaczmarek. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | final class ViewCaseView: View {
12 |
13 | // MARK: Properites
14 |
15 | lazy var signInButton: UIButton = {
16 | let button = UIButton(type: .system)
17 | button.translatesAutoresizingMaskIntoConstraints = false
18 | button.layer.cornerRadius = 10
19 | button.setTitle("Sign In", for: .normal)
20 | button.setTitleColor(.white, for: .normal)
21 | button.backgroundColor = .blue
22 | return button
23 | }()
24 |
25 | lazy var emailTextField: UITextField = {
26 | let textField = InsetsTextField()
27 | textField.translatesAutoresizingMaskIntoConstraints = false
28 | textField.placeholder = "Email"
29 | textField.layer.cornerRadius = 10
30 | textField.layer.borderColor = UIColor.black.cgColor
31 | textField.layer.borderWidth = 1
32 | return textField
33 | }()
34 |
35 | lazy var passwordTextField: UITextField = {
36 | let textField = InsetsTextField()
37 | textField.translatesAutoresizingMaskIntoConstraints = false
38 | textField.placeholder = "Password"
39 | textField.layer.cornerRadius = 10
40 | textField.layer.borderColor = UIColor.black.cgColor
41 | textField.layer.borderWidth = 1
42 | return textField
43 | }()
44 |
45 | // MARK: Overrides
46 |
47 | override init() {
48 | super.init()
49 |
50 | addSubview(signInButton)
51 | addSubview(passwordTextField)
52 | addSubview(emailTextField)
53 |
54 | NSLayoutConstraint.activate([
55 | emailTextField.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 20),
56 | emailTextField.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -20),
57 | emailTextField.topAnchor.constraint(equalTo: safeAreaLayoutGuide.topAnchor, constant: 20),
58 | emailTextField.heightAnchor.constraint(equalToConstant: 44),
59 |
60 | passwordTextField.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 20),
61 | passwordTextField.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -20),
62 | passwordTextField.topAnchor.constraint(equalTo: emailTextField.bottomAnchor, constant: 40),
63 | passwordTextField.heightAnchor.constraint(equalToConstant: 44),
64 |
65 | signInButton.heightAnchor.constraint(equalToConstant: 44),
66 | signInButton.widthAnchor.constraint(equalToConstant: 200),
67 | signInButton.bottomAnchor.constraint(equalTo: keyboardLayoutGuideBackport.topAnchor, constant: -40),
68 | signInButton.centerXAnchor.constraint(equalTo: centerXAnchor)
69 | ])
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/KeyboardLayoutGuide/Cases/UITableView/TableViewCaseViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TableViewCaseViewController.swift
3 | // KeyboardLayoutGuide
4 | //
5 | // Created by Patryk Kaczmarek on 03/01/2020.
6 | // Copyright © 2020 Patryk Kaczmarek. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | final class TableViewCaseViewController: ViewController {
12 |
13 | // MARK: Private
14 |
15 | private let tableViewCellReuseIdentifier = "TableViewCaseViewControllerCell"
16 |
17 | private lazy var cars: [Car] = {
18 | guard let url = Bundle.main.url(forResource: "car-list", withExtension: "json") else {
19 | return []
20 | }
21 |
22 | do {
23 | let data = try Data(contentsOf: url, options: .mappedIfSafe)
24 | return try JSONDecoder().decode([Car].self, from: data)
25 | } catch {
26 | return []
27 | }
28 | }()
29 |
30 | private var filteredCars: [Car] = []
31 |
32 | private var source: [Car] {
33 | if let searchText = customView.searchField.text, !searchText.isEmpty {
34 | return filteredCars
35 | }
36 | return cars
37 | }
38 |
39 | // MARK: Initializer
40 |
41 | init() {
42 | super.init(view: TableViewCaseView())
43 | }
44 |
45 | // MARK: Overrides
46 |
47 | override func viewDidLoad() {
48 | super.viewDidLoad()
49 |
50 | customView.searchField.addTarget(self, action: #selector(textFieldDidChange), for: .editingChanged)
51 | customView.searchField.delegate = self
52 | customView.tableView.dataSource = self
53 | customView.tableView.allowsSelection = false
54 | customView.tableView.register(CarTableViewCell.self, forCellReuseIdentifier: tableViewCellReuseIdentifier)
55 | automaticallyAdjustKeyboardLayoutGuide = true
56 | }
57 | }
58 |
59 | // MARK: UITableViewDataSource
60 |
61 | extension TableViewCaseViewController: UITableViewDataSource {
62 |
63 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
64 | source.count
65 | }
66 |
67 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
68 | let cell = tableView.dequeueReusableCell(withIdentifier: tableViewCellReuseIdentifier, for: indexPath)
69 | cell.textLabel?.text = source[indexPath.row].brand
70 | cell.detailTextLabel?.text = "\( source[indexPath.row].models.count) models"
71 | return cell
72 | }
73 | }
74 |
75 | // MARK: UITextFieldDelegate
76 |
77 | extension TableViewCaseViewController: UITextFieldDelegate {
78 |
79 | func textFieldShouldReturn(_ textField: UITextField) -> Bool {
80 | view.endEditing(true)
81 | return true
82 | }
83 | }
84 |
85 | // MARK: Private
86 |
87 | private extension TableViewCaseViewController {
88 |
89 | @objc func textFieldDidChange(textField: UITextField) {
90 | if let string = textField.text?.lowercased() {
91 | filteredCars = cars.filter { $0.brand.lowercased().contains(string) }
92 | } else {
93 | filteredCars = []
94 | }
95 | customView.tableView.reloadData()
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/KeyboardLayoutGuide/Cases/UIScrollView/ScrollViewCaseView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ScrollViewCaseView.swift
3 | // KeyboardLayoutGuide
4 | //
5 | // Created by Patryk Kaczmarek on 03/01/2020.
6 | // Copyright © 2020 Patryk Kaczmarek. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | final class ScrollViewCaseView: View {
12 |
13 | // MARK: Properties
14 |
15 | lazy var nameTextField: UITextField = {
16 | let textField = InsetsTextField()
17 | textField.translatesAutoresizingMaskIntoConstraints = false
18 | textField.keyboardType = .numberPad
19 | textField.placeholder = "position (cm)"
20 | return textField
21 | }()
22 |
23 | lazy var goButton: UIButton = {
24 | let button = UIButton(type: .system)
25 | button.translatesAutoresizingMaskIntoConstraints = false
26 | button.layer.cornerRadius = 10
27 | button.layer.borderWidth = 2
28 | button.layer.borderColor = UIColor.blue.cgColor
29 | button.setTitle("find", for: .normal)
30 | button.setTitleColor(.blue, for: .normal)
31 | button.backgroundColor = .white
32 | return button
33 | }()
34 |
35 | private lazy var rulerView: RulerView = {
36 | let view = RulerView(length: 600, divider: 10)
37 | view.translatesAutoresizingMaskIntoConstraints = false
38 | return view
39 | }()
40 |
41 | private lazy var scrollView: ScrollView = {
42 | let view = ScrollView()
43 | view.translatesAutoresizingMaskIntoConstraints = false
44 | return view
45 | }()
46 |
47 | // MARK: Overrides
48 |
49 | override init() {
50 | super.init()
51 |
52 | backgroundColor = .white
53 | addSubview(scrollView)
54 | scrollView.contentView.addSubview(nameTextField)
55 | scrollView.contentView.addSubview(goButton)
56 | scrollView.contentView.addSubview(rulerView)
57 |
58 | NSLayoutConstraint.activate([
59 | scrollView.leadingAnchor.constraint(equalTo: leadingAnchor),
60 | scrollView.trailingAnchor.constraint(equalTo: trailingAnchor),
61 | scrollView.topAnchor.constraint(equalTo: safeAreaLayoutGuide.topAnchor),
62 | scrollView.bottomAnchor.constraint(equalTo: keyboardLayoutGuideBackport.topAnchor),
63 |
64 | goButton.trailingAnchor.constraint(equalTo: scrollView.contentView.trailingAnchor, constant: -20),
65 | goButton.centerYAnchor.constraint(equalTo: nameTextField.centerYAnchor),
66 | goButton.widthAnchor.constraint(equalToConstant: 80),
67 | goButton.heightAnchor.constraint(equalToConstant: 44),
68 |
69 | nameTextField.leadingAnchor.constraint(equalTo: scrollView.contentView.leadingAnchor, constant: 20),
70 | nameTextField.trailingAnchor.constraint(equalTo: goButton.leadingAnchor, constant: -20),
71 | nameTextField.topAnchor.constraint(equalTo: scrollView.contentView.topAnchor, constant: 20),
72 | nameTextField.heightAnchor.constraint(equalToConstant: 44),
73 |
74 | rulerView.leadingAnchor.constraint(equalTo: scrollView.contentView.leadingAnchor),
75 | rulerView.trailingAnchor.constraint(equalTo: scrollView.contentView.trailingAnchor),
76 | rulerView.topAnchor.constraint(equalTo: nameTextField.bottomAnchor, constant: 20),
77 | rulerView.bottomAnchor.constraint(equalTo: scrollView.contentView.bottomAnchor)
78 | ])
79 | }
80 |
81 | // MARK: API
82 |
83 | func scroll(to position: Int) {
84 | let partIndex = position / rulerView.divider
85 | let value = max(min(rulerView.length, position), 0)
86 | let y = rulerView.frame.minY + CGFloat(value / rulerView.divider) * rulerView.partHeight
87 | let maxY = scrollView.contentSize.height - scrollView.bounds.height
88 | scrollView.setContentOffset(CGPoint(x: 0, y: min(y, maxY)), animated: true)
89 | rulerView.highlightPart(at: partIndex)
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/KeyboardLayoutGuide/Cases/Closures/ClosuresCaseViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ClosuresCaseViewController.swift
3 | // KeyboardLayoutGuide
4 | //
5 | // Created by Patryk Kaczmarek on 03/01/2020.
6 | // Copyright © 2020 Patryk Kaczmarek. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | /// Confetti like particle effects inspired by:
12 | /// https://medium.com/@prabhu_irl/particle-effects-in-swift-using-caemitterlayer-79fb88452724
13 | final class ClosuresCaseViewController: ViewController {
14 |
15 | private enum Colors {
16 | static let red = UIColor(red: 1.0, green: 0.0, blue: 77.0/255.0, alpha: 1.0)
17 | static let blue = UIColor.blue
18 | static let green = UIColor(red: 35.0/255.0 , green: 233/255, blue: 173/255.0, alpha: 1.0)
19 | static let yellow = UIColor(red: 1, green: 209/255, blue: 77.0/255.0, alpha: 1.0)
20 | }
21 |
22 | private enum Images {
23 | static let box = UIImage(named: "Box")!
24 | static let triangle = UIImage(named: "Triangle")!
25 | static let circle = UIImage(named: "Circle")!
26 | static let swirl = UIImage(named: "Spiral")!
27 | }
28 |
29 | // MARK: Properties
30 |
31 | private var emitter = CAEmitterLayer()
32 |
33 | private var colors: [UIColor] = [
34 | Colors.red,
35 | Colors.blue,
36 | Colors.green,
37 | Colors.yellow
38 | ]
39 |
40 | private var images: [UIImage] = [
41 | Images.box,
42 | Images.triangle,
43 | Images.circle,
44 | Images.swirl
45 | ]
46 |
47 | private var velocities: [Int] = [
48 | 100,
49 | 90,
50 | 150,
51 | 200
52 | ]
53 |
54 | var animate: Bool = false {
55 | didSet {
56 | if animate {
57 | emitter.emitterPosition = CGPoint(x: view.frame.size.width / 2, y: -10)
58 | emitter.emitterShape = .line
59 | emitter.emitterSize = CGSize(width: view.frame.size.width, height: 2.0)
60 | emitter.emitterCells = generateEmitterCells()
61 | view.layer.addSublayer(emitter)
62 | } else {
63 | emitter.removeFromSuperlayer()
64 | }
65 | }
66 | }
67 |
68 | // MARK: Initializer
69 |
70 | init() {
71 | super.init(view: ClosuresCaseView())
72 | }
73 |
74 | // MARK: Overrides
75 |
76 | override func viewDidLoad() {
77 | super.viewDidLoad()
78 |
79 | customView.textField.delegate = self
80 | automaticallyAdjustKeyboardLayoutGuide = true
81 | onKeyboardWillAppear = { [weak self] _ in
82 | self?.animate = true
83 | }
84 | onKeyboardWillDisappear = { [weak self] _ in
85 | self?.animate = false
86 | }
87 | }
88 | }
89 |
90 | // MARK: UITextFieldDelegate
91 |
92 | extension ClosuresCaseViewController: UITextFieldDelegate {
93 |
94 | func textFieldShouldReturn(_ textField: UITextField) -> Bool {
95 | view.endEditing(true)
96 | return true
97 | }
98 | }
99 |
100 | // MARK: Private
101 |
102 | private extension ClosuresCaseViewController {
103 |
104 | func generateEmitterCells() -> [CAEmitterCell] {
105 | var cells:[CAEmitterCell] = [CAEmitterCell]()
106 | for index in 0..<16 {
107 | let cell = CAEmitterCell()
108 | cell.birthRate = 4.0
109 | cell.lifetime = 14.0
110 | cell.lifetimeRange = 0
111 | cell.velocity = CGFloat(getRandomVelocity())
112 | cell.velocityRange = 0
113 | cell.emissionLongitude = CGFloat(Double.pi)
114 | cell.emissionRange = 0.5
115 | cell.spin = 3.5
116 | cell.spinRange = 0
117 | cell.color = getNextColor(i: index)
118 | cell.contents = getNextImage(i: index)
119 | cell.scaleRange = 0.25
120 | cell.scale = 0.1
121 | cells.append(cell)
122 | }
123 | return cells
124 | }
125 |
126 | func getRandomVelocity() -> Int {
127 | velocities[getRandomNumber()]
128 | }
129 |
130 | func getRandomNumber() -> Int {
131 | Int(arc4random_uniform(4))
132 | }
133 |
134 | func getNextColor(i: Int) -> CGColor {
135 | if i <= 4 {
136 | return colors[0].cgColor
137 | } else if i <= 8 {
138 | return colors[1].cgColor
139 | } else if i <= 12 {
140 | return colors[2].cgColor
141 | } else {
142 | return colors[3].cgColor
143 | }
144 | }
145 |
146 | func getNextImage(i: Int) -> CGImage {
147 | images[i % 4].cgImage!
148 | }
149 | }
150 |
--------------------------------------------------------------------------------
/KeyboardLayoutGuide/KeyboardLayoutGuide/ViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ViewController.swift
3 | // KeyboardLayoutGuide
4 | //
5 | // Created by Patryk Kaczmarek on 19/12/2019.
6 | // Copyright © 2020 Patryk Kaczmarek. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class ViewController: UIViewController {
12 |
13 | // MARK: Properties
14 |
15 | /// Flag indicating whether top keyboard layout guide should be tracked.
16 | /// It is recommended to set this flag in view controller's init method
17 | /// cause keyboard may appear before the view controller's view is fully loaded.
18 | ///
19 | /// Setting this property to `true` automatically registers view controller for keyboard notifications.
20 | /// Setting this property to `false` automatically removes view controller from keyboard notifications.
21 | ///
22 | /// Example:
23 | /// Few view controllers have been initialized at once but only one of them has been displayed.
24 | /// It means that only this one which is visible, loaded its view.
25 | /// Other view controllers will load their views (technically invoke viewDidLoad() method)
26 | /// shortly before they become visible.
27 | ///
28 | /// - SeeAlso: View.keyboardHeightConstraint
29 | /// - SeeAlso: View.KeyboardLayoutGuide
30 | /// - SeeAlso: UIViewController.isViewLoaded
31 | ///
32 | /// - Warning: Switching to emoji keyboard by pressing the language selector button (the globe) works fine.
33 | /// Neverthelss when changing keyboard type by long pressing the language selector button,
34 | /// no UIKeyboardWillChangeFrame notification is sent what causes layout guide to not be adjusted properly.
35 | /// It looks as a bug on iOS 11, since even the apple messages and facebook messenger apps don't handle this right.
36 | var automaticallyAdjustKeyboardLayoutGuide = false {
37 | willSet {
38 | newValue ? startObservingKeyboardNotifications() : stopObservingKeyboardNotifications()
39 | }
40 | }
41 |
42 | /// Closure triggered when keyboard is going to appear.
43 | var onKeyboardWillAppear: ((Notification) -> Void)?
44 |
45 | /// Closure triggered when keyboard is going to disappear.
46 | var onKeyboardWillDisappear: ((Notification) -> Void)?
47 |
48 | /// Custom View
49 | let customView: CustomView
50 |
51 | /// An array of notification names used by self.
52 | private var keyboardNotifications: [Notification.Name] {
53 | [
54 | UIResponder.keyboardWillHideNotification,
55 | UIResponder.keyboardWillShowNotification,
56 | UIResponder.keyboardWillChangeFrameNotification
57 | ]
58 | }
59 |
60 | // MARK: Initializer
61 |
62 | /// Initializes view controller with generic `View`.
63 | ///
64 | /// - Parameters:
65 | /// - view: A main view of view controller that will be placed on top of view controller's view.
66 | init(view: CustomView) {
67 | customView = view
68 | customView.translatesAutoresizingMaskIntoConstraints = false
69 | super.init(nibName: nil, bundle: nil)
70 | }
71 |
72 | @available(*, unavailable) required init?(coder aDecoder: NSCoder) {
73 | fatalError("init(coder:) has not been implemented")
74 | }
75 |
76 | /// - SeeAlso: Swift.deinit
77 | deinit {
78 | stopObservingKeyboardNotifications()
79 | }
80 |
81 | // MARK: Overrides
82 |
83 | /// - SeeAlso: UIViewController.loadView()
84 | override func loadView() {
85 | super.loadView()
86 |
87 | view.addSubview(customView)
88 | NSLayoutConstraint.activate([
89 | customView.topAnchor.constraint(equalTo: view.topAnchor),
90 | customView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
91 | customView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
92 | customView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
93 | ])
94 | }
95 | }
96 |
97 | // MARK: Private
98 |
99 | private extension ViewController {
100 |
101 | func startObservingKeyboardNotifications() {
102 | keyboardNotifications.forEach { registerForNotification(name: $0) }
103 | }
104 |
105 | func stopObservingKeyboardNotifications() {
106 | keyboardNotifications.forEach { NotificationCenter.default.removeObserver(self, name: $0, object: nil) }
107 | }
108 |
109 | func registerForNotification(name: Notification.Name) {
110 | NotificationCenter.default.addObserver(forName: name, object: nil, queue: nil, using: handleNotification(name: name))
111 | }
112 |
113 | func handleNotification(name: Notification.Name) -> (Notification) -> Void {
114 | { [weak self] notification in
115 | guard let self = self, self.automaticallyAdjustKeyboardLayoutGuide, var offset = notification.keyboardRect?.height else {
116 | return
117 | }
118 |
119 | switch name {
120 | case UIResponder.keyboardWillHideNotification:
121 | offset = 0
122 | self.onKeyboardWillDisappear?(notification)
123 | case UIResponder.keyboardWillShowNotification:
124 | self.onKeyboardWillAppear?(notification)
125 | default:
126 | break
127 | }
128 |
129 | let animationDuration = notification.keyboardAnimationDuration ?? 0.25
130 | self.customView.keyboardHeightConstraint.constant = offset
131 | UIView.animate(withDuration: animationDuration) {
132 | self.customView.layoutIfNeeded()
133 | }
134 | }
135 | }
136 | }
137 |
138 | // MARK: Notification
139 |
140 | private extension Notification {
141 |
142 | /// Defines the duration of keyboard animation.
143 | /// Nil if notification doesn't contain such info.
144 | var keyboardAnimationDuration: TimeInterval? {
145 | (userInfo?[UIResponder.keyboardAnimationDurationUserInfoKey] as? NSNumber)?.doubleValue
146 | }
147 |
148 | /// Defines the rectangle of keyboard.
149 | /// Nil if notification doesn't contain such info.
150 | var keyboardRect: CGRect? {
151 | userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect
152 | }
153 | }
154 |
--------------------------------------------------------------------------------
/KeyboardLayoutGuide/car-list.json:
--------------------------------------------------------------------------------
1 | [{"brand": "Seat", "models": ["Alhambra", "Altea", "Altea XL", "Arosa", "Cordoba", "Cordoba Vario", "Exeo", "Ibiza", "Ibiza ST", "Exeo ST", "Leon", "Leon ST", "Inca", "Mii", "Toledo"]},
2 | {"brand": "Renault", "models": ["Captur", "Clio", "Clio Grandtour", "Espace", "Express", "Fluence", "Grand Espace", "Grand Modus", "Grand Scenic", "Kadjar", "Kangoo", "Kangoo Express", "Koleos", "Laguna", "Laguna Grandtour", "Latitude", "Mascott", "Mégane", "Mégane CC", "Mégane Combi", "Mégane Grandtour", "Mégane Coupé", "Mégane Scénic", "Scénic", "Talisman", "Talisman Grandtour", "Thalia", "Twingo", "Wind", "Zoé"]},
3 | {"brand": "Peugeot", "models": ["1007", "107", "106", "108", "2008", "205", "205 Cabrio", "206", "206 CC", "206 SW", "207", "207 CC", "207 SW", "306", "307", "307 CC", "307 SW", "308", "308 CC", "308 SW", "309", "4007", "4008", "405", "406", "407", "407 SW", "5008", "508", "508 SW", "605", "806", "607", "807", "Bipper", "RCZ"]},
4 | {"brand": "Dacia", "models": ["Dokker", "Duster", "Lodgy", "Logan", "Logan MCV", "Logan Van", "Sandero", "Solenza"]},
5 | {"brand": "Citroën", "models": ["Berlingo", "C-Crosser", "C-Elissée", "C-Zero", "C1", "C2", "C3", "C3 Picasso", "C4", "C4 Aircross", "C4 Cactus", "C4 Coupé", "C4 Grand Picasso", "C4 Sedan", "C5", "C5 Break", "C5 Tourer", "C6", "C8", "DS3", "DS4", "DS5", "Evasion", "Jumper", "Jumpy", "Saxo", "Nemo", "Xantia", "Xsara"]},
6 | {"brand": "Opel", "models": ["Agila", "Ampera", "Antara", "Astra", "Astra cabrio", "Astra caravan", "Astra coupé", "Calibra", "Campo", "Cascada", "Corsa", "Frontera", "Insignia", "Insignia kombi", "Kadett", "Meriva", "Mokka", "Movano", "Omega", "Signum", "Vectra", "Vectra Caravan", "Vivaro", "Vivaro Kombi", "Zafira"]},
7 | {"brand": "Alfa Romeo", "models": ["145", "146", "147", "155", "156", "156 Sportwagon", "159", "159 Sportwagon", "164", "166", "4C", "Brera", "GTV", "MiTo", "Crosswagon", "Spider", "GT", "Giulietta", "Giulia"]},
8 | {"brand": "Škoda", "models": ["Favorit", "Felicia", "Citigo", "Fabia", "Fabia Combi", "Fabia Sedan", "Felicia Combi", "Octavia", "Octavia Combi", "Roomster", "Yeti", "Rapid", "Rapid Spaceback", "Superb", "Superb Combi"]},
9 | {"brand": "Chevrolet", "models": ["Alero", "Aveo", "Camaro", "Captiva", "Corvette", "Cruze", "Cruze SW", "Epica", "Equinox", "Evanda", "HHR", "Kalos", "Lacetti", "Lacetti SW", "Lumina", "Malibu", "Matiz", "Monte Carlo", "Nubira", "Orlando", "Spark", "Suburban", "Tacuma", "Tahoe", "Trax"]},
10 | {"brand": "Porsche", "models": ["911 Carrera", "911 Carrera Cabrio", "911 Targa", "911 Turbo", "924", "944", "997", "Boxster", "Cayenne", "Cayman", "Macan", "Panamera"]},
11 | {"brand": "Honda", "models": ["Accord", "Accord Coupé", "Accord Tourer", "City", "Civic", "Civic Aerodeck", "Civic Coupé", "Civic Tourer", "Civic Type R", "CR-V", "CR-X", "CR-Z", "FR-V", "HR-V", "Insight", "Integra", "Jazz", "Legend", "Prelude"]},
12 | {"brand": "Subaru", "models": ["BRZ", "Forester", "Impreza", "Impreza Wagon", "Justy", "Legacy", "Legacy Wagon", "Legacy Outback", "Levorg", "Outback", "SVX", "Tribeca", "Tribeca B9", "XV"]},
13 | {"brand": "Mazda", "models": ["121", "2", "3", "323", "323 Combi", "323 Coupé", "323 F", "5", "6", "6 Combi", "626", "626 Combi", "B-Fighter", "B2500", "BT", "CX-3", "CX-5", "CX-7", "CX-9", "Demio", "MPV", "MX-3", "MX-5", "MX-6", "Premacy", "RX-7", "RX-8", "Xedox 6"]},
14 | {"brand": "Mitsubishi", "models": ["3000 GT", "ASX", "Carisma", "Colt", "Colt CC", "Eclipse", "Fuso canter", "Galant", "Galant Combi", "Grandis", "L200", "L200 Pick up", "L200 Pick up Allrad", "L300", "Lancer", "Lancer Combi", "Lancer Evo", "Lancer Sportback", "Outlander", "Pajero", "Pajeto Pinin", "Pajero Pinin Wagon", "Pajero Sport", "Pajero Wagon", "Space Star"]},
15 | {"brand": "Lexus", "models": ["CT", "GS", "GS 300", "GX", "IS", "IS 200", "IS 250 C", "IS-F", "LS", "LX", "NX", "RC F", "RX", "RX 300", "RX 400h", "RX 450h", "SC 430"]},
16 | {"brand": "Toyota", "models": ["4-Runner", "Auris", "Avensis", "Avensis Combi", "Avensis Van Verso", "Aygo", "Camry", "Carina", "Celica", "Corolla", "Corolla Combi", "Corolla sedan", "Corolla Verso", "FJ Cruiser", "GT86", "Hiace", "Hiace Van", "Highlander", "Hilux", "Land Cruiser", "MR2", "Paseo", "Picnic", "Prius", "RAV4", "Sequoia", "Starlet", "Supra", "Tundra", "Urban Cruiser", "Verso", "Yaris", "Yaris Verso"]},
17 | {"brand": "BMW", "models": ["i3", "i8", "M3", "M4", "M5", "M6", "Rad 1", "Rad 1 Cabrio", "Rad 1 Coupé", "Rad 2", "Rad 2 Active Tourer", "Rad 2 Coupé", "Rad 2 Gran Tourer", "Rad 3", "Rad 3 Cabrio", "Rad 3 Compact", "Rad 3 Coupé", "Rad 3 GT", "Rad 3 Touring", "Rad 4", "Rad 4 Cabrio", "Rad 4 Gran Coupé", "Rad 5", "Rad 5 GT", "Rad 5 Touring", "Rad 6", "Rad 6 Cabrio", "Rad 6 Coupé", "Rad 6 Gran Coupé", "Rad 7", "Rad 8 Coupé", "X1", "X3", "X4", "X5", "X6", "Z3", "Z3 Coupé", "Z3 Roadster", "Z4", "Z4 Roadster"]},
18 | {"brand": "Volkswagen", "models": ["Amarok", "Beetle", "Bora", "Bora Variant", "Caddy", "Caddy Van", "Life", "California", "Caravelle", "CC", "Crafter", "Crafter Van", "Crafter Kombi", "CrossTouran", "Eos", "Fox", "Golf", "Golf Cabrio", "Golf Plus", "Golf Sportvan", "Golf Variant", "Jetta", "LT", "Lupo", "Multivan", "New Beetle", "New Beetle Cabrio", "Passat", "Passat Alltrack", "Passat CC", "Passat Variant", "Passat Variant Van", "Phaeton", "Polo", "Polo Van", "Polo Variant", "Scirocco", "Sharan", "T4", "T4 Caravelle", "T4 Multivan", "T5", "T5 Caravelle", "T5 Multivan", "T5 Transporter Shuttle", "Tiguan", "Touareg", "Touran"]},
19 | {"brand": "Suzuki", "models": ["Alto", "Baleno", "Baleno kombi", "Grand Vitara", "Grand Vitara XL-7", "Ignis", "Jimny", "Kizashi", "Liana", "Samurai", "Splash", "Swift", "SX4", "SX4 Sedan", "Vitara", "Wagon R+"]},
20 | {"brand": "Mercedes-Benz", "models": ["100 D", "115", "124", "126", "190", "190 D", "190 E", "200 - 300", "200 D", "200 E", "210 Van", "210 kombi", "310 Van", "310 kombi", "230 - 300 CE Coupé", "260 - 560 SE", "260 - 560 SEL", "500 - 600 SEC Coupé", "Trieda A", "A", "A L", "AMG GT", "Trieda B", "Trieda C", "C", "C Sportcoupé", "C T", "Citan", "CL", "CL", "CLA", "CLC", "CLK Cabrio", "CLK Coupé", "CLS", "Trieda E", "E", "E Cabrio", "E Coupé", "E T", "Trieda G", "G Cabrio", "GL", "GLA", "GLC", "GLE", "GLK", "Trieda M", "MB 100", "Trieda R", "Trieda S", "S", "S Coupé", "SL", "SLC", "SLK", "SLR", "Sprinter"]},
21 | {"brand": "Saab", "models": ["9-3", "9-3 Cabriolet", "9-3 Coupé", "9-3 SportCombi", "9-5", "9-5 SportCombi", "900", "900 C", "900 C Turbo", "9000"]},
22 | {"brand": "Audi", "models": ["100", "100 Avant", "80", "80 Avant", "80 Cabrio", "90", "A1", "A2", "A3", "A3 Cabriolet", "A3 Limuzina", "A3 Sportback", "A4", "A4 Allroad", "A4 Avant", "A4 Cabriolet", "A5", "A5 Cabriolet", "A5 Sportback", "A6", "A6 Allroad", "A6 Avant", "A7", "A8", "A8 Long", "Q3", "Q5", "Q7", "R8", "RS4 Cabriolet", "RS4/RS4 Avant", "RS5", "RS6 Avant", "RS7", "S3/S3 Sportback", "S4 Cabriolet", "S4/S4 Avant", "S5/S5 Cabriolet", "S6/RS6", "S7", "S8", "SQ5", "TT Coupé", "TT Roadster", "TTS"]},
23 | {"brand": "Kia", "models": ["Avella", "Besta", "Carens", "Carnival", "Cee`d", "Cee`d SW", "Cerato", "K 2500", "Magentis", "Opirus", "Optima", "Picanto", "Pregio", "Pride", "Pro Cee`d", "Rio", "Rio Combi", "Rio sedan", "Sephia", "Shuma", "Sorento", "Soul", "Sportage", "Venga"]},
24 | {"brand": "Land Rover", "models": ["109", "Defender", "Discovery", "Discovery Sport", "Freelander", "Range Rover", "Range Rover Evoque", "Range Rover Sport"]},
25 | {"brand": "Dodge", "models": ["Avenger", "Caliber", "Challenger", "Charger", "Grand Caravan", "Journey", "Magnum", "Nitro", "RAM", "Stealth", "Viper"]},
26 | {"brand": "Chrysler", "models": ["300 C", "300 C Touring", "300 M", "Crossfire", "Grand Voyager", "LHS", "Neon", "Pacifica", "Plymouth", "PT Cruiser", "Sebring", "Sebring Convertible", "Stratus", "Stratus Cabrio", "Town & Country", "Voyager"]},
27 | {"brand": "Ford", "models": ["Aerostar", "B-Max", "C-Max", "Cortina", "Cougar", "Edge", "Escort", "Escort Cabrio", "Escort kombi", "Explorer", "F-150", "F-250", "Fiesta", "Focus", "Focus C-Max", "Focus CC", "Focus kombi", "Fusion", "Galaxy", "Grand C-Max", "Ka", "Kuga", "Maverick", "Mondeo", "Mondeo Combi", "Mustang", "Orion", "Puma", "Ranger", "S-Max", "Sierra", "Street Ka", "Tourneo Connect", "Tourneo Custom", "Transit", "Transit", "Transit Bus", "Transit Connect LWB", "Transit Courier", "Transit Custom", "Transit kombi", "Transit Tourneo", "Transit Valnik", "Transit Van", "Transit Van 350", "Windstar"]},
28 | {"brand": "Hummer", "models": ["H2", "H3"]},
29 | {"brand": "Hyundai", "models": ["Accent", "Atos", "Atos Prime", "Coupé", "Elantra", "Galloper", "Genesis", "Getz", "Grandeur", "H 350", "H1", "H1 Bus", "H1 Van", "H200", "i10", "i20", "i30", "i30 CW", "i40", "i40 CW", "ix20", "ix35", "ix55", "Lantra", "Matrix", "Santa Fe", "Sonata", "Terracan", "Trajet", "Tucson", "Veloster"]},
30 | {"brand": "Infiniti", "models": ["EX", "FX", "G", "G Coupé", "M", "Q", "QX"]},
31 | {"brand": "Jaguar", "models": ["Daimler", "F-Pace", "F-Type", "S-Type", "Sovereign", "X-Type", "X-type Estate", "XE", "XF", "XJ", "XJ12", "XJ6", "XJ8", "XJ8", "XJR", "XK", "XK8 Convertible", "XKR", "XKR Convertible"]},
32 | {"brand": "Jeep", "models": ["Cherokee", "Commander", "Compass", "Grand Cherokee", "Patriot", "Renegade", "Wrangler"]},
33 | {"brand": "Nissan", "models": ["100 NX", "200 SX", "350 Z", "350 Z Roadster", "370 Z", "Almera", "Almera Tino", "Cabstar E - T", "Cabstar TL2 Valnik", "e-NV200", "GT-R", "Insterstar", "Juke", "King Cab", "Leaf", "Maxima", "Maxima QX", "Micra", "Murano", "Navara", "Note", "NP300 Pickup", "NV200", "NV400", "Pathfinder", "Patrol", "Patrol GR", "Pickup", "Pixo", "Primastar", "Primastar Combi", "Primera", "Primera Combi", "Pulsar", "Qashqai", "Serena", "Sunny", "Terrano", "Tiida", "Trade", "Vanette Cargo", "X-Trail"]},
34 | {"brand": "Volvo", "models": ["240", "340", "360", "460", "850", "850 kombi", "C30", "C70", "C70 Cabrio", "C70 Coupé", "S40", "S60", "S70", "S80", "S90", "V40", "V50", "V60", "V70", "V90", "XC60", "XC70", "XC90"]},
35 | {"brand": "Daewoo", "models": ["Espero", "Kalos", "Lacetti", "Lanos", "Leganza", "Lublin", "Matiz", "Nexia", "Nubira", "Nubira kombi", "Racer", "Tacuma", "Tico"]},
36 | {"brand": "Fiat", "models": ["1100", "126", "500", "500L", "500X", "850", "Barchetta", "Brava", "Cinquecento", "Coupé", "Croma", "Doblo", "Doblo Cargo", "Doblo Cargo Combi", "Ducato", "Ducato Van", "Ducato Kombi", "Ducato Podvozok", "Florino", "Florino Combi", "Freemont", "Grande Punto", "Idea", "Linea", "Marea", "Marea Weekend", "Multipla", "Palio Weekend", "Panda", "Panda Van", "Punto", "Punto Cabriolet", "Punto Evo", "Punto Van", "Qubo", "Scudo", "Scudo Van", "Scudo Kombi", "Sedici", "Seicento", "Stilo", "Stilo Multiwagon", "Strada", "Talento", "Tipo", "Ulysse", "Uno", "X1/9"]},
37 | {"brand": "MINI", "models": ["Cooper", "Cooper Cabrio", "Cooper Clubman", "Cooper D", "Cooper D Clubman", "Cooper S", "Cooper S Cabrio", "Cooper S Clubman", "Countryman", "Mini One", "One D"]},
38 | {"brand": "Rover", "models": ["200", "214", "218", "25", "400", "414", "416", "620", "75"]},
39 | {"brand": "Smart", "models": ["Cabrio", "City-Coupé", "Compact Pulse", "Forfour", "Fortwo cabrio", "Fortwo coupé", "Roadster"]}]
40 |
--------------------------------------------------------------------------------
/KeyboardLayoutGuide.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 50;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 5E82785E244732DA00FBA39B /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E82785D244732DA00FBA39B /* AppDelegate.swift */; };
11 | 5E827867244732DE00FBA39B /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 5E827866244732DE00FBA39B /* Assets.xcassets */; };
12 | 5E82786A244732DE00FBA39B /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 5E827868244732DE00FBA39B /* LaunchScreen.storyboard */; };
13 | 5E8278A02447342D00FBA39B /* DemoTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E82789E2447342D00FBA39B /* DemoTableViewController.swift */; };
14 | 5E8278A12447342D00FBA39B /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E82789F2447342D00FBA39B /* SceneDelegate.swift */; };
15 | 5E8278A32447343A00FBA39B /* car-list.json in Resources */ = {isa = PBXBuildFile; fileRef = 5E8278A22447343A00FBA39B /* car-list.json */; };
16 | 5E8278BC2447344C00FBA39B /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E8278A52447344C00FBA39B /* ViewController.swift */; };
17 | 5E8278BD2447344C00FBA39B /* View.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E8278A62447344C00FBA39B /* View.swift */; };
18 | 5E8278BE2447344C00FBA39B /* NSLayoutConstraint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E8278A72447344C00FBA39B /* NSLayoutConstraint.swift */; };
19 | 5E8278BF2447344C00FBA39B /* ScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E8278A82447344C00FBA39B /* ScrollView.swift */; };
20 | 5E8278C02447344C00FBA39B /* ScrollViewCaseView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E8278AB2447344C00FBA39B /* ScrollViewCaseView.swift */; };
21 | 5E8278C12447344C00FBA39B /* RulerPart.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E8278AC2447344C00FBA39B /* RulerPart.swift */; };
22 | 5E8278C22447344C00FBA39B /* ScrollViewCaseViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E8278AD2447344C00FBA39B /* ScrollViewCaseViewController.swift */; };
23 | 5E8278C32447344C00FBA39B /* RulerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E8278AE2447344C00FBA39B /* RulerView.swift */; };
24 | 5E8278C42447344C00FBA39B /* ViewCaseViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E8278B02447344C00FBA39B /* ViewCaseViewController.swift */; };
25 | 5E8278C52447344C00FBA39B /* ViewCaseView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E8278B12447344C00FBA39B /* ViewCaseView.swift */; };
26 | 5E8278C62447344C00FBA39B /* TableViewCaseView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E8278B32447344C00FBA39B /* TableViewCaseView.swift */; };
27 | 5E8278C72447344C00FBA39B /* Car.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E8278B42447344C00FBA39B /* Car.swift */; };
28 | 5E8278C82447344C00FBA39B /* TableViewCaseViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E8278B52447344C00FBA39B /* TableViewCaseViewController.swift */; };
29 | 5E8278C92447344C00FBA39B /* CarTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E8278B62447344C00FBA39B /* CarTableViewCell.swift */; };
30 | 5E8278CA2447344C00FBA39B /* ClosuresCaseView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E8278B82447344C00FBA39B /* ClosuresCaseView.swift */; };
31 | 5E8278CB2447344C00FBA39B /* ClosuresCaseViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E8278B92447344C00FBA39B /* ClosuresCaseViewController.swift */; };
32 | 5E8278CC2447344C00FBA39B /* InsetsTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E8278BB2447344C00FBA39B /* InsetsTextField.swift */; };
33 | /* End PBXBuildFile section */
34 |
35 | /* Begin PBXFileReference section */
36 | 5E82785A244732D900FBA39B /* KeyboardLayoutGuide.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = KeyboardLayoutGuide.app; sourceTree = BUILT_PRODUCTS_DIR; };
37 | 5E82785D244732DA00FBA39B /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
38 | 5E827866244732DE00FBA39B /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
39 | 5E827869244732DE00FBA39B /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
40 | 5E82789E2447342D00FBA39B /* DemoTableViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DemoTableViewController.swift; sourceTree = ""; };
41 | 5E82789F2447342D00FBA39B /* SceneDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; };
42 | 5E8278A22447343A00FBA39B /* car-list.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "car-list.json"; sourceTree = ""; };
43 | 5E8278A52447344C00FBA39B /* ViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; };
44 | 5E8278A62447344C00FBA39B /* View.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = View.swift; sourceTree = ""; };
45 | 5E8278A72447344C00FBA39B /* NSLayoutConstraint.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NSLayoutConstraint.swift; sourceTree = ""; };
46 | 5E8278A82447344C00FBA39B /* ScrollView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ScrollView.swift; sourceTree = ""; };
47 | 5E8278AB2447344C00FBA39B /* ScrollViewCaseView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ScrollViewCaseView.swift; sourceTree = ""; };
48 | 5E8278AC2447344C00FBA39B /* RulerPart.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RulerPart.swift; sourceTree = ""; };
49 | 5E8278AD2447344C00FBA39B /* ScrollViewCaseViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ScrollViewCaseViewController.swift; sourceTree = ""; };
50 | 5E8278AE2447344C00FBA39B /* RulerView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RulerView.swift; sourceTree = ""; };
51 | 5E8278B02447344C00FBA39B /* ViewCaseViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ViewCaseViewController.swift; sourceTree = ""; };
52 | 5E8278B12447344C00FBA39B /* ViewCaseView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ViewCaseView.swift; sourceTree = ""; };
53 | 5E8278B32447344C00FBA39B /* TableViewCaseView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TableViewCaseView.swift; sourceTree = ""; };
54 | 5E8278B42447344C00FBA39B /* Car.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Car.swift; sourceTree = ""; };
55 | 5E8278B52447344C00FBA39B /* TableViewCaseViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TableViewCaseViewController.swift; sourceTree = ""; };
56 | 5E8278B62447344C00FBA39B /* CarTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CarTableViewCell.swift; sourceTree = ""; };
57 | 5E8278B82447344C00FBA39B /* ClosuresCaseView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ClosuresCaseView.swift; sourceTree = ""; };
58 | 5E8278B92447344C00FBA39B /* ClosuresCaseViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ClosuresCaseViewController.swift; sourceTree = ""; };
59 | 5E8278BB2447344C00FBA39B /* InsetsTextField.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InsetsTextField.swift; sourceTree = ""; };
60 | 5E8278ED244734E700FBA39B /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
61 | /* End PBXFileReference section */
62 |
63 | /* Begin PBXFrameworksBuildPhase section */
64 | 5E827857244732D900FBA39B /* Frameworks */ = {
65 | isa = PBXFrameworksBuildPhase;
66 | buildActionMask = 2147483647;
67 | files = (
68 | );
69 | runOnlyForDeploymentPostprocessing = 0;
70 | };
71 | /* End PBXFrameworksBuildPhase section */
72 |
73 | /* Begin PBXGroup section */
74 | 5E827851244732D900FBA39B = {
75 | isa = PBXGroup;
76 | children = (
77 | 5E82785C244732DA00FBA39B /* KeyboardLayoutGuide */,
78 | 5E82785B244732D900FBA39B /* Products */,
79 | );
80 | sourceTree = "";
81 | };
82 | 5E82785B244732D900FBA39B /* Products */ = {
83 | isa = PBXGroup;
84 | children = (
85 | 5E82785A244732D900FBA39B /* KeyboardLayoutGuide.app */,
86 | );
87 | name = Products;
88 | sourceTree = "";
89 | };
90 | 5E82785C244732DA00FBA39B /* KeyboardLayoutGuide */ = {
91 | isa = PBXGroup;
92 | children = (
93 | 5E82785D244732DA00FBA39B /* AppDelegate.swift */,
94 | 5E82789F2447342D00FBA39B /* SceneDelegate.swift */,
95 | 5E82789E2447342D00FBA39B /* DemoTableViewController.swift */,
96 | 5E8278A92447344C00FBA39B /* Cases */,
97 | 5E8278BA2447344C00FBA39B /* Common */,
98 | 5E8278A42447344C00FBA39B /* KeyboardLayoutGuide */,
99 | 5E827866244732DE00FBA39B /* Assets.xcassets */,
100 | 5E827868244732DE00FBA39B /* LaunchScreen.storyboard */,
101 | 5E8278A22447343A00FBA39B /* car-list.json */,
102 | 5E8278ED244734E700FBA39B /* Info.plist */,
103 | );
104 | path = KeyboardLayoutGuide;
105 | sourceTree = "";
106 | };
107 | 5E8278A42447344C00FBA39B /* KeyboardLayoutGuide */ = {
108 | isa = PBXGroup;
109 | children = (
110 | 5E8278A52447344C00FBA39B /* ViewController.swift */,
111 | 5E8278A62447344C00FBA39B /* View.swift */,
112 | 5E8278A72447344C00FBA39B /* NSLayoutConstraint.swift */,
113 | 5E8278A82447344C00FBA39B /* ScrollView.swift */,
114 | );
115 | path = KeyboardLayoutGuide;
116 | sourceTree = "";
117 | };
118 | 5E8278A92447344C00FBA39B /* Cases */ = {
119 | isa = PBXGroup;
120 | children = (
121 | 5E8278AA2447344C00FBA39B /* UIScrollView */,
122 | 5E8278AF2447344C00FBA39B /* UIView */,
123 | 5E8278B22447344C00FBA39B /* UITableView */,
124 | 5E8278B72447344C00FBA39B /* Closures */,
125 | );
126 | path = Cases;
127 | sourceTree = "";
128 | };
129 | 5E8278AA2447344C00FBA39B /* UIScrollView */ = {
130 | isa = PBXGroup;
131 | children = (
132 | 5E8278AB2447344C00FBA39B /* ScrollViewCaseView.swift */,
133 | 5E8278AC2447344C00FBA39B /* RulerPart.swift */,
134 | 5E8278AD2447344C00FBA39B /* ScrollViewCaseViewController.swift */,
135 | 5E8278AE2447344C00FBA39B /* RulerView.swift */,
136 | );
137 | path = UIScrollView;
138 | sourceTree = "";
139 | };
140 | 5E8278AF2447344C00FBA39B /* UIView */ = {
141 | isa = PBXGroup;
142 | children = (
143 | 5E8278B02447344C00FBA39B /* ViewCaseViewController.swift */,
144 | 5E8278B12447344C00FBA39B /* ViewCaseView.swift */,
145 | );
146 | path = UIView;
147 | sourceTree = "";
148 | };
149 | 5E8278B22447344C00FBA39B /* UITableView */ = {
150 | isa = PBXGroup;
151 | children = (
152 | 5E8278B32447344C00FBA39B /* TableViewCaseView.swift */,
153 | 5E8278B42447344C00FBA39B /* Car.swift */,
154 | 5E8278B52447344C00FBA39B /* TableViewCaseViewController.swift */,
155 | 5E8278B62447344C00FBA39B /* CarTableViewCell.swift */,
156 | );
157 | path = UITableView;
158 | sourceTree = "";
159 | };
160 | 5E8278B72447344C00FBA39B /* Closures */ = {
161 | isa = PBXGroup;
162 | children = (
163 | 5E8278B82447344C00FBA39B /* ClosuresCaseView.swift */,
164 | 5E8278B92447344C00FBA39B /* ClosuresCaseViewController.swift */,
165 | );
166 | path = Closures;
167 | sourceTree = "";
168 | };
169 | 5E8278BA2447344C00FBA39B /* Common */ = {
170 | isa = PBXGroup;
171 | children = (
172 | 5E8278BB2447344C00FBA39B /* InsetsTextField.swift */,
173 | );
174 | path = Common;
175 | sourceTree = "";
176 | };
177 | /* End PBXGroup section */
178 |
179 | /* Begin PBXNativeTarget section */
180 | 5E827859244732D900FBA39B /* KeyboardLayoutGuide */ = {
181 | isa = PBXNativeTarget;
182 | buildConfigurationList = 5E82786E244732DE00FBA39B /* Build configuration list for PBXNativeTarget "KeyboardLayoutGuide" */;
183 | buildPhases = (
184 | 5E827856244732D900FBA39B /* Sources */,
185 | 5E827857244732D900FBA39B /* Frameworks */,
186 | 5E827858244732D900FBA39B /* Resources */,
187 | );
188 | buildRules = (
189 | );
190 | dependencies = (
191 | );
192 | name = KeyboardLayoutGuide;
193 | productName = KeyboardLayoutGuide;
194 | productReference = 5E82785A244732D900FBA39B /* KeyboardLayoutGuide.app */;
195 | productType = "com.apple.product-type.application";
196 | };
197 | /* End PBXNativeTarget section */
198 |
199 | /* Begin PBXProject section */
200 | 5E827852244732D900FBA39B /* Project object */ = {
201 | isa = PBXProject;
202 | attributes = {
203 | LastSwiftUpdateCheck = 1130;
204 | LastUpgradeCheck = 1130;
205 | ORGANIZATIONNAME = Netguru;
206 | TargetAttributes = {
207 | 5E827859244732D900FBA39B = {
208 | CreatedOnToolsVersion = 11.3.1;
209 | };
210 | };
211 | };
212 | buildConfigurationList = 5E827855244732D900FBA39B /* Build configuration list for PBXProject "KeyboardLayoutGuide" */;
213 | compatibilityVersion = "Xcode 9.3";
214 | developmentRegion = en;
215 | hasScannedForEncodings = 0;
216 | knownRegions = (
217 | en,
218 | Base,
219 | );
220 | mainGroup = 5E827851244732D900FBA39B;
221 | productRefGroup = 5E82785B244732D900FBA39B /* Products */;
222 | projectDirPath = "";
223 | projectRoot = "";
224 | targets = (
225 | 5E827859244732D900FBA39B /* KeyboardLayoutGuide */,
226 | );
227 | };
228 | /* End PBXProject section */
229 |
230 | /* Begin PBXResourcesBuildPhase section */
231 | 5E827858244732D900FBA39B /* Resources */ = {
232 | isa = PBXResourcesBuildPhase;
233 | buildActionMask = 2147483647;
234 | files = (
235 | 5E8278A32447343A00FBA39B /* car-list.json in Resources */,
236 | 5E82786A244732DE00FBA39B /* LaunchScreen.storyboard in Resources */,
237 | 5E827867244732DE00FBA39B /* Assets.xcassets in Resources */,
238 | );
239 | runOnlyForDeploymentPostprocessing = 0;
240 | };
241 | /* End PBXResourcesBuildPhase section */
242 |
243 | /* Begin PBXSourcesBuildPhase section */
244 | 5E827856244732D900FBA39B /* Sources */ = {
245 | isa = PBXSourcesBuildPhase;
246 | buildActionMask = 2147483647;
247 | files = (
248 | 5E8278C62447344C00FBA39B /* TableViewCaseView.swift in Sources */,
249 | 5E8278C52447344C00FBA39B /* ViewCaseView.swift in Sources */,
250 | 5E8278C92447344C00FBA39B /* CarTableViewCell.swift in Sources */,
251 | 5E8278C72447344C00FBA39B /* Car.swift in Sources */,
252 | 5E82785E244732DA00FBA39B /* AppDelegate.swift in Sources */,
253 | 5E8278CB2447344C00FBA39B /* ClosuresCaseViewController.swift in Sources */,
254 | 5E8278BF2447344C00FBA39B /* ScrollView.swift in Sources */,
255 | 5E8278CA2447344C00FBA39B /* ClosuresCaseView.swift in Sources */,
256 | 5E8278BE2447344C00FBA39B /* NSLayoutConstraint.swift in Sources */,
257 | 5E8278A02447342D00FBA39B /* DemoTableViewController.swift in Sources */,
258 | 5E8278C02447344C00FBA39B /* ScrollViewCaseView.swift in Sources */,
259 | 5E8278CC2447344C00FBA39B /* InsetsTextField.swift in Sources */,
260 | 5E8278C12447344C00FBA39B /* RulerPart.swift in Sources */,
261 | 5E8278BD2447344C00FBA39B /* View.swift in Sources */,
262 | 5E8278C32447344C00FBA39B /* RulerView.swift in Sources */,
263 | 5E8278BC2447344C00FBA39B /* ViewController.swift in Sources */,
264 | 5E8278C42447344C00FBA39B /* ViewCaseViewController.swift in Sources */,
265 | 5E8278C22447344C00FBA39B /* ScrollViewCaseViewController.swift in Sources */,
266 | 5E8278C82447344C00FBA39B /* TableViewCaseViewController.swift in Sources */,
267 | 5E8278A12447342D00FBA39B /* SceneDelegate.swift in Sources */,
268 | );
269 | runOnlyForDeploymentPostprocessing = 0;
270 | };
271 | /* End PBXSourcesBuildPhase section */
272 |
273 | /* Begin PBXVariantGroup section */
274 | 5E827868244732DE00FBA39B /* LaunchScreen.storyboard */ = {
275 | isa = PBXVariantGroup;
276 | children = (
277 | 5E827869244732DE00FBA39B /* Base */,
278 | );
279 | name = LaunchScreen.storyboard;
280 | sourceTree = "";
281 | };
282 | /* End PBXVariantGroup section */
283 |
284 | /* Begin XCBuildConfiguration section */
285 | 5E82786C244732DE00FBA39B /* Debug */ = {
286 | isa = XCBuildConfiguration;
287 | buildSettings = {
288 | ALWAYS_SEARCH_USER_PATHS = NO;
289 | CLANG_ANALYZER_NONNULL = YES;
290 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
291 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
292 | CLANG_CXX_LIBRARY = "libc++";
293 | CLANG_ENABLE_MODULES = YES;
294 | CLANG_ENABLE_OBJC_ARC = YES;
295 | CLANG_ENABLE_OBJC_WEAK = YES;
296 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
297 | CLANG_WARN_BOOL_CONVERSION = YES;
298 | CLANG_WARN_COMMA = YES;
299 | CLANG_WARN_CONSTANT_CONVERSION = YES;
300 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
301 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
302 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
303 | CLANG_WARN_EMPTY_BODY = YES;
304 | CLANG_WARN_ENUM_CONVERSION = YES;
305 | CLANG_WARN_INFINITE_RECURSION = YES;
306 | CLANG_WARN_INT_CONVERSION = YES;
307 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
308 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
309 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
310 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
311 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
312 | CLANG_WARN_STRICT_PROTOTYPES = YES;
313 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
314 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
315 | CLANG_WARN_UNREACHABLE_CODE = YES;
316 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
317 | COPY_PHASE_STRIP = NO;
318 | DEBUG_INFORMATION_FORMAT = dwarf;
319 | ENABLE_STRICT_OBJC_MSGSEND = YES;
320 | ENABLE_TESTABILITY = YES;
321 | GCC_C_LANGUAGE_STANDARD = gnu11;
322 | GCC_DYNAMIC_NO_PIC = NO;
323 | GCC_NO_COMMON_BLOCKS = YES;
324 | GCC_OPTIMIZATION_LEVEL = 0;
325 | GCC_PREPROCESSOR_DEFINITIONS = (
326 | "DEBUG=1",
327 | "$(inherited)",
328 | );
329 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
330 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
331 | GCC_WARN_UNDECLARED_SELECTOR = YES;
332 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
333 | GCC_WARN_UNUSED_FUNCTION = YES;
334 | GCC_WARN_UNUSED_VARIABLE = YES;
335 | IPHONEOS_DEPLOYMENT_TARGET = 13.2;
336 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
337 | MTL_FAST_MATH = YES;
338 | ONLY_ACTIVE_ARCH = YES;
339 | SDKROOT = iphoneos;
340 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
341 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
342 | };
343 | name = Debug;
344 | };
345 | 5E82786D244732DE00FBA39B /* Release */ = {
346 | isa = XCBuildConfiguration;
347 | buildSettings = {
348 | ALWAYS_SEARCH_USER_PATHS = NO;
349 | CLANG_ANALYZER_NONNULL = YES;
350 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
351 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
352 | CLANG_CXX_LIBRARY = "libc++";
353 | CLANG_ENABLE_MODULES = YES;
354 | CLANG_ENABLE_OBJC_ARC = YES;
355 | CLANG_ENABLE_OBJC_WEAK = YES;
356 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
357 | CLANG_WARN_BOOL_CONVERSION = YES;
358 | CLANG_WARN_COMMA = YES;
359 | CLANG_WARN_CONSTANT_CONVERSION = YES;
360 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
361 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
362 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
363 | CLANG_WARN_EMPTY_BODY = YES;
364 | CLANG_WARN_ENUM_CONVERSION = YES;
365 | CLANG_WARN_INFINITE_RECURSION = YES;
366 | CLANG_WARN_INT_CONVERSION = YES;
367 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
368 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
369 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
370 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
371 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
372 | CLANG_WARN_STRICT_PROTOTYPES = YES;
373 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
374 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
375 | CLANG_WARN_UNREACHABLE_CODE = YES;
376 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
377 | COPY_PHASE_STRIP = NO;
378 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
379 | ENABLE_NS_ASSERTIONS = NO;
380 | ENABLE_STRICT_OBJC_MSGSEND = YES;
381 | GCC_C_LANGUAGE_STANDARD = gnu11;
382 | GCC_NO_COMMON_BLOCKS = YES;
383 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
384 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
385 | GCC_WARN_UNDECLARED_SELECTOR = YES;
386 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
387 | GCC_WARN_UNUSED_FUNCTION = YES;
388 | GCC_WARN_UNUSED_VARIABLE = YES;
389 | IPHONEOS_DEPLOYMENT_TARGET = 13.2;
390 | MTL_ENABLE_DEBUG_INFO = NO;
391 | MTL_FAST_MATH = YES;
392 | SDKROOT = iphoneos;
393 | SWIFT_COMPILATION_MODE = wholemodule;
394 | SWIFT_OPTIMIZATION_LEVEL = "-O";
395 | VALIDATE_PRODUCT = YES;
396 | };
397 | name = Release;
398 | };
399 | 5E82786F244732DE00FBA39B /* Debug */ = {
400 | isa = XCBuildConfiguration;
401 | buildSettings = {
402 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
403 | CODE_SIGN_STYLE = Automatic;
404 | INFOPLIST_FILE = KeyboardLayoutGuide/Info.plist;
405 | IPHONEOS_DEPLOYMENT_TARGET = 13.0;
406 | LD_RUNPATH_SEARCH_PATHS = (
407 | "$(inherited)",
408 | "@executable_path/Frameworks",
409 | );
410 | PRODUCT_BUNDLE_IDENTIFIER = com.netguru.KeyboardLayoutGuide;
411 | PRODUCT_NAME = "$(TARGET_NAME)";
412 | SWIFT_VERSION = 5.0;
413 | TARGETED_DEVICE_FAMILY = "1,2";
414 | };
415 | name = Debug;
416 | };
417 | 5E827870244732DE00FBA39B /* Release */ = {
418 | isa = XCBuildConfiguration;
419 | buildSettings = {
420 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
421 | CODE_SIGN_STYLE = Automatic;
422 | INFOPLIST_FILE = KeyboardLayoutGuide/Info.plist;
423 | IPHONEOS_DEPLOYMENT_TARGET = 13.0;
424 | LD_RUNPATH_SEARCH_PATHS = (
425 | "$(inherited)",
426 | "@executable_path/Frameworks",
427 | );
428 | PRODUCT_BUNDLE_IDENTIFIER = com.netguru.KeyboardLayoutGuide;
429 | PRODUCT_NAME = "$(TARGET_NAME)";
430 | SWIFT_VERSION = 5.0;
431 | TARGETED_DEVICE_FAMILY = "1,2";
432 | };
433 | name = Release;
434 | };
435 | /* End XCBuildConfiguration section */
436 |
437 | /* Begin XCConfigurationList section */
438 | 5E827855244732D900FBA39B /* Build configuration list for PBXProject "KeyboardLayoutGuide" */ = {
439 | isa = XCConfigurationList;
440 | buildConfigurations = (
441 | 5E82786C244732DE00FBA39B /* Debug */,
442 | 5E82786D244732DE00FBA39B /* Release */,
443 | );
444 | defaultConfigurationIsVisible = 0;
445 | defaultConfigurationName = Release;
446 | };
447 | 5E82786E244732DE00FBA39B /* Build configuration list for PBXNativeTarget "KeyboardLayoutGuide" */ = {
448 | isa = XCConfigurationList;
449 | buildConfigurations = (
450 | 5E82786F244732DE00FBA39B /* Debug */,
451 | 5E827870244732DE00FBA39B /* Release */,
452 | );
453 | defaultConfigurationIsVisible = 0;
454 | defaultConfigurationName = Release;
455 | };
456 | /* End XCConfigurationList section */
457 | };
458 | rootObject = 5E827852244732D900FBA39B /* Project object */;
459 | }
460 |
--------------------------------------------------------------------------------