diff --git a/AR-Tester.xcodeproj/project.pbxproj b/AR-Tester.xcodeproj/project.pbxproj index 7770c19..da96dce 100644 --- a/AR-Tester.xcodeproj/project.pbxproj +++ b/AR-Tester.xcodeproj/project.pbxproj @@ -7,6 +7,9 @@ objects = { /* Begin PBXBuildFile section */ + 236D22351FD04B4C0050D127 /* KeyboardHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 236D22341FD04B4C0050D127 /* KeyboardHandler.swift */; }; + 236D22371FD04CFF0050D127 /* UIViewController+KeyboardHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 236D22361FD04CFF0050D127 /* UIViewController+KeyboardHandler.swift */; }; + 236D22391FD04EBF0050D127 /* UIScrollView+Configure.swift in Sources */ = {isa = PBXBuildFile; fileRef = 236D22381FD04EBF0050D127 /* UIScrollView+Configure.swift */; }; 62C72C025493CD7BF278804E /* libPods-AR-Tester.a in Frameworks */ = {isa = PBXBuildFile; fileRef = DB964CD86DAE6957500DA1AA /* libPods-AR-Tester.a */; }; C21393DC1F545435002BF34B /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C21393DB1F545435002BF34B /* AppDelegate.swift */; }; C21393DE1F545435002BF34B /* art.scnassets in Resources */ = {isa = PBXBuildFile; fileRef = C21393DD1F545435002BF34B /* art.scnassets */; }; @@ -18,6 +21,9 @@ /* End PBXBuildFile section */ /* Begin PBXFileReference section */ + 236D22341FD04B4C0050D127 /* KeyboardHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyboardHandler.swift; sourceTree = ""; }; + 236D22361FD04CFF0050D127 /* UIViewController+KeyboardHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIViewController+KeyboardHandler.swift"; sourceTree = ""; }; + 236D22381FD04EBF0050D127 /* UIScrollView+Configure.swift */ = {isa = PBXFileReference; indentWidth = 4; lastKnownFileType = sourcecode.swift; path = "UIScrollView+Configure.swift"; sourceTree = ""; tabWidth = 4; }; 2B16C0D3EC27696A7455BF75 /* Pods-AR-Tester.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AR-Tester.release.xcconfig"; path = "Pods/Target Support Files/Pods-AR-Tester/Pods-AR-Tester.release.xcconfig"; sourceTree = ""; }; 97FFA548E22A16129B7B6B41 /* Pods-AR-Tester.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AR-Tester.debug.xcconfig"; path = "Pods/Target Support Files/Pods-AR-Tester/Pods-AR-Tester.debug.xcconfig"; sourceTree = ""; }; C21393D81F545435002BF34B /* AR-Tester.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "AR-Tester.app"; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -84,12 +90,15 @@ children = ( C21393DB1F545435002BF34B /* AppDelegate.swift */, C2FC140A1F5B6EEE00DBA490 /* StartSceneViewController.swift */, + 236D22341FD04B4C0050D127 /* KeyboardHandler.swift */, + 236D22361FD04CFF0050D127 /* UIViewController+KeyboardHandler.swift */, C21393DD1F545435002BF34B /* art.scnassets */, C21393DF1F545435002BF34B /* CameraController.swift */, C21393E11F545435002BF34B /* Main.storyboard */, C21393E41F545435002BF34B /* Assets.xcassets */, C21393E61F545435002BF34B /* LaunchScreen.storyboard */, C21393E91F545435002BF34B /* Info.plist */, + 236D22381FD04EBF0050D127 /* UIScrollView+Configure.swift */, ); path = "AR-Tester"; sourceTree = ""; @@ -265,9 +274,12 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 236D22391FD04EBF0050D127 /* UIScrollView+Configure.swift in Sources */, C2FC140B1F5B6EEE00DBA490 /* StartSceneViewController.swift in Sources */, C21393E01F545435002BF34B /* CameraController.swift in Sources */, C21393DC1F545435002BF34B /* AppDelegate.swift in Sources */, + 236D22351FD04B4C0050D127 /* KeyboardHandler.swift in Sources */, + 236D22371FD04CFF0050D127 /* UIViewController+KeyboardHandler.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/AR-Tester/Assets.xcassets/AppIcon.appiconset/Contents.json b/AR-Tester/Assets.xcassets/AppIcon.appiconset/Contents.json index d8db8d6..e606dc3 100644 --- a/AR-Tester/Assets.xcassets/AppIcon.appiconset/Contents.json +++ b/AR-Tester/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -6,8 +6,9 @@ "scale" : "2x" }, { - "idiom" : "iphone", "size" : "20x20", + "idiom" : "iphone", + "filename" : "icon_20pt@3x.png", "scale" : "3x" }, { @@ -16,8 +17,9 @@ "scale" : "2x" }, { - "idiom" : "iphone", "size" : "29x29", + "idiom" : "iphone", + "filename" : "icon_29pt@3x.png", "scale" : "3x" }, { @@ -31,18 +33,21 @@ "scale" : "3x" }, { - "idiom" : "iphone", "size" : "60x60", + "idiom" : "iphone", + "filename" : "icon_60pt@2x.png", "scale" : "2x" }, { - "idiom" : "iphone", "size" : "60x60", + "idiom" : "iphone", + "filename" : "icon_60pt@3x.png", "scale" : "3x" }, { - "idiom" : "ipad", "size" : "20x20", + "idiom" : "ipad", + "filename" : "icon_20pt.png", "scale" : "1x" }, { @@ -51,43 +56,51 @@ "scale" : "2x" }, { - "idiom" : "ipad", "size" : "29x29", + "idiom" : "ipad", + "filename" : "icon_29pt.png", "scale" : "1x" }, { - "idiom" : "ipad", "size" : "29x29", + "idiom" : "ipad", + "filename" : "icon_29pt@2x.png", "scale" : "2x" }, { - "idiom" : "ipad", "size" : "40x40", + "idiom" : "ipad", + "filename" : "icon_40pt.png", "scale" : "1x" }, { - "idiom" : "ipad", "size" : "40x40", + "idiom" : "ipad", + "filename" : "icon_40pt@2x.png", "scale" : "2x" }, { - "idiom" : "ipad", "size" : "76x76", + "idiom" : "ipad", + "filename" : "icon_76pt.png", "scale" : "1x" }, { - "idiom" : "ipad", "size" : "76x76", + "idiom" : "ipad", + "filename" : "icon_76pt@2x.png", "scale" : "2x" }, { - "idiom" : "ipad", "size" : "83.5x83.5", + "idiom" : "ipad", + "filename" : "icon_83.5@2x.png", "scale" : "2x" }, { - "idiom" : "ios-marketing", "size" : "1024x1024", + "idiom" : "ios-marketing", + "filename" : "Icon.png", "scale" : "1x" } ], diff --git a/AR-Tester/Assets.xcassets/AppIcon.appiconset/Icon.png b/AR-Tester/Assets.xcassets/AppIcon.appiconset/Icon.png new file mode 100644 index 0000000..1656f72 Binary files /dev/null and b/AR-Tester/Assets.xcassets/AppIcon.appiconset/Icon.png differ diff --git a/AR-Tester/Assets.xcassets/AppIcon.appiconset/icon_20pt.png b/AR-Tester/Assets.xcassets/AppIcon.appiconset/icon_20pt.png new file mode 100644 index 0000000..3b14be7 Binary files /dev/null and b/AR-Tester/Assets.xcassets/AppIcon.appiconset/icon_20pt.png differ diff --git a/AR-Tester/Assets.xcassets/AppIcon.appiconset/icon_20pt@3x.png b/AR-Tester/Assets.xcassets/AppIcon.appiconset/icon_20pt@3x.png new file mode 100644 index 0000000..d6377f4 Binary files /dev/null and b/AR-Tester/Assets.xcassets/AppIcon.appiconset/icon_20pt@3x.png differ diff --git a/AR-Tester/Assets.xcassets/AppIcon.appiconset/icon_29pt.png b/AR-Tester/Assets.xcassets/AppIcon.appiconset/icon_29pt.png new file mode 100644 index 0000000..9059f20 Binary files /dev/null and b/AR-Tester/Assets.xcassets/AppIcon.appiconset/icon_29pt.png differ diff --git a/AR-Tester/Assets.xcassets/AppIcon.appiconset/icon_29pt@2x.png b/AR-Tester/Assets.xcassets/AppIcon.appiconset/icon_29pt@2x.png new file mode 100644 index 0000000..7a29db2 Binary files /dev/null and b/AR-Tester/Assets.xcassets/AppIcon.appiconset/icon_29pt@2x.png differ diff --git a/AR-Tester/Assets.xcassets/AppIcon.appiconset/icon_29pt@3x.png b/AR-Tester/Assets.xcassets/AppIcon.appiconset/icon_29pt@3x.png new file mode 100644 index 0000000..d59ed69 Binary files /dev/null and b/AR-Tester/Assets.xcassets/AppIcon.appiconset/icon_29pt@3x.png differ diff --git a/AR-Tester/Assets.xcassets/AppIcon.appiconset/icon_40pt.png b/AR-Tester/Assets.xcassets/AppIcon.appiconset/icon_40pt.png new file mode 100644 index 0000000..fcfc932 Binary files /dev/null and b/AR-Tester/Assets.xcassets/AppIcon.appiconset/icon_40pt.png differ diff --git a/AR-Tester/Assets.xcassets/AppIcon.appiconset/icon_40pt@2x.png b/AR-Tester/Assets.xcassets/AppIcon.appiconset/icon_40pt@2x.png new file mode 100644 index 0000000..6804d7b Binary files /dev/null and b/AR-Tester/Assets.xcassets/AppIcon.appiconset/icon_40pt@2x.png differ diff --git a/AR-Tester/Assets.xcassets/AppIcon.appiconset/icon_60pt@2x.png b/AR-Tester/Assets.xcassets/AppIcon.appiconset/icon_60pt@2x.png new file mode 100644 index 0000000..d8363d3 Binary files /dev/null and b/AR-Tester/Assets.xcassets/AppIcon.appiconset/icon_60pt@2x.png differ diff --git a/AR-Tester/Assets.xcassets/AppIcon.appiconset/icon_60pt@3x.png b/AR-Tester/Assets.xcassets/AppIcon.appiconset/icon_60pt@3x.png new file mode 100644 index 0000000..38fc3f9 Binary files /dev/null and b/AR-Tester/Assets.xcassets/AppIcon.appiconset/icon_60pt@3x.png differ diff --git a/AR-Tester/Assets.xcassets/AppIcon.appiconset/icon_76pt.png b/AR-Tester/Assets.xcassets/AppIcon.appiconset/icon_76pt.png new file mode 100644 index 0000000..f243297 Binary files /dev/null and b/AR-Tester/Assets.xcassets/AppIcon.appiconset/icon_76pt.png differ diff --git a/AR-Tester/Assets.xcassets/AppIcon.appiconset/icon_76pt@2x.png b/AR-Tester/Assets.xcassets/AppIcon.appiconset/icon_76pt@2x.png new file mode 100644 index 0000000..790e7b8 Binary files /dev/null and b/AR-Tester/Assets.xcassets/AppIcon.appiconset/icon_76pt@2x.png differ diff --git a/AR-Tester/Assets.xcassets/AppIcon.appiconset/icon_83.5@2x.png b/AR-Tester/Assets.xcassets/AppIcon.appiconset/icon_83.5@2x.png new file mode 100644 index 0000000..5eb37b0 Binary files /dev/null and b/AR-Tester/Assets.xcassets/AppIcon.appiconset/icon_83.5@2x.png differ diff --git a/AR-Tester/Base.lproj/Main.storyboard b/AR-Tester/Base.lproj/Main.storyboard index ea32880..b0b27bb 100644 --- a/AR-Tester/Base.lproj/Main.storyboard +++ b/AR-Tester/Base.lproj/Main.storyboard @@ -1,10 +1,10 @@ - + - + @@ -70,7 +70,7 @@ - + @@ -124,6 +124,7 @@ + @@ -138,6 +139,9 @@ + + + @@ -151,11 +155,17 @@ + + + + + + diff --git a/AR-Tester/Info.plist b/AR-Tester/Info.plist index ac95952..c555653 100644 --- a/AR-Tester/Info.plist +++ b/AR-Tester/Info.plist @@ -52,8 +52,6 @@ UISupportedInterfaceOrientations UIInterfaceOrientationPortrait - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight UISupportedInterfaceOrientations~ipad diff --git a/AR-Tester/KeyboardHandler.swift b/AR-Tester/KeyboardHandler.swift new file mode 100644 index 0000000..a70d8dd --- /dev/null +++ b/AR-Tester/KeyboardHandler.swift @@ -0,0 +1,177 @@ +// +// KeyboardHandler.swift +// AR-Tester +// +// Created by Egor Petrov on 23.02.17. +// Copyright © 2017 Egor Petrov. All rights reserved. +// + +import UIKit + +var activeInputView: UIView? +var prevInputView: UIView? + +protocol KeyboardHandlerDelegate: class { + /// This function handles keyboard state changing + /// + /// - parameter input: Active input component - `UITextField` or `UITextView` + /// - parameter state: Keyboard state - .opened, .frameChanged, .hidden + /// - parameter info: Keyboard appearance and size information + /// + func keyboardStateChanged(input: UIView?, state: KeyboardState, info: KeyboardInfo) + func keyboardActiveInputViewChanged(input: UIView?, info: KeyboardInfo) +} + +extension KeyboardHandlerDelegate { + /// This function informs about changed input view without changing keyboard state + /// - parameter input: Active input component - `UITextField` or `UITextView` + /// - parameter info: Keyboard appearance and size information + func keyboardActiveInputViewChanged(input: UIView?, info: KeyboardInfo) {} +} + +enum KeyboardState: String { // String for rawValue while testing + case opened, hidden, frameChanged +} + +struct KeyboardInfo { + let beginFrame: CGRect + let endFrame: CGRect + let duration: TimeInterval + let curve: UIViewAnimationCurve + + func animate(_ animations: @escaping () -> Void, completion: ((Bool) -> Void)? = nil) { + let curveOption: UIViewAnimationOptions + switch curve { + case .linear: + curveOption = .curveLinear + case .easeIn: + curveOption = .curveEaseIn + case .easeOut: + curveOption = .curveEaseOut + case .easeInOut: + curveOption = .curveEaseInOut + } + + UIView.animate(withDuration: duration, + delay: 0, + options: [curveOption], + animations: animations, + completion: completion) + } +} + +/// Receives system notifications about keyboard appearance +/// +/// How to use: +/// 1) Subclass your controller from **KeyboardHandlerDelegate** +/// 2) Set `keyboardDelegate` to self +/// +class KeyboardHandler { + + weak var delegate: KeyboardHandlerDelegate? + + private enum VisibilityState { + case hidden, visible, rotating + } + + private var currentState: VisibilityState = .hidden { + didSet { + switch currentState { + case .visible: + wasVisible = false + case .hidden: + wasVisible = true + case .rotating: + break + } + } + } + + init(delegate: KeyboardHandlerDelegate?) { + self.delegate = delegate + + [ + NSNotification.Name.UIKeyboardWillShow: #selector(willShown(notification:)), + NSNotification.Name.UIKeyboardWillHide: #selector(willHidden(notification:)), + NSNotification.Name.UIKeyboardWillChangeFrame: #selector(willChangeFrame(notification:)), + NSNotification.Name.UIApplicationWillChangeStatusBarOrientation: #selector(statusWillRotate(notification:)), + NSNotification.Name.UIApplicationDidChangeStatusBarOrientation: #selector(statusDidRotate(notification:)) + ].forEach { + NotificationCenter.default.addObserver(self, selector: $0.value, name: $0.key, object: nil) + } + } + + private var wasVisible = false // aka previousValue + @objc func statusWillRotate(notification: Notification) { + currentState = .rotating + } + + @objc func statusDidRotate(notification: Notification) { + currentState = wasVisible ? .hidden : .visible + } + + @objc func willShown(notification: Notification) { + guard let delegate = delegate else { + return + } + if prevInputView != nil && activeInputView != nil && currentState == .visible { + if prevInputView != activeInputView { + getInfo(from: notification) { info in + delegate.keyboardActiveInputViewChanged(input: activeInputView, info: info) + prevInputView = activeInputView + return + } + } + } + switch currentState { + case .visible: + return + case .rotating: + currentState = .visible + return + case .hidden: + break + } + currentState = .visible + getInfo(from: notification) { info in + delegate.keyboardStateChanged(input: activeInputView, state: .opened, info: info) + } + } + + @objc func willHidden(notification: Notification) { + if currentState == .rotating { + return + } + currentState = .hidden + prevInputView = nil + getInfo(from: notification) { info in + delegate?.keyboardStateChanged(input: activeInputView, state: .hidden, info: info) + } + } + + @objc func willChangeFrame(notification: Notification) { + getInfo(from: notification) { info in + if info.beginFrame.height != info.endFrame.height { + delegate?.keyboardStateChanged(input: activeInputView, state: .frameChanged, info: info) + } + } + } + + // ----- Private ----- + private func extractValues(from: [AnyHashable: Any]?) -> KeyboardInfo? { + if let beginFrame = from?[UIKeyboardFrameBeginUserInfoKey] as? CGRect, + let endFrame = from?[UIKeyboardFrameEndUserInfoKey] as? CGRect, + let duration = from?[UIKeyboardAnimationDurationUserInfoKey] as? TimeInterval, + let curveRaw = from?[UIKeyboardAnimationCurveUserInfoKey] as? Int, + let curve = UIViewAnimationCurve(rawValue: curveRaw) { + return KeyboardInfo(beginFrame: beginFrame, endFrame: endFrame, duration: duration, curve: curve) + } + return nil + } + + private func getInfo(from notification: Notification, completion: (KeyboardInfo) -> Void) { + if let info = extractValues(from: notification.userInfo) { + completion(info) + } + } +} diff --git a/AR-Tester/StartSceneViewController.swift b/AR-Tester/StartSceneViewController.swift index a046020..87c41f3 100644 --- a/AR-Tester/StartSceneViewController.swift +++ b/AR-Tester/StartSceneViewController.swift @@ -9,14 +9,22 @@ import UIKit class StartSceneViewController: UIViewController { - @IBOutlet weak var activeField: UITextField! - @IBOutlet weak var scrollView: UIScrollView! + @IBOutlet var activeField: UITextField! + @IBOutlet var scrollView: UIScrollView! { + didSet { + scrollView.configure(with: .defaultConfiguration) + } + } + + @IBOutlet var bottomConstraint: NSLayoutConstraint! - override func viewDidLoad() { + @IBAction func tapGestureRecognizer(_ sender: UITapGestureRecognizer) { + view.endEditing(true) + } + override func viewDidLoad() { super.viewDidLoad() - self.activeField.delegate = self - // Prepare view scroll scheme for corrent keyboard opening - registerForKeyboardNotifications() + activeField.delegate = self + keyboardDelegate = self } override func viewDidLayoutSubviews() { @@ -33,22 +41,7 @@ class StartSceneViewController: UIViewController { activeField.layer.masksToBounds = true } - override func didReceiveMemoryWarning() { - super.didReceiveMemoryWarning() - // Dispose of any resources that can be recreated. - } - - override func viewDidDisappear(_ animated: Bool) { - deregisterFromKeyboardNotifications() - } - - // MARK: - Navigation - - // In a storyboard-based application, you will often want to do a little preparation before navigation override func prepare(for segue: UIStoryboardSegue, sender: Any?) { - // Get the new view controller using segue.destinationViewController. - // Pass the selected object to the new view controller. - if segue.identifier == "goCameraSegue" { if let cameraController = segue.destination as? CameraController { cameraController.enteredURL = activeField.text! @@ -62,67 +55,19 @@ class StartSceneViewController: UIViewController { } } -extension StartSceneViewController: UITextFieldDelegate { - func registerForKeyboardNotifications() { - //Adding notifies on keyboard appearing - NotificationCenter.default.addObserver(self, - selector: #selector(keyboardWasShown(notification:)), - name: NSNotification.Name.UIKeyboardWillShow, - object: nil) - NotificationCenter.default.addObserver(self, - selector: #selector(keyboardWillBeHidden(notification:)), - name: NSNotification.Name.UIKeyboardWillHide, - object: nil) - } - - func deregisterFromKeyboardNotifications() { - //Removing notifies on keyboard appearing - NotificationCenter.default.removeObserver(self, name: NSNotification.Name.UIKeyboardWillShow, object: nil) - NotificationCenter.default.removeObserver(self, name: NSNotification.Name.UIKeyboardWillHide, object: nil) - } - - @objc func keyboardWasShown(notification: NSNotification) { - //Need to calculate keyboard exact size due to Apple suggestions - - self.scrollView.isScrollEnabled = true - var info = notification.userInfo! - let keyboardSize = (info[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue.size - let contentInsets = UIEdgeInsets(top: 0.0, - left: 0.0, - bottom: keyboardSize!.height + 20, - right: 0.0) - - self.scrollView.contentInset = contentInsets - self.scrollView.scrollIndicatorInsets = contentInsets - - var aRect: CGRect = self.view.frame - aRect.size.height -= keyboardSize!.height - if let activeField = self.activeField { - if (!aRect.contains(activeField.convert(activeField.frame.origin, to: self.view))) { - self.scrollView.scrollRectToVisible(activeField.frame, animated: true) - } +extension StartSceneViewController: KeyboardHandlerDelegate { + func keyboardStateChanged(input: UIView?, state: KeyboardState, info: KeyboardInfo) { + var indicatorInsets = scrollView.scrollIndicatorInsets + + switch state { + case .frameChanged, .opened: + let scrollViewBottomInset = info.endFrame.height + 15 + scrollView.contentInset.bottom = scrollViewBottomInset + indicatorInsets.bottom = info.endFrame.height + case .hidden: + let point = CGPoint(x: 0, y: 0) + scrollView.setContentOffset(point, animated: true) + indicatorInsets.bottom = 0 } } - - @objc func keyboardWillBeHidden(notification: NSNotification) { - //Once keyboard disappears, restore original positions - var info = notification.userInfo! - let keyboardSize = (info[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue.size - let contentInsets = UIEdgeInsets(top: 0.0, - left: 0.0, - bottom: -keyboardSize!.height, - right: 0.0) - self.scrollView.contentInset = contentInsets - self.scrollView.scrollIndicatorInsets = contentInsets - self.view.endEditing(true) - self.scrollView.isScrollEnabled = false - } - - func textFieldDidBeginEditing(_ textField: UITextField) { - activeField = textField - } - - func textFieldDidEndEditing(_ textField: UITextField) { - // activeField = nil - } } diff --git a/AR-Tester/UIScrollView+Configure.swift b/AR-Tester/UIScrollView+Configure.swift new file mode 100644 index 0000000..0c9cf61 --- /dev/null +++ b/AR-Tester/UIScrollView+Configure.swift @@ -0,0 +1,71 @@ +// +// UIScrollView+Configure.swift +// AR-Tester +// +// Created by Egor Petrov on 30/11/2017. +// Copyright © 2017 Peter Savchenko. All rights reserved. +// + +import UIKit + +private var associationKey = "scrollView_bottom_inset" + +struct ScrollViewConfiguration { + var topInset: CGFloat? + var bottomInset: CGFloat? + var bottomIndicatorInset: CGFloat? + + init(topInset: CGFloat? = nil, + bottomInset: CGFloat? = nil, + bottomIndicatorInset: CGFloat? = nil) { + self.topInset = topInset + self.bottomInset = bottomInset + self.bottomIndicatorInset = bottomIndicatorInset + } + + static var `default` = defaultConfiguration +} + +private var defaultConfiguration: ScrollViewConfiguration { + return ScrollViewConfiguration(topInset: 0, + bottomInset: 0) +} + +extension UIScrollView { + private(set) var defaultBottomInset: CGFloat { + get { + return (objc_getAssociatedObject(self, &associationKey) as? CGFloat) ?? defaultConfiguration.bottomInset! + } + set { + objc_setAssociatedObject(self, &associationKey, newValue, .OBJC_ASSOCIATION_RETAIN) + } + } + + enum ConfigurationType { + case defaultConfiguration + case custom(ScrollViewConfiguration) + } + + func configure(with configuration: ConfigurationType) { + switch configuration { + case .defaultConfiguration: + setup(configuration: defaultConfiguration) + case let .custom(customConfig): + setup(configuration: customConfig) + } + } + + private func setup(configuration: ScrollViewConfiguration) { + if let topInset = configuration.topInset { + self.contentInset.top = topInset + } + if let bottomInset = configuration.bottomInset { + self.contentInset.bottom = bottomInset + defaultBottomInset = bottomInset + } + if let bottomIndicatorInset = configuration.bottomIndicatorInset { + self.scrollIndicatorInsets.bottom = bottomIndicatorInset + } + } +} + diff --git a/AR-Tester/UIViewController+KeyboardHandler.swift b/AR-Tester/UIViewController+KeyboardHandler.swift new file mode 100644 index 0000000..3df8f8f --- /dev/null +++ b/AR-Tester/UIViewController+KeyboardHandler.swift @@ -0,0 +1,75 @@ +// +// UIViewController+KeyboardHandler.swift +// AR-Tester +// +// Created by Egor Petrov on 30/11/2017. +// Copyright © 2017 Peter Savchenko. All rights reserved. +// + +import UIKit +import ObjectiveC + +private var associationKey = "private_storedkbDelegate" + +// MARK: - keyboardDelegate +extension UIViewController { + weak var keyboardDelegate: KeyboardHandlerDelegate? { + get { + if let handler = objc_getAssociatedObject(self, &associationKey) as? KeyboardHandler { + return handler.delegate + } + return nil + } + set { + let handler = KeyboardHandler(delegate: newValue) + objc_setAssociatedObject(self, &associationKey, handler, .OBJC_ASSOCIATION_RETAIN) + } + } +} + +// MARK: - UITextViewDelegate, UITextFieldDelegate + +/* + Adds close toolBar for UITextField and UITextView + Do not forget to call `super` if overriding and set `delegate` of target edit fields from code or via Storyboard + + Overriding example: + + override func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool { + // doing whatever you want + return super.textFieldShouldBeginEditing(textField) + } + */ +extension UIViewController: UITextViewDelegate, UITextFieldDelegate { + public func textViewShouldBeginEditing(_ textView: UITextView) -> Bool { + return true + } + + public func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool { + return true + } + + // ----- Staff only ----- + private var doneToolBar: UIToolbar { + let barFrame = CGRect(x: 0, y: 0, width: 0, height: 40) + let bar = UIToolbar(frame: barFrame) + let btnDone = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(_endEditing)) + let flex = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil) + bar.items = [flex, btnDone] + return bar + } + + private func changeWith(view: UIView) { + if prevInputView == nil { + prevInputView = view + activeInputView = view + } else { + prevInputView = activeInputView + activeInputView = view + } + } + + @objc private func _endEditing() { + view.endEditing(true) + } +}