{{announcement.body}}
{{announcement.title}}

How to Integrate Multiple Windows Feature (Introduced in iOS13) in iPadOS

DZone 's Guide to

How to Integrate Multiple Windows Feature (Introduced in iOS13) in iPadOS

In this article, we discuss how to create multiple windows in iPadOS with Swift in order to help users better manage their workflow in an application.

· Web Dev Zone ·
Free Resource

From this iOS tutorial, you will learn about:

  • 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.

  1. Desktop Class Safari.
  2. Multiple App Instances (Windows) At The Same Time.
  3. Safari Download Manager.
  4. External Devices Support in Files.
  5. Local Storage Management in Files.
  6. Better Text Manipulation.
  7. Automation with Shortcuts.
  8. Mouse Support.
  9. Better Home Screen.
  10. 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. 

You may also like:  Swift Essentials.

What Are Multiple Windows on the iPad?

Previously, there was a feature that let 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. 

Types of Windows

  1. Primary window: It contains multiple app objects and the actions associated with them. People tend to interact with a primary window over time.
  2. 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.

Benefits of Multiple Windows 

  • Multiple windows show different areas of 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).

Steps to Integrate Multiple Windows Feature in iPadOS

Step 1: Create a new project in Xcode

Creating a new project in XCode

Step 2: Create a Single View Application

Selecting single view application

Step 3: Enter the project name (For instance, SOChatDemo)

Adding project name

Step 4: Create a “MainSplitViewController” class of UISplitViewController

Creating MainSplitViewController class

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

Adding a SplitViewController in Main.storyboard

Assigning MainSplitViewController class

Step 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)

Adding Common, Model, and View folder

Step 7: Add ChatListViewController class to show the Chat User list. Get a UIViewController in main.storyboard and assign ChatListViewController class to it.

Adding a ChatListViewController class
Step  8:  Take IBOutlet of UITableview and declare an array of User’s Chat list from Model => MessageModel class (UserModel)
Swift




x
24


1
class ChatListViewController: UIViewController { 
2
    //MARK:- Variables 
3
    var arrUserList : [UsersModel] = [] 
4
    //MARK:- IBOutlets 
5
    @IBOutlet weak var tblUsers: UITableView! 
6
    //MARK:- UIView Life Cycle 
7
    override func viewDidLoad() { 
8
        super.viewDidLoad() 
9
        arrUserList = generateRandomUsers() 
10
        tblUsers.estimatedRowHeight = 76 
11
        tblUsers.rowHeight = UITableView.automaticDimension
12
            tblUsers.reloadData()
13
        tblUsers.dragDelegate = self 
14
  
15
        if arrUserList.count > 0 { 
16
            DispatchQueue.main.asyncAfter(deadline: .now()+0.2) { 
17
            self.tblUsers.selectRow(at: IndexPath(row: 0, section: 0),
18
                                    animated: false, scrollPosition: .top) 
19
             self.setDataInDetailVC(model: self.arrUserList[0]) 
20
    
21
            }
22
        } 
23
    }
24
}



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

Swift




xxxxxxxxxx
1
53


 
1
//MARK:- UITableViewDragDelegate Delegate 
2
extension ChatListViewController: UITableViewDragDelegate { 
3
    func tableView(_ tableView: UITableView, itemsForBeginning session:
4
                   UIDragSession, at indexPath: IndexPath) -> [UIDragItem] { 
5
        let selectedMessage = arrUserList[indexPath.row] 
6
        let userActivity = selectedMessage.openDetailUserActivity 
7
        let itemProvider = NSItemProvider(object: UIImage(named:
8
                                              selectedMessage.displayImage)!) 
9
        itemProvider.registerObject(userActivity, visibility: .all) 
10
 
          
11
        let dragItem = UIDragItem(itemProvider: itemProvider) 
12
        dragItem.localObject = selectedMessage 
13
        
14
        return [dragItem] 
15
    } 
16
} 
17
 
          
18
//MARK:- UITableViewDelegate Delegate and UITableViewDataSource 
19
extension ChatListViewController : UITableViewDelegate, UITableViewDataSource { 
20
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 
21
    return arrUserList.count 
22
}
23
  
24
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 
25
    if let cell : ChatUsersTableViewCell =      
26
        tableView.dequeueReusableCell(withIdentifier:"ChatUsersTableViewCell")
27
            as?
28
    
29
        ChatUsersTableViewCell { 
30
            cell.cellConfig(user: arrUserList[indexPath.row]) 
31
            return cell 
32
    } else { 
33
        return UITableViewCell() 
34
    } 
35
} 
36
  
37
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 
38
    self.setDataInDetailVC(model: arrUserList[indexPath.row]) 
39
} 
40
  
41
fileprivate func setDataInDetailVC(model:UsersModel) { 
42
    if let vc : MainSplitViewController = self.navigationController?.parent
43
        as? MainSplitViewController { 
44
        for childern in vc.children { 
45
            if let navVC = childern as? UINavigationController,let childVC =
46
                navVC.viewControllers.first as? ChatViewController { 
47
                  childVC.userModel = model 
48
                  childVC.refreshData() 
49
            } 
50
        } 
51
    } 
52
    } 
53
}



Step 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.

Swift




xxxxxxxxxx
1
159


1
class ChatViewController: UIViewController {
2
 
          
3
    
4
 
          
5
    //MARK:- Variables
6
 
          
7
    var userModel : UsersModel!
8
 
          
9
    var isValidFromOtherWindow : Bool = false
10
 
          
11
    private var arrMessage : [Messages] = []
12
 
          
13
    private var currentUser : UsersModel = UsersModel(senderID: String(0), displayName: generateRandomName(), profession: generateProfessionName(), displayImage: "22")
14
 
          
15
    private var activeTextField : UITextView? = nil
16
 
          
17
    
18
 
          
19
    //MARK:- IBOutlets
20
 
          
21
    @IBOutlet weak var lblShadowMessage: UILabel!
22
 
          
23
    @IBOutlet weak var txtMessage: UITextView!
24
 
          
25
    @IBOutlet weak var tblMessages: UITableView!
26
 
          
27
    @IBOutlet weak var btnSend: UIButton!
28
 
          
29
    @IBOutlet weak var scrollView: UIScrollView!
30
 
          
31
    @IBOutlet weak var containerView: UIView!
32
 
          
33
    
34
 
          
35
    //MARK:- UIView Life Cycle
36
 
          
37
    override func viewDidLoad() {
38
 
          
39
        super.viewDidLoad()
40
 
          
41
        setUI()
42
 
          
43
        if (isValidFromOtherWindow) {
44
 
          
45
            self.navigationItem.hidesBackButton = true
46
 
          
47
            self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: "Done", style: .plain, target: self, action: #selector(dismissViewController))
48
 
          
49
            
50
 
          
51
        }
52
 
          
53
        NotificationCenter.default.addObserver(
54
 
          
55
            self,
56
 
          
57
            selector: #selector(self.insertMessages(_:)),
58
 
          
59
            name: .messageAddedNotifiation,
60
 
          
61
            object: nil)
62
 
          
63
    }
64
 
          
65
    
66
 
          
67
    override func viewDidAppear(_ animated: Bool) {
68
 
          
69
        super.viewDidAppear(animated)
70
 
          
71
        if (isValidFromOtherWindow) {
72
 
          
73
            self.refreshData()
74
 
          
75
        }
76
 
          
77
        self.registerKeyboardNotifications()
78
 
          
79
        guard let user = userModel else {
80
 
          
81
            return
82
 
          
83
        }
84
 
          
85
        if #available(iOS 13.0, *) {
86
 
          
87
            view.window?.windowScene?.userActivity = user.openDetailUserActivity
88
 
          
89
        }
90
 
          
91
        
92
 
          
93
    }
94
 
          
95
    
96
 
          
97
    override func viewWillDisappear(_ animated: Bool) {
98
 
          
99
        super.viewWillDisappear(animated)
100
 
          
101
        self.deRegisterKeyboardNotifications()
102
 
          
103
        if #available(iOS 13.0, *) {
104
 
          
105
            view.window?.windowScene?.userActivity = nil
106
 
          
107
        }
108
 
          
109
    }
110
 
          
111
    
112
 
          
113
    //MARK:- Set UI and Chat Configration
114
 
          
115
    func refreshData() {
116
 
          
117
        arrMessage = generateRandomMessages(currentUser: currentUser, otherUser: userModel)
118
 
          
119
        guard self.tblMessages != nil else {
120
 
          
121
            return
122
 
          
123
        }
124
 
          
125
        self.tblMessages.reloadData()
126
 
          
127
        self.tblMessages.scrollToBottom()
128
 
          
129
    }
130
 
          
131
    
132
 
          
133
    fileprivate func setUI() {
134
 
          
135
        
136
 
          
137
        txtMessage.layer.cornerRadius = 4
138
 
          
139
        if (!isValidFromOtherWindow) {
140
 
          
141
            txtMessage.addDoneButtonOnKeyboard()
142
 
          
143
        }
144
 
          
145
        
146
 
          
147
        tblMessages.register(UINib(nibName: "IncommingChatMessageTableViewCell", bundle: nil), forCellReuseIdentifier: "IncommingChatMessageTableViewCell")
148
 
          
149
        tblMessages.register(UINib(nibName: "OutgoingChatMessageTableViewCell", bundle: nil), forCellReuseIdentifier: "OutgoingChatMessageTableViewCell")
150
 
          
151
        tblMessages.estimatedRowHeight = 70
152
 
          
153
        tblMessages.rowHeight = UITableView.automaticDimension
154
 
          
155
        tblMessages.reloadData()
156
 
          
157
        
158
 
          
159
    }



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

Swift




xxxxxxxxxx
1
174


 
1
@objc fileprivate func dismissViewController() {
2
        if #available(iOS 13.0, *) {
3
            var currentSession : UISceneSession? = nil
4
            for session in UIApplication.shared.openSessions {
5
                if let scene = session.scene,let currentScene = view.window?.windowScene,scene == currentScene {
6
                    currentSession = session
7
                }
8
            }
9
            guard let session = currentSession else {
10
                return
11
            }
12
            UIApplication.shared.requestSceneSessionDestruction(session, options: nil) { (error) in
13
                
14
            }
15
        }
16
        
17
    }
18
    
19
    
20
    fileprivate func setUpdateLayout() {
21
        self.view.updateConstraints()
22
        self.view.layoutIfNeeded()
23
        self.view.setNeedsLayout()
24
    }
25
    
26
    //MARK:- IBAction
27
    @IBAction fileprivate func btnSendAction(_ sender: UIButton) {
28
        NotificationCenter.default.post(name: .messageAddedNotifiation, object: self.userModel, userInfo: ["data":[txtMessage.text ?? ""]])
29
        txtMessage.text = ""
30
        lblShadowMessage.text = ""
31
        txtMessage.resignFirstResponder()
32
    }
33
    
34
}
35
 
36
//MARK:- UITableViewDelegate and UITableViewDataSource
37
extension ChatViewController : UITableViewDelegate,UITableViewDataSource {
38
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
39
        return arrMessage.count
40
    }
41
    
42
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
43
        let message = arrMessage[indexPath.row]
44
        if message.sender.senderId == self.currentUser.senderId {
45
            if let cell = tableView.dequeueReusableCell(withIdentifier: "OutgoingChatMessageTableViewCell") as? OutgoingChatMessageTableViewCell {
46
                cell.cellConfig(model: message)
47
                return cell
48
            }
49
        }else {
50
            if let cell = tableView.dequeueReusableCell(withIdentifier: "IncommingChatMessageTableViewCell") as? IncommingChatMessageTableViewCell {
51
                cell.cellConfig(model: message)
52
                return cell
53
            }
54
        }
55
        
56
        return UITableViewCell()
57
    }
58
    
59
    
60
}
61
 
          
62
//MARK:- UITextViewDelegate
63
extension ChatViewController : UITextViewDelegate {
64
    func textViewShouldBeginEditing(_ textView: UITextView) -> Bool {
65
        activeTextField = textView
66
        return true
67
    }
68
    func textViewDidBeginEditing(_ textView: UITextView) {
69
        
70
    }
71
    
72
    func textViewDidEndEditing(_ textView: UITextView) {
73
        activeTextField = nil
74
    }
75
    
76
    func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
77
        let currentText = textView.text ?? ""
78
        guard let stringRange = Range(range, in: currentText) else { return false }
79
        let updatedText = currentText.replacingCharacters(in: stringRange, with: text)
80
        let previousHeight = lblShadowMessage.frame.height
81
        lblShadowMessage.text = updatedText
82
        self.tblMessages.layoutIfNeeded()
83
        self.setUpdateLayout()
84
        let newHeight = lblShadowMessage.frame.height - previousHeight
85
        tblMessages.contentOffset = CGPoint(x: 0, y: tblMessages.contentOffset.y + newHeight)
86
        self.tblMessages.layoutIfNeeded()
87
        return true
88
    }
89
}
90
 
          
91
//MARK:- InputBarAccessoryViewDelegate
92
extension ChatViewController  {
93
    
94
    @objc fileprivate func insertMessages(_ notification:NSNotification) {
95
        guard let user = notification.object as? UsersModel else {
96
            return
97
        }
98
        guard let userModel = self.userModel else {
99
            return
100
        }
101
        if userModel.senderId != user.senderId {
102
            return
103
        }
104
        guard let dictData = notification.userInfo as? [String:Any],let data = dictData["data"] as? [String] else {
105
            return
106
        }
107
        if arrMessage.count == 0 {
108
            return
109
        }
110
        
111
        for component in data {
112
            if component.trimmingCharacters(in: .whitespacesAndNewlines).count == 0 {
113
                continue;
114
            }else{
115
                let message = Messages(sender: currentUser, messageId: String(Int(arrMessage[arrMessage.count-1].messageId) ?? 0 + 1), sentDate: Date(), message: component.trimmingCharacters(in: .whitespacesAndNewlines))
116
                arrMessage.append(message)
117
            }
118
            
119
        }
120
        self.tblMessages.reloadData()
121
        self.tblMessages.setNeedsDisplay()
122
        self.tblMessages.scrollToBottom()
123
    }
124
}
125
 
          
126
//MARK: - Keyboard Notification observer Methods
127
extension ChatViewController {
128
    
129
    fileprivate func registerKeyboardNotifications() {
130
            
131
        NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardWillShow(notification:)), name: UIResponder.keyboardWillShowNotification, object: nil)
132
 
          
133
        NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardWillHide(notification:)), name: UIResponder.keyboardWillHideNotification, object: nil)
134
        
135
 
          
136
    }
137
    
138
    fileprivate func deRegisterKeyboardNotifications() {
139
               
140
        NotificationCenter.default.removeObserver(self, name: UIResponder.keyboardWillShowNotification, object: nil)
141
        NotificationCenter.default.removeObserver(self, name: UIResponder.keyboardDidHideNotification, object: nil)
142
    }
143
    
144
    @objc fileprivate func keyboardWillShow(notification: NSNotification) {
145
        
146
        if let activeTextField = activeTextField { // this method will get called even if a system generated alert with keyboard appears over the current VC.
147
            
148
            let info: NSDictionary = notification.userInfo! as NSDictionary
149
            let value: NSValue = info.value(forKey: UIResponder.keyboardFrameEndUserInfoKey) as! NSValue
150
            let keyboardSize: CGSize = value.cgRectValue.size
151
            
152
            let contentInsets: UIEdgeInsets = UIEdgeInsets(top: 0.0, left: 0.0, bottom: keyboardSize.height, right: 0.0)
153
            scrollView.contentInset = contentInsets
154
            scrollView.scrollIndicatorInsets = contentInsets
155
            
156
            var aRect: CGRect = self.view.frame
157
            aRect.size.height -= keyboardSize.height
158
            let activeTextFieldRect: CGRect? = activeTextField.convert(activeTextField.frame, to: self.containerView)//activeTextField.frame
159
            
160
            let activeTextFieldOrigin: CGPoint? = activeTextFieldRect?.origin
161
            if (!aRect.contains(activeTextFieldOrigin!)) {
162
                scrollView.scrollRectToVisible(activeTextFieldRect!, animated:true)
163
            }
164
        }
165
    }
166
    
167
    
168
    @objc fileprivate func keyboardWillHide(notification: NSNotification) {
169
        
170
        let contentInsets: UIEdgeInsets = .zero
171
        scrollView.contentInset = contentInsets
172
        scrollView.scrollIndicatorInsets = contentInsets
173
    }
174
}



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. I hope that you found this iPhone tutorial useful to further your understanding of this concept about this new feature.


Further Reading


Topics:
web dev ,swift ,objective-c ,ios ,ipados ,tutorial

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}