├── 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 | --------------------------------------------------------------------------------