I wanted to post a solution I created which will show a progress bar in a separate window to show progress with results that could be listed off.
Attached is an image below of what it looks like.
Compatibility: Swift 3
Features: Threaded codeToRun & Cancel + Done Button. Automatically shows progress amount left if you wanted with updateProgress function.
The code is very simple and easy to modify, just a storyboard and a viewcontroller.
- Use the XML below and save with the final name I suggest below somewhere in your project.
ProgressWindow.storyboard
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="11542" systemVersion="16B2555" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES">
<device id="retina4_7" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="11524"/>
<capability name="Constraints to layout margins" minToolsVersion="6.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--Progress-->
<scene sceneID="5gE-ws-FsC">
<objects>
<viewController storyboardIdentifier="progressWindow" title="Progress" id="IB9-Dc-dCV" customClass="ProgressWindowViewController" customModule="WorkOrders" customModuleProvider="target" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="Lvc-9P-nmJ"/>
<viewControllerLayoutGuide type="bottom" id="xJS-yG-jWM"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="iDk-68-mde">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Please Wait..." textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="9ui-W7-ucD">
<rect key="frame" x="16" y="84" width="343" height="21"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
<progressView opaque="NO" contentMode="scaleToFill" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="ov7-yH-A5z">
<rect key="frame" x="16" y="113" width="343" height="2"/>
</progressView>
<textView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" editable="NO" text="Starting Up..." textAlignment="natural" selectable="NO" translatesAutoresizingMaskIntoConstraints="NO" id="bwA-YT-FcE">
<rect key="frame" x="16" y="123" width="343" height="464"/>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<fontDescription key="fontDescription" type="system" pointSize="14"/>
<textInputTraits key="textInputTraits" autocapitalizationType="sentences"/>
</textView>
<navigationBar contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="RPs-Mo-Cfx">
<rect key="frame" x="-4" y="20" width="383" height="44"/>
<items>
<navigationItem title="Starting Sync..." id="Y87-LY-5o5">
<barButtonItem key="rightBarButtonItem" title="Cancel" id="AD3-in-E6j">
<connections>
<action selector="cancelNavItemButtonActionWithSender:" destination="IB9-Dc-dCV" id="IF1-MG-v2x"/>
</connections>
</barButtonItem>
</navigationItem>
</items>
</navigationBar>
<button opaque="NO" contentMode="scaleToFill" enabled="NO" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="6lh-KK-lX1">
<rect key="frame" x="113" y="595" width="150" height="42"/>
<fontDescription key="fontDescription" type="system" pointSize="25"/>
<state key="normal" title="Done"/>
<state key="disabled" title="Please Wait..."/>
<connections>
<action selector="doneButtonActionWithSender:" destination="IB9-Dc-dCV" eventType="touchUpInside" id="KQH-Th-NAC"/>
</connections>
</button>
</subviews>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstAttribute="leadingMargin" secondItem="ov7-yH-A5z" secondAttribute="leading" id="9Gm-bd-GY6"/>
<constraint firstItem="xJS-yG-jWM" firstAttribute="top" secondItem="bwA-YT-FcE" secondAttribute="bottom" constant="20" id="CT1-0k-Skt"/>
<constraint firstItem="6lh-KK-lX1" firstAttribute="top" secondItem="bwA-YT-FcE" secondAttribute="bottom" constant="8" symbolic="YES" id="Fb8-eP-lxu"/>
<constraint firstItem="RPs-Mo-Cfx" firstAttribute="leading" secondItem="iDk-68-mde" secondAttribute="leadingMargin" constant="-20" id="JSY-Na-oAF"/>
<constraint firstItem="xJS-yG-jWM" firstAttribute="top" secondItem="6lh-KK-lX1" secondAttribute="bottom" constant="30" id="NHY-fO-W26"/>
<constraint firstAttribute="trailingMargin" secondItem="RPs-Mo-Cfx" secondAttribute="trailing" constant="-20" id="QxH-pj-oOA"/>
<constraint firstItem="RPs-Mo-Cfx" firstAttribute="top" secondItem="Lvc-9P-nmJ" secondAttribute="bottom" id="VIf-63-vaw"/>
<constraint firstAttribute="trailingMargin" secondItem="bwA-YT-FcE" secondAttribute="trailing" id="WxH-hu-ZVQ"/>
<constraint firstAttribute="leadingMargin" secondItem="bwA-YT-FcE" secondAttribute="leading" id="XEd-Ba-ZfL"/>
<constraint firstItem="bwA-YT-FcE" firstAttribute="top" secondItem="ov7-yH-A5z" secondAttribute="bottom" constant="8" id="Xjr-bH-ILB"/>
<constraint firstItem="6lh-KK-lX1" firstAttribute="centerY" secondItem="iDk-68-mde" secondAttribute="centerY" id="ZU1-pD-czP"/>
<constraint firstItem="ov7-yH-A5z" firstAttribute="top" secondItem="9ui-W7-ucD" secondAttribute="bottom" constant="8" symbolic="YES" id="avI-Ab-G29"/>
<constraint firstAttribute="leadingMargin" secondItem="9ui-W7-ucD" secondAttribute="leading" id="dse-zV-g00"/>
<constraint firstItem="6lh-KK-lX1" firstAttribute="centerX" secondItem="iDk-68-mde" secondAttribute="centerX" id="i5Q-oY-DdV"/>
<constraint firstAttribute="trailingMargin" secondItem="9ui-W7-ucD" secondAttribute="trailing" id="jqu-1f-IuA"/>
<constraint firstItem="9ui-W7-ucD" firstAttribute="top" secondItem="RPs-Mo-Cfx" secondAttribute="bottom" constant="20" id="nrH-ey-Zcm"/>
<constraint firstAttribute="trailingMargin" secondItem="ov7-yH-A5z" secondAttribute="trailing" id="qha-Es-6Au"/>
</constraints>
<variation key="default">
<mask key="constraints">
<exclude reference="ZU1-pD-czP"/>
<exclude reference="CT1-0k-Skt"/>
</mask>
</variation>
</view>
<connections>
<outlet property="cancelNavButton" destination="AD3-in-E6j" id="SJc-Bc-N6j"/>
<outlet property="currentProgressLabel" destination="9ui-W7-ucD" id="zij-yQ-MFX"/>
<outlet property="doneButton" destination="6lh-KK-lX1" id="rh2-RF-4ak"/>
<outlet property="navItemLabel" destination="Y87-LY-5o5" id="ijO-a7-TrD"/>
<outlet property="navigationBar" destination="RPs-Mo-Cfx" id="WEq-F4-Pup"/>
<outlet property="theProgressBar" destination="ov7-yH-A5z" id="FUE-9J-iBh"/>
<outlet property="theTextView" destination="bwA-YT-FcE" id="1sR-23-NZH"/>
</connections>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="TH6-NB-Eos" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="-492" y="1474"/>
</scene>
</scenes>
</document>
- Save the following code below into your project with the name ProgressWindowViewController.swift
ProgressWindowViewController.swift
import UIKit
protocol progressWindowDelegate : class{
var titleToGive : String {get}
func codeToRun(progressWindowViewController:ProgressWindowViewController)
var codeToCancel : ()->() {get}
}
class ProgressWindowViewController: UIViewController {
@IBOutlet weak var theTextView: UITextView!
@IBOutlet weak var currentProgressLabel: UILabel!
@IBOutlet weak var theProgressBar: UIProgressView!
@IBOutlet weak var navItemLabel: UINavigationItem!
@IBOutlet weak var doneButton: UIButton!
@IBOutlet weak var cancelNavButton: UIBarButtonItem!
@IBOutlet weak var navigationBar: UINavigationBar!
//For showing network activity
var indicator: UIActivityIndicatorView = UIActivityIndicatorView(activityIndicatorStyle: UIActivityIndicatorViewStyle.gray)
//Sets delegate
weak var controllerDelegate:progressWindowDelegate? = nil
override func viewDidLoad() {
super.viewDidLoad()
navItemLabel.title = controllerDelegate!.titleToGive
//Run on the main thread first then in background thread.
DispatchQueue.main.async {
DispatchQueue.global(qos: .background).async{
self.controllerDelegate?.codeToRun(progressWindowViewController: self)
}
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func writeToProgressWindow(text:NSMutableAttributedString){
DispatchQueue.main.async(execute: {
print("dispatch_queue_get_main_queue -> writeToProgressWindow()")
self.theTextView.attributedText = text
})
}
func updateNetworkIndicator(active:Bool){
DispatchQueue.main.async(execute: {
if(active){
UIApplication.shared.isNetworkActivityIndicatorVisible = true
}else{
UIApplication.shared.isNetworkActivityIndicatorVisible = false
}
})
}
func updateProgress(updatetext:String,amount:Int,left:Int){
DispatchQueue.main.async(execute: {
print("dispatch_queue_get_main_queue -> updatingProgress()")
self.currentProgressLabel.text = updatetext+" : \(amount) / \(left)"
self.theProgressBar.setProgress(Float(amount/left), animated: true) //progress is represented as a percentage of the total
})
}
func updateProgressWindowWeFinished(title:String){
//Enable done button and Disable/Hide Cancel Button. Add Final Messages
DispatchQueue.main.async(execute: {
self.doneButton.isEnabled = true
self.navItemLabel.title = title
self.cancelNavButton.isEnabled = false
self.cancelNavButton.tintColor = UIColor.clear
})
}
@IBAction func cancelNavItemButtonAction(sender: UIBarButtonItem) {
//Run on the main thread first then in background thread.
DispatchQueue.main.async {
DispatchQueue.global(qos: .background).sync{
print("dispatch_queue_priority_default")
self.controllerDelegate?.codeToCancel()
}
self.dismiss(animated: true, completion: nil)//closes the window.
}
}
@IBAction func doneButtonAction(sender: UIButton) {
self.dismiss(animated: true, completion: nil)//closes the window.
}
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
// Get the new view controller using segue.destinationViewController.
// Pass the selected object to the new view controller.
}
*/
}
3.) To use you simply set the view controller which called it to the progressWindowDelegate and supply the needed information to communicate with the popover.
Some example code below if you need it:
SampleViewControllerWithYesButton.swift
class SyncViewController: UIViewController, progressWindowDelegate {
var codeToCancel = {print("code to cancel")}
var titleToGive = "Starting Sync..."
func codeToRun(progressWindowViewController:ProgressWindowViewController) {
print("code to run")
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
@IBAction func yesButtonAction(_ sender: UIButton) {
let storyboard = UIStoryboard(name: "ProgressWindow", bundle: nil)
let controller = storyboard.instantiateViewController(withIdentifier: "progressWindow") as! ProgressWindowViewController
controller.controllerDelegate = self
self.present(controller, animated: true, completion: nil)
}
@IBAction func noButtonAction(_ sender: UIButton) {
tabBarController?.selectedIndex = 1 //Send them to the list then.
}
/*
// 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.
}
*/
}
Whatever you put in the codeToRun() will run as a background thread. To access any of the UI the codeToRun has access to the progressWindowViewController which has been preconfigured to hook up to the UI with these elements at your control.
@IBOutlet weak var theTextView: UITextView!
@IBOutlet weak var currentProgressLabel: UILabel!
@IBOutlet weak var theProgressBar: UIProgressView!
@IBOutlet weak var navItemLabel: UINavigationItem!
@IBOutlet weak var doneButton: UIButton!
@IBOutlet weak var cancelNavButton: UIBarButtonItem!
@IBOutlet weak var navigationBar: UINavigationBar!
I use this in all my projects anytime I'm doing a sync, upload or a time consuming network task.
This also shows the activity indictor as well to show that you are trying to do something. All of this can be customized as its very easy to look over and understand how it works. Hopefully this helps save some of you some time quickly getting a progress type window up.