October 5, 2019 4483

From this iOS tutorial, you will learn:

Different features integrated into iPadOS

What multiple windows on the iPad is

The types of windows

The benefits of using this feature

What types of apps can benefit from this feature

Steps to integrate multiple windows feature in iPadOS

iOS 13 was launched with a lot of new features and functionalities. These advancements were not only for iPhoneOS but also for iPadOS. In this blog, we will talk about one of the most important features that recently launched- multiple windows. Herein we will talk about how to integrate multiple windows feature in iPadOS.

With the release of iOS13, iPad has come nearer to functioning as the main computer. A lot of new features released in iOS13 have made iPad Pro a great MacBook replacement. Let us take a look at the new features that have accomplished this feat.

Desktop Class Safari Multiple App Instances (Windows) At The Same Time Safari Download Manager External Devices Support in Files Local Storage Management in Files Better Text Manipulation Automation with Shortcuts Mouse Support Better Home Screen Dark Mode

The main feature that has led to this advancement is using multiple instances (or windows) for single or multiple apps at the same time.

What are the Multiple Windows on the iPad?

Previously, there was a feature that lets the users have multiple tabs of different apps on their iPad but multiple windows for the same or different applications were introduced in iOS 13.

This feature enables your app to run two instances of your interface side-by-side. In simple words, if it is a document-based app, people could have multiple document windows open at the same time. In simpler words, your users will love it.

Bonus: Multiple windows are created easily by using simple features of drag and drop.

What are the Types of Windows?

Primary window: It contains multiple app objects and the actions associated with them. People tend to interact with a primary window over time. Auxiliary window: It contains a single object and the actions associated with it. People tend to interact with an auxiliary window only once before closing it.

What are the Benefits of Multiple Windows?

Multiple windows show different areas of the content. For instance, people might have one primary Mail window to display their Inbox and another to show their Drafts mailbox.

Auxiliary windows also give users additional views into the app’s content and functionality.

Users are enabled to act in one window and refer to something in the other window.

Which types of apps utilize this feature?

Most apps can utilize this feature in some way or another. Yet, it should be made sure that this feature is not mandatory for the functioning of your app. This feature is only to improve the multitasking feature of iPadOS and enhance user-experience.

Some examples of apps that utilize this feature in an appropriate manner.

Document-based apps

Navigation based apps (Maps)

Web browsing apps (Safari)

Dates/ Event management apps (Calendar)

Let us now see the steps of integrating multiple windows feature in iPadOS

Steps to Integrate Multiple Windows Feature in iPadOS

1. Create a new project in Xcode

2. Create a Single View Application

3. Enter the project name (For instance, SOChatDemo)

4. Create a “MainSplitViewController” class of UISplitViewController

5. Add and SplitViewController in Main.storyboard and assign MainSplitViewController class to it.

6. Drag and drop “Common”, “Model”, and “View” folder in the app (from demo app) because it is required for chat data (here, we are showing offline chat data)

7. Add ChatListViewController class to show the Chat User list.

Get a UIViewController in main.storyboard and assign ChatListViewController class to it.

8 Take IBOutlet of UITableview and declare an array of User’s Chat list from Model => MessageModel class (UserModel)

class ChatListViewController: UIViewController { //MARK:- Variables var arrUserList : [UsersModel] = [] //MARK:- IBOutlets @IBOutlet weak var tblUsers: UITableView! //MARK:- UIView Life Cycle override func viewDidLoad() { super.viewDidLoad() arrUserList = generateRandomUsers() tblUsers.estimatedRowHeight = 76 tblUsers.rowHeight = UITableView.automaticDimension tblUsers.reloadData() tblUsers.dragDelegate = self if arrUserList.count > 0 { DispatchQueue.main.asyncAfter(deadline: .now()+0.2) { self.tblUsers.selectRow(at: IndexPath(row: 0, section: 0), animated: false, scrollPosition: .top) self.setDataInDetailVC(model: self.arrUserList[0]) } } } }

9. In order to split the iPad screen for viewing two apps side by side, follow this code.

//MARK:- UITableViewDragDelegate Delegate extension ChatListViewController: UITableViewDragDelegate { func tableView(_ tableView: UITableView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] { let selectedMessage = arrUserList[indexPath.row] let userActivity = selectedMessage.openDetailUserActivity let itemProvider = NSItemProvider(object: UIImage(named: selectedMessage.displayImage)!) itemProvider.registerObject(userActivity, visibility: .all) let dragItem = UIDragItem(itemProvider: itemProvider) dragItem.localObject = selectedMessage return [dragItem] } } //MARK:- UITableViewDelegate Delegate and UITableViewDataSource extension ChatListViewController : UITableViewDelegate, UITableViewDataSource { func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return arrUserList.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { if let cell : ChatUsersTableViewCell = tableView.dequeueReusableCell(withIdentifier: "ChatUsersTableViewCell") as? ChatUsersTableViewCell { cell.cellConfig(user: arrUserList[indexPath.row]) return cell }else { return UITableViewCell() } } func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { self.setDataInDetailVC(model: arrUserList[indexPath.row]) } fileprivate func setDataInDetailVC(model:UsersModel) { if let vc : MainSplitViewController = self.navigationController?.parent as? MainSplitViewController { for childern in vc.children { if let navVC = childern as? UINavigationController,let childVC = navVC.viewControllers.first as? ChatViewController { childVC.userModel = model childVC.refreshData() } } } } }

10. Add ChatViewController class to show Chat Detail.

Get a UIViewController in main.storyboard and assign ChatViewController class to it.

To show the detail of chat we will fetch dummy messages from MessageModel and display in the list.

class ChatViewController: UIViewController { //MARK:- Variables var userModel : UsersModel! var isValidFromOtherWindow : Bool = false private var arrMessage : [Messages] = [] private var currentUser : UsersModel = UsersModel(senderID: String(0), displayName: generateRandomName(), profession: generateProfessionName(), displayImage: "22") private var activeTextField : UITextView? = nil //MARK:- IBOutlets @IBOutlet weak var lblShadowMessage: UILabel! @IBOutlet weak var txtMessage: UITextView! @IBOutlet weak var tblMessages: UITableView! @IBOutlet weak var btnSend: UIButton! @IBOutlet weak var scrollView: UIScrollView! @IBOutlet weak var containerView: UIView! //MARK:- UIView Life Cycle override func viewDidLoad() { super.viewDidLoad() setUI() if (isValidFromOtherWindow) { self.navigationItem.hidesBackButton = true self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: "Done", style: .plain, target: self, action: #selector(dismissViewController)) } NotificationCenter.default.addObserver( self, selector: #selector(self.insertMessages(_:)), name: .messageAddedNotifiation, object: nil) } override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) if (isValidFromOtherWindow) { self.refreshData() } self.registerKeyboardNotifications() guard let user = userModel else { return } if #available(iOS 13.0, *) { view.window?.windowScene?.userActivity = user.openDetailUserActivity } } override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) self.deRegisterKeyboardNotifications() if #available(iOS 13.0, *) { view.window?.windowScene?.userActivity = nil } } //MARK:- Set UI and Chat Configration func refreshData() { arrMessage = generateRandomMessages(currentUser: currentUser, otherUser: userModel) guard self.tblMessages != nil else { return } self.tblMessages.reloadData() self.tblMessages.scrollToBottom() } fileprivate func setUI() { txtMessage.layer.cornerRadius = 4 if (!isValidFromOtherWindow) { txtMessage.addDoneButtonOnKeyboard() } tblMessages.register(UINib(nibName: "IncommingChatMessageTableViewCell", bundle: nil), forCellReuseIdentifier: "IncommingChatMessageTableViewCell") tblMessages.register(UINib(nibName: "OutgoingChatMessageTableViewCell", bundle: nil), forCellReuseIdentifier: "OutgoingChatMessageTableViewCell") tblMessages.estimatedRowHeight = 70 tblMessages.rowHeight = UITableView.automaticDimension tblMessages.reloadData() }

11. This method is responsible for creating window scenes and creating multiple windows.

@objc fileprivate func dismissViewController() { if #available(iOS 13.0, *) { var currentSession : UISceneSession? = nil for session in UIApplication.shared.openSessions { if let scene = session.scene,let currentScene = view.window?.windowScene,scene == currentScene { currentSession = session } } guard let session = currentSession else { return } UIApplication.shared.requestSceneSessionDestruction(session, options: nil) { (error) in } } } fileprivate func setUpdateLayout() { self.view.updateConstraints() self.view.layoutIfNeeded() self.view.setNeedsLayout() } //MARK:- IBAction @IBAction fileprivate func btnSendAction(_ sender: UIButton) { NotificationCenter.default.post(name: .messageAddedNotifiation, object: self.userModel, userInfo: ["data":[txtMessage.text ?? ""]]) txtMessage.text = "" lblShadowMessage.text = "" txtMessage.resignFirstResponder() } } //MARK:- UITableViewDelegate and UITableViewDataSource extension ChatViewController : UITableViewDelegate,UITableViewDataSource { func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return arrMessage.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let message = arrMessage[indexPath.row] if message.sender.senderId == self.currentUser.senderId { if let cell = tableView.dequeueReusableCell(withIdentifier: "OutgoingChatMessageTableViewCell") as? OutgoingChatMessageTableViewCell { cell.cellConfig(model: message) return cell } }else { if let cell = tableView.dequeueReusableCell(withIdentifier: "IncommingChatMessageTableViewCell") as? IncommingChatMessageTableViewCell { cell.cellConfig(model: message) return cell } } return UITableViewCell() } } //MARK:- UITextViewDelegate extension ChatViewController : UITextViewDelegate { func textViewShouldBeginEditing(_ textView: UITextView) -> Bool { activeTextField = textView return true } func textViewDidBeginEditing(_ textView: UITextView) { } func textViewDidEndEditing(_ textView: UITextView) { activeTextField = nil } func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool { let currentText = textView.text ?? "" guard let stringRange = Range(range, in: currentText) else { return false } let updatedText = currentText.replacingCharacters(in: stringRange, with: text) let previousHeight = lblShadowMessage.frame.height lblShadowMessage.text = updatedText self.tblMessages.layoutIfNeeded() self.setUpdateLayout() let newHeight = lblShadowMessage.frame.height - previousHeight tblMessages.contentOffset = CGPoint(x: 0, y: tblMessages.contentOffset.y + newHeight) self.tblMessages.layoutIfNeeded() return true } } //MARK:- InputBarAccessoryViewDelegate extension ChatViewController { @objc fileprivate func insertMessages(_ notification:NSNotification) { guard let user = notification.object as? UsersModel else { return } guard let userModel = self.userModel else { return } if userModel.senderId != user.senderId { return } guard let dictData = notification.userInfo as? [String:Any],let data = dictData["data"] as? [String] else { return } if arrMessage.count == 0 { return } for component in data { if component.trimmingCharacters(in: .whitespacesAndNewlines).count == 0 { continue; }else{ let message = Messages(sender: currentUser, messageId: String(Int(arrMessage[arrMessage.count-1].messageId) ?? 0 + 1), sentDate: Date(), message: component.trimmingCharacters(in: .whitespacesAndNewlines)) arrMessage.append(message) } } self.tblMessages.reloadData() self.tblMessages.setNeedsDisplay() self.tblMessages.scrollToBottom() } } //MARK: - Keyboard Notification observer Methods extension ChatViewController { fileprivate func registerKeyboardNotifications() { NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardWillShow(notification:)), name: UIResponder.keyboardWillShowNotification, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardWillHide(notification:)), name: UIResponder.keyboardWillHideNotification, object: nil) } fileprivate func deRegisterKeyboardNotifications() { NotificationCenter.default.removeObserver(self, name: UIResponder.keyboardWillShowNotification, object: nil) NotificationCenter.default.removeObserver(self, name: UIResponder.keyboardDidHideNotification, object: nil) } @objc fileprivate func keyboardWillShow(notification: NSNotification) { if let activeTextField = activeTextField { // this method will get called even if a system generated alert with keyboard appears over the current VC. let info: NSDictionary = notification.userInfo! as NSDictionary let value: NSValue = info.value(forKey: UIResponder.keyboardFrameEndUserInfoKey) as! NSValue let keyboardSize: CGSize = value.cgRectValue.size let contentInsets: UIEdgeInsets = UIEdgeInsets(top: 0.0, left: 0.0, bottom: keyboardSize.height, right: 0.0) scrollView.contentInset = contentInsets scrollView.scrollIndicatorInsets = contentInsets var aRect: CGRect = self.view.frame aRect.size.height -= keyboardSize.height let activeTextFieldRect: CGRect? = activeTextField.convert(activeTextField.frame, to: self.containerView)//activeTextField.frame let activeTextFieldOrigin: CGPoint? = activeTextFieldRect?.origin if (!aRect.contains(activeTextFieldOrigin!)) { scrollView.scrollRectToVisible(activeTextFieldRect!, animated:true) } } } @objc fileprivate func keyboardWillHide(notification: NSNotification) { let contentInsets: UIEdgeInsets = .zero scrollView.contentInset = contentInsets scrollView.scrollIndicatorInsets = contentInsets } }

Concluding Remarks

Apple might have introduced this feature quite late but you should not wait for anything now. If your app is the type that requires integrating multiple windows feature, then go for it. We hope that you found this iPhone tutorial useful in clearing your concept about this new feature.

In case, if you have any suggestions or queries in this tutorial or any questions related to iPhone app development, we are all ears. You may also feel free to tell us what we have missed in this or if you feel something is unclear.

We are a leading iPhone app development company and have hands-on experience in developing iOS apps with top features and functionalities. If you wish to develop a performance-oriented app with advanced features like this or want to hire iPhone developers, contact us. You may schedule a 30-min free consultation with our iOS developers and expert. All you need to do is fill the contact us form in the footer.

You may also like:

This page was last edited on May 25th, 2020, at 6:07.