From 33c2ad5448677ce759ccd529f0fa079d06ea2413 Mon Sep 17 00:00:00 2001 From: Ashley Ng Date: Tue, 10 Mar 2020 22:38:49 -0500 Subject: [PATCH 1/2] Add support to show splitview for iPads --- Example/ChatExample.xcodeproj/project.pbxproj | 8 ++++ Example/Sources/AppDelegate.swift | 8 +++- .../BasicExampleViewController.swift | 2 +- .../View Controllers/ChatViewController.swift | 21 ++++++---- .../LaunchViewController.swift | 29 +++++++++---- ...essageSubviewContainerViewController.swift | 41 +++++++++++++++++++ .../MessageSubviewViewController.swift | 33 +++++++++++++++ 7 files changed, 125 insertions(+), 17 deletions(-) create mode 100644 Example/Sources/View Controllers/MessageSubviewContainerViewController.swift create mode 100644 Example/Sources/View Controllers/MessageSubviewViewController.swift diff --git a/Example/ChatExample.xcodeproj/project.pbxproj b/Example/ChatExample.xcodeproj/project.pbxproj index 89a85340..c83db8f5 100644 --- a/Example/ChatExample.xcodeproj/project.pbxproj +++ b/Example/ChatExample.xcodeproj/project.pbxproj @@ -29,6 +29,8 @@ 50739F9621C5090A008CA369 /* BasicAudioController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50739F9521C5090A008CA369 /* BasicAudioController.swift */; }; 5074EF4E2163555900D82952 /* sound2.m4a in Resources */ = {isa = PBXBuildFile; fileRef = 5074EF4C2163555900D82952 /* sound2.m4a */; }; 5074EF4F2163555900D82952 /* sound1.m4a in Resources */ = {isa = PBXBuildFile; fileRef = 5074EF4D2163555900D82952 /* sound1.m4a */; }; + 63ECDABE24182A470016C349 /* MessageSubviewViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 63ECDABD24182A470016C349 /* MessageSubviewViewController.swift */; }; + 63ECDAC2241889630016C349 /* MessageSubviewContainerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 63ECDAC1241889630016C349 /* MessageSubviewContainerViewController.swift */; }; 882B5E811CF7D53600B6E160 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 882B5E781CF7D53600B6E160 /* AppDelegate.swift */; }; 882B5E821CF7D53600B6E160 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 882B5E791CF7D53600B6E160 /* Assets.xcassets */; }; 882B5E831CF7D53600B6E160 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 882B5E7A1CF7D53600B6E160 /* LaunchScreen.storyboard */; }; @@ -96,6 +98,8 @@ 5074EF4C2163555900D82952 /* sound2.m4a */ = {isa = PBXFileReference; lastKnownFileType = file; path = sound2.m4a; sourceTree = ""; }; 5074EF4D2163555900D82952 /* sound1.m4a */ = {isa = PBXFileReference; lastKnownFileType = file; path = sound1.m4a; sourceTree = ""; }; 56F0AC85B38034EC92CCBC7D /* Pods_ChatExampleTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_ChatExampleTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 63ECDABD24182A470016C349 /* MessageSubviewViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageSubviewViewController.swift; sourceTree = ""; }; + 63ECDAC1241889630016C349 /* MessageSubviewContainerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageSubviewContainerViewController.swift; sourceTree = ""; }; 882B5E331CF7D4B900B6E160 /* ChatExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ChatExample.app; sourceTree = BUILT_PRODUCTS_DIR; }; 882B5E491CF7D4B900B6E160 /* ChatExampleTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ChatExampleTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 882B5E541CF7D4B900B6E160 /* ChatExampleUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ChatExampleUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -198,6 +202,8 @@ 385C293D211FF38F0010B4BA /* MessageContainerController.swift */, 385C293E211FF38F0010B4BA /* NavigationController.swift */, 385C293C211FF38E0010B4BA /* SettingsViewController.swift */, + 63ECDABD24182A470016C349 /* MessageSubviewViewController.swift */, + 63ECDAC1241889630016C349 /* MessageSubviewContainerViewController.swift */, ); path = "View Controllers"; sourceTree = ""; @@ -536,6 +542,7 @@ 385C2946211FF38F0010B4BA /* LaunchViewController.swift in Sources */, 385C2939211FF37B0010B4BA /* Lorem.swift in Sources */, 385C2928211FF33B0010B4BA /* MockMessage.swift in Sources */, + 63ECDABE24182A470016C349 /* MessageSubviewViewController.swift in Sources */, 385C2923211FF32E0010B4BA /* TableViewCells.swift in Sources */, 385C2942211FF38F0010B4BA /* AdvancedExampleViewController.swift in Sources */, 50739F9621C5090A008CA369 /* BasicAudioController.swift in Sources */, @@ -553,6 +560,7 @@ 882B5E811CF7D53600B6E160 /* AppDelegate.swift in Sources */, 385C292D211FF3520010B4BA /* CustomMessageFlowLayout.swift in Sources */, 38CCCC592258419300DD5482 /* AutocompleteExampleViewController.swift in Sources */, + 63ECDAC2241889630016C349 /* MessageSubviewContainerViewController.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Example/Sources/AppDelegate.swift b/Example/Sources/AppDelegate.swift index d5efe9f4..f98806e1 100644 --- a/Example/Sources/AppDelegate.swift +++ b/Example/Sources/AppDelegate.swift @@ -30,9 +30,15 @@ final internal class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + + let masterVC = NavigationController(rootViewController: LaunchViewController()) + let detailVC = NavigationController() + let splitVC = UISplitViewController() + splitVC.viewControllers = [masterVC, detailVC] + splitVC.preferredDisplayMode = .allVisible window = UIWindow(frame: UIScreen.main.bounds) - window?.rootViewController = NavigationController(rootViewController: LaunchViewController()) + window?.rootViewController = splitVC window?.makeKeyAndVisible() if UserDefaults.isFirstLaunch() { diff --git a/Example/Sources/View Controllers/BasicExampleViewController.swift b/Example/Sources/View Controllers/BasicExampleViewController.swift index 98e81d55..74a25ecc 100644 --- a/Example/Sources/View Controllers/BasicExampleViewController.swift +++ b/Example/Sources/View Controllers/BasicExampleViewController.swift @@ -26,7 +26,7 @@ import UIKit import MapKit import MessageKit -final class BasicExampleViewController: ChatViewController { +class BasicExampleViewController: ChatViewController { override func configureMessageCollectionView() { super.configureMessageCollectionView() messagesCollectionView.messagesLayoutDelegate = self diff --git a/Example/Sources/View Controllers/ChatViewController.swift b/Example/Sources/View Controllers/ChatViewController.swift index 71c853ed..f58d8285 100644 --- a/Example/Sources/View Controllers/ChatViewController.swift +++ b/Example/Sources/View Controllers/ChatViewController.swift @@ -300,10 +300,14 @@ extension ChatViewController: MessageLabelDelegate { extension ChatViewController: InputBarAccessoryViewDelegate { + @objc func inputBar(_ inputBar: InputBarAccessoryView, didPressSendButtonWith text: String) { + processInputBar(messageInputBar) + } + func processInputBar(_ inputBar: InputBarAccessoryView) { // Here we can parse for which substrings were autocompleted - let attributedText = messageInputBar.inputTextView.attributedText! + let attributedText = inputBar.inputTextView.attributedText! let range = NSRange(location: 0, length: attributedText.length) attributedText.enumerateAttribute(.autocompleted, in: range, options: []) { (_, range, _) in @@ -313,18 +317,19 @@ extension ChatViewController: InputBarAccessoryViewDelegate { } let components = inputBar.inputTextView.components - messageInputBar.inputTextView.text = String() - messageInputBar.invalidatePlugins() - + inputBar.inputTextView.text = String() + inputBar.invalidatePlugins() // Send button activity animation - messageInputBar.sendButton.startAnimating() - messageInputBar.inputTextView.placeholder = "Sending..." + inputBar.sendButton.startAnimating() + inputBar.inputTextView.placeholder = "Sending..." + // Resign first responder for iPad split view + inputBar.inputTextView.resignFirstResponder() DispatchQueue.global(qos: .default).async { // fake send request task sleep(1) DispatchQueue.main.async { [weak self] in - self?.messageInputBar.sendButton.stopAnimating() - self?.messageInputBar.inputTextView.placeholder = "Aa" + inputBar.sendButton.stopAnimating() + inputBar.inputTextView.placeholder = "Aa" self?.insertMessages(components) self?.messagesCollectionView.scrollToBottom(animated: true) } diff --git a/Example/Sources/View Controllers/LaunchViewController.swift b/Example/Sources/View Controllers/LaunchViewController.swift index 50794115..d0a02d09 100644 --- a/Example/Sources/View Controllers/LaunchViewController.swift +++ b/Example/Sources/View Controllers/LaunchViewController.swift @@ -32,7 +32,7 @@ final internal class LaunchViewController: UITableViewController { return .lightContent } - let cells = ["Basic Example", "Advanced Example", "Autocomplete Example", "Embedded Example", "Settings", "Source Code", "Contributors"] + let cells = ["Basic Example", "Advanced Example", "Autocomplete Example", "Embedded Example", "Subview Example", "Settings", "Source Code", "Contributors"] // MARK: - View Life Cycle @@ -73,19 +73,34 @@ final internal class LaunchViewController: UITableViewController { // MARK: - UITableViewDelegate + // swiftlint:disable cyclomatic_complexity override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { let cell = cells[indexPath.row] switch cell { case "Basic Example": - navigationController?.pushViewController(BasicExampleViewController(), animated: true) + let vc = BasicExampleViewController() + let detailVC = NavigationController(rootViewController: vc) + splitViewController?.showDetailViewController(detailVC, sender: self) case "Advanced Example": - navigationController?.pushViewController(AdvancedExampleViewController(), animated: true) + let vc = AdvancedExampleViewController() + let detailVC = NavigationController(rootViewController: vc) + splitViewController?.showDetailViewController(detailVC, sender: self) case "Autocomplete Example": - navigationController?.pushViewController(AutocompleteExampleViewController(), animated: true) + let vc = AutocompleteExampleViewController() + let detailVC = NavigationController(rootViewController: vc) + splitViewController?.showDetailViewController(detailVC, sender: self) case "Embedded Example": - navigationController?.pushViewController(MessageContainerController(), animated: true) + let vc = MessageContainerController() + let detailVC = NavigationController(rootViewController: vc) + splitViewController?.showDetailViewController(detailVC, sender: self) case "Settings": - navigationController?.pushViewController(SettingsViewController(), animated: true) + let vc = SettingsViewController() + let detailVC = NavigationController(rootViewController: vc) + splitViewController?.showDetailViewController(detailVC, sender: self) + case "Subview Example": + let vc = MessageSubviewContainerViewController() + let detailVC = NavigationController(rootViewController: vc) + splitViewController?.showDetailViewController(detailVC, sender: self) case "Source Code": guard let url = URL(string: "https://github.com/MessageKit/MessageKit") else { return } openURL(url) @@ -103,6 +118,6 @@ final internal class LaunchViewController: UITableViewController { if #available(iOS 10.0, *) { webViewController.preferredControlTintColor = .primaryColor } - present(webViewController, animated: true, completion: nil) + splitViewController?.showDetailViewController(webViewController, sender: self) } } diff --git a/Example/Sources/View Controllers/MessageSubviewContainerViewController.swift b/Example/Sources/View Controllers/MessageSubviewContainerViewController.swift new file mode 100644 index 00000000..c1c2bb1d --- /dev/null +++ b/Example/Sources/View Controllers/MessageSubviewContainerViewController.swift @@ -0,0 +1,41 @@ +// +// MessageSubviewContainerViewController.swift +// ChatExample +// +// Created by Ng, Ashley on 3/10/20. +// Copyright © 2020 MessageKit. All rights reserved. +// + +import Foundation +import UIKit + +final class MessageSubviewContainerViewController: UIViewController { + + override var preferredStatusBarStyle: UIStatusBarStyle { + return .lightContent + } + + let messageSubviewViewController = MessageSubviewViewController() + + override func viewDidLoad() { + super.viewDidLoad() + + messageSubviewViewController.willMove(toParent: self) + addChild(messageSubviewViewController) + view.addSubview(messageSubviewViewController.view) + messageSubviewViewController.didMove(toParent: self) + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + navigationController?.navigationBar.isTranslucent = true + navigationController?.navigationBar.barTintColor = .clear + } + + override func viewWillDisappear(_ animated: Bool) { + super.viewWillDisappear(animated) + navigationController?.navigationBar.isTranslucent = false + navigationController?.navigationBar.barTintColor = .primaryColor + } + +} diff --git a/Example/Sources/View Controllers/MessageSubviewViewController.swift b/Example/Sources/View Controllers/MessageSubviewViewController.swift new file mode 100644 index 00000000..ae220dd1 --- /dev/null +++ b/Example/Sources/View Controllers/MessageSubviewViewController.swift @@ -0,0 +1,33 @@ +// +// MessageSubviewViewController.swift +// ChatExample +// +// Created by Ng, Ashley on 3/10/20. +// Copyright © 2020 MessageKit. All rights reserved. +// + +import Foundation +import InputBarAccessoryView + +class MessageSubviewViewController: BasicExampleViewController { + + private var keyboardManager = KeyboardManager() + + private let subviewInputBar = InputBarAccessoryView() + + override func viewDidLoad() { + super.viewDidLoad() + subviewInputBar.delegate = self + additionalBottomInset = 88 + } + + override func didMove(toParent parent: UIViewController?) { + super.didMove(toParent: parent) + parent?.view.addSubview(subviewInputBar) + keyboardManager.bind(inputAccessoryView: subviewInputBar) + } + + override func inputBar(_ inputBar: InputBarAccessoryView, didPressSendButtonWith text: String) { + processInputBar(subviewInputBar) + } +} From d2a34894a5008e85cca1cab5367fa04f19bcc39d Mon Sep 17 00:00:00 2001 From: Ashley Ng Date: Thu, 7 May 2020 18:29:23 -0500 Subject: [PATCH 2/2] Address code review - change varibale names with VC to ViewController - add MIT License Header to new files - add documentation about using a height constant --- Example/Sources/AppDelegate.swift | 12 +++---- .../LaunchViewController.swift | 36 +++++++++---------- ...essageSubviewContainerViewController.swift | 30 ++++++++++++---- .../MessageSubviewViewController.swift | 33 ++++++++++++----- 4 files changed, 72 insertions(+), 39 deletions(-) diff --git a/Example/Sources/AppDelegate.swift b/Example/Sources/AppDelegate.swift index f98806e1..5895d4ca 100644 --- a/Example/Sources/AppDelegate.swift +++ b/Example/Sources/AppDelegate.swift @@ -31,14 +31,14 @@ final internal class AppDelegate: UIResponder, UIApplicationDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { - let masterVC = NavigationController(rootViewController: LaunchViewController()) - let detailVC = NavigationController() - let splitVC = UISplitViewController() - splitVC.viewControllers = [masterVC, detailVC] - splitVC.preferredDisplayMode = .allVisible + let masterViewController = NavigationController(rootViewController: LaunchViewController()) + let detailViewController = NavigationController() + let splitViewController = UISplitViewController() + splitViewController.viewControllers = [masterViewController, detailViewController] + splitViewController.preferredDisplayMode = .allVisible window = UIWindow(frame: UIScreen.main.bounds) - window?.rootViewController = splitVC + window?.rootViewController = splitViewController window?.makeKeyAndVisible() if UserDefaults.isFirstLaunch() { diff --git a/Example/Sources/View Controllers/LaunchViewController.swift b/Example/Sources/View Controllers/LaunchViewController.swift index a04706cf..fa0f46a0 100644 --- a/Example/Sources/View Controllers/LaunchViewController.swift +++ b/Example/Sources/View Controllers/LaunchViewController.swift @@ -74,29 +74,29 @@ final internal class LaunchViewController: UITableViewController { let cell = cells[indexPath.row] switch cell { case "Basic Example": - let vc = BasicExampleViewController() - let detailVC = NavigationController(rootViewController: vc) - splitViewController?.showDetailViewController(detailVC, sender: self) + let viewController = BasicExampleViewController() + let detailViewController = NavigationController(rootViewController: viewController) + splitViewController?.showDetailViewController(detailViewController, sender: self) case "Advanced Example": - let vc = AdvancedExampleViewController() - let detailVC = NavigationController(rootViewController: vc) - splitViewController?.showDetailViewController(detailVC, sender: self) + let viewController = AdvancedExampleViewController() + let detailViewController = NavigationController(rootViewController: viewController) + splitViewController?.showDetailViewController(detailViewController, sender: self) case "Autocomplete Example": - let vc = AutocompleteExampleViewController() - let detailVC = NavigationController(rootViewController: vc) - splitViewController?.showDetailViewController(detailVC, sender: self) + let viewController = AutocompleteExampleViewController() + let detailViewController = NavigationController(rootViewController: viewController) + splitViewController?.showDetailViewController(detailViewController, sender: self) case "Embedded Example": - let vc = MessageContainerController() - let detailVC = NavigationController(rootViewController: vc) - splitViewController?.showDetailViewController(detailVC, sender: self) + let viewController = MessageContainerController() + let detailViewController = NavigationController(rootViewController: viewController) + splitViewController?.showDetailViewController(detailViewController, sender: self) case "Settings": - let vc = SettingsViewController() - let detailVC = NavigationController(rootViewController: vc) - splitViewController?.showDetailViewController(detailVC, sender: self) + let viewController = SettingsViewController() + let detailViewController = NavigationController(rootViewController: viewController) + splitViewController?.showDetailViewController(detailViewController, sender: self) case "Subview Example": - let vc = MessageSubviewContainerViewController() - let detailVC = NavigationController(rootViewController: vc) - splitViewController?.showDetailViewController(detailVC, sender: self) + let viewController = MessageSubviewContainerViewController() + let detailViewController = NavigationController(rootViewController: viewController) + splitViewController?.showDetailViewController(detailViewController, sender: self) case "Source Code": guard let url = URL(string: "https://github.com/MessageKit/MessageKit") else { return } openURL(url) diff --git a/Example/Sources/View Controllers/MessageSubviewContainerViewController.swift b/Example/Sources/View Controllers/MessageSubviewContainerViewController.swift index c1c2bb1d..4ddbb373 100644 --- a/Example/Sources/View Controllers/MessageSubviewContainerViewController.swift +++ b/Example/Sources/View Controllers/MessageSubviewContainerViewController.swift @@ -1,10 +1,26 @@ -// -// MessageSubviewContainerViewController.swift -// ChatExample -// -// Created by Ng, Ashley on 3/10/20. -// Copyright © 2020 MessageKit. All rights reserved. -// +/* +MIT License + +Copyright (c) 2017-2019 MessageKit + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ import Foundation import UIKit diff --git a/Example/Sources/View Controllers/MessageSubviewViewController.swift b/Example/Sources/View Controllers/MessageSubviewViewController.swift index ae220dd1..47a75e0e 100644 --- a/Example/Sources/View Controllers/MessageSubviewViewController.swift +++ b/Example/Sources/View Controllers/MessageSubviewViewController.swift @@ -1,15 +1,31 @@ -// -// MessageSubviewViewController.swift -// ChatExample -// -// Created by Ng, Ashley on 3/10/20. -// Copyright © 2020 MessageKit. All rights reserved. -// +/* +MIT License + +Copyright (c) 2017-2019 MessageKit + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ import Foundation import InputBarAccessoryView -class MessageSubviewViewController: BasicExampleViewController { +final class MessageSubviewViewController: BasicExampleViewController { private var keyboardManager = KeyboardManager() @@ -18,6 +34,7 @@ class MessageSubviewViewController: BasicExampleViewController { override func viewDidLoad() { super.viewDidLoad() subviewInputBar.delegate = self + // Take into account the height of the bottom input bar additionalBottomInset = 88 }