Is it possible to put a loading animation over the VNDocumentViewController? As in, when the user presses the Save button, is there a way for me to somehow indicate that the Vision is processing the image and hasn't frozen? Right now, in my app, there is a long pause between the user pressing Save and the actual image being processed.Here is an example from another post of what I'm trying to create
Here is one example of adding a loading indicator using UIActivityIndicatorView().
startAnimating() to start the animation and stopAnimation() to stop the animation.
iOS - Display a progress indicator at the center of the screen rather than the view
guard let topWindow = UIApplication.shared.windows.last else {return}
let overlayView = UIView(frame: topWindow.bounds)
overlayView.backgroundColor = UIColor.clear
topWindow.addSubview(overlayView)
let hudView = UIActivityIndicatorView()
hudView.bounds = CGRect(x: 0, y: 0, width: 20, height: 20)
overlayView.addSubview(hudView)
hudView.center = overlayView.center
hudView.startAnimating()
Alternatively, you could look into using Cocoapod MBProgressHud
https://cocoapods.org/pods/MBProgressHUD
There's a way you can extend a class in Swift that captures this problem well. The idea is you want a UIActivityIndicator in your VNDocumentCameraViewController. But we'd like that to be a part of every version of this we use. We could simply embed the DocumentVC's view into our current view and superimpose a UIActivityIndicator above it in the view stack, but that's pretty hacky. Here's a quick way we can extend any class and solve this problem
import VisionKit
import UIKit
extension VNDocumentCameraViewController {
private struct LoadingContainer {
static var loadingIndicator = UIActivityIndicatorView()
}
var loadingIndicator: UIActivityIndicatorView {
return LoadingContainer.loadingIndicator
}
func animateLoadingIndicator() {
if loadingIndicator.superview == nil {
view.addSubview(loadingIndicator)
//Setup your constraints through your favorite method
//This constrains it to the very center of the controller
loadingIndicator.frame = CGRect(
x: view.frame.width / 2.0,
y: view.frame.height / 2.0,
width: 20,
height: 20)
//Setup additional state like color/etc here
loadingIndicator.color = .white
}
loadingIndicator.startAnimating()
}
func stopAnimatingLoadingIndicator() {
loadingIndicator.stopAnimating()
}
}
The place we can call these functions are in the delegate methods for VNDocumentCameraViewController that you implement in your presenting ViewController:
func documentCameraViewController(
_ controller: VNDocumentCameraViewController,
didFinishWith scan: VNDocumentCameraScan
) {
controller.animateLoadingIndicator()
}
Related
I want to create a Floating Button in my app which will be visible all over the page and can handle the event from a separate ViewController to maintain the button state.
I want to open a ViewController from a separate window at the same time when the user login into the app. And if the user clicks on the button then ViewController will handle the event and if click outside the button then it should handle by the main controller.
Thanks
First Create a UIViwe with a .xib file and create your required UI. All UI-related functions will be there in UIView class and for action create a manager class that manages all events.
Use the below code to add playerView to the window
PlayerManager.shared.addPlayer()
See bellow code for playerManager class
class PlayerManager : NSObject{
static let shared = PlayerManager()
private override init(){
super.init()
}
func addPlayer(){
let window = UIApplication.shared.keyWindow
let player = Bundle.main.loadNibNamed("PlayerView", owner: nil, options: nil)![0] as! PlayerView
let outerView = UIView(frame: CGRect(x: getXValueForView(width: 180), y: getYValueForView(height: 120), width: 180, height: 60))
player.frame.size = CGSize(width: 180, height: 60)
player.playerDelegate = self
outerView.clipsToBounds = true
player.miniPlayer.isHidden = false
player.expendedPlayer.isHidden = true
outerView.addSubview(player)
window?.addSubview(outerView)
}
}
In the following code, I want to draw a progress bar that grows. what should I call at the line marked
// what goes here?
Assume the code is run on the main thread.
class View: UIView {
func updateProgressBar(){
var boxFrame = CGRect(x:10, y:10: width:100, height: 100)
for _ in 0...<10 {
let box = UIView(frame: boxFrame)
box.backgroundColor = .blue
addSubview(box)
// What goes here?
Thread.sleep(forTimeInterval: 2)
boxFrame.origin.x = boxFrame.width
}
}
}
What will be the answer from the following option and why?
setNeedsDisplay()
layer.draw(in: UIGraphicsGetCurrentConntext()!)
draw(bounds)
This approach is incorrect. It should be implemented in another way. such as with DispatchQueue.asyncAfter()
Writing an app with some network activity I find myself writing the same code for multiple view controllers over and over just to display an activity indicator.
class SomeViewController: UIViewController {
let indicator: UIActivityIndicatorView = UIActivityIndicatorView(frame: CGRect(x: 0.0, y: 0.0, width: 100.0, height: 100.0))
override func viewDidLoad() {
super.viewDidLoad()
// customize indicator
self.indicator.layer.cornerRadius = 10
self.indicator.center = self.view.center
self.indicator.hidesWhenStopped = true
self.indicator.activityIndicatorViewStyle = UIActivityIndicatorViewStyle.whiteLarge
self.indicator.backgroundColor = UIColor(red: 1/255, green: 1/255, blue: 1/255, alpha: 0.5)
}
// MARK: - Acitivity Indicator
func startIndicatingActivity() {
DispatchQueue.main.async {
self.view.addSubview(self.indicator)
self.indicator.startAnimating()
}
}
func stopIndicatingActivity() {
DispatchQueue.main.async {
self.indicator.stopAnimating()
}
}
}
Within the same SomeViewController class I can then use it as follows:
#IBAction func startHeavyNetworkStuffButtonPressed(_ sender: UIButton) {
startIndicatingActivity()
doHeavyNetworkStuff() { success in
// heavy networking has finished
stopIndicatingActivity()
}
}
This works fine as long as I only need to display the activity indicator in a single view controller. However, it's tedious to do it over and over for every view controller that needs this functionality. As I hate writing the same code over and over, I am in search of a solution where I can simply call
startIndicatingActivity() (and stopIndicatingActivity() respectively) in any view controller.
0th idea - Extension
My obvious first thought was to write an extension for the UIViewController class. As I need to store an instance of the UIActivityIndicatorView, however, I got the Extensions may not contain stored properties error.
1st idea - Subclassing
Next up: subclassing UIViewController. This would work fine for any simple view controller. However, if I needed the same functionality for a MyCustomTableViewController, I would again need to first subclass from UITableViewController and copy/paste existing code.
My question
Is there an elegant way to call startIndicatingActivity() / stopIndicatingActivity() in any view controller while avoiding to copy/paste large amounts of code? I'm assuming an elegant solution would involve an extension, protocol, or some kind of multiple-inheritance approach.
This SO thread is the solution! Turns out there is a way to solve this with an extension and simulated properties, after all.
Posting the complete solution for the interested reader:
Extending UIViewController
extension UIViewController {
// see ObjectAssociation<T> class below
private static let association = ObjectAssociation<UIActivityIndicatorView>()
var indicator: UIActivityIndicatorView {
set { UIViewController.association[self] = newValue }
get {
if let indicator = UIViewController.association[self] {
return indicator
} else {
UIViewController.association[self] = UIActivityIndicatorView.customIndicator(at: self.view.center)
return UIViewController.association[self]!
}
}
}
// MARK: - Acitivity Indicator
public func startIndicatingActivity() {
DispatchQueue.main.async {
self.view.addSubview(self.indicator)
self.indicator.startAnimating()
//UIApplication.shared.beginIgnoringInteractionEvents() // if desired
}
}
public func stopIndicatingActivity() {
DispatchQueue.main.async {
self.indicator.stopAnimating()
//UIApplication.shared.endIgnoringInteractionEvents()
}
}
}
Borrowing code from said SO thread
// source: https://stackoverflow.com/questions/25426780/how-to-have-stored-properties-in-swift-the-same-way-i-had-on-objective-c
public final class ObjectAssociation<T: AnyObject> {
private let policy: objc_AssociationPolicy
/// - Parameter policy: An association policy that will be used when linking objects.
public init(policy: objc_AssociationPolicy = .OBJC_ASSOCIATION_RETAIN_NONATOMIC) {
self.policy = policy
}
/// Accesses associated object.
/// - Parameter index: An object whose associated object is to be accessed.
public subscript(index: AnyObject) -> T? {
get { return objc_getAssociatedObject(index, Unmanaged.passUnretained(self).toOpaque()) as! T? }
set { objc_setAssociatedObject(index, Unmanaged.passUnretained(self).toOpaque(), newValue, policy) }
}
}
For the sake of completeness
I also extended the UIActivityIndicatorView class with a static function with my customizations.
extension UIActivityIndicatorView {
public static func customIndicator(at center: CGPoint) -> UIActivityIndicatorView {
let indicator = UIActivityIndicatorView(frame: CGRect(x: 0.0, y: 0.0, width: 100.0, height: 100.0))
indicator.layer.cornerRadius = 10
indicator.center = center
indicator.hidesWhenStopped = true
indicator.activityIndicatorViewStyle = UIActivityIndicatorViewStyle.whiteLarge
indicator.backgroundColor = UIColor(red: 1/255, green: 1/255, blue: 1/255, alpha: 0.5)
return indicator
}
}
Sample show full loading in any View
extension UIView{
/**
ShowLoader: loading view ..
- parameter Color: ActivityIndicator and view loading color .
*/
func showLoader(_ color:UIColor?){
let LoaderView = UIView(frame: CGRect(x: 0, y: 0, width: self.frame.width, height: self.frame.height))
LoaderView.tag = -888754
LoaderView.backgroundColor = color
let Loader = UIActivityIndicatorView(frame: CGRect(x: 0, y: 0, width: 60, height: 30))
Loader.center = LoaderView.center
Loader.activityIndicatorViewStyle = .whiteLarge
Loader.color = Color.primaryColor
Loader.startAnimating()
LoaderView.addSubview(Loader)
self.addSubview(LoaderView)
}
/**
dismissLoader: hidden loading view ..
*/
func dismissLoader(){
self.viewWithTag(-888754)?.removeFromSuperview()
}
}
call this func
in UIViewController
self.view.showLoader(nil) //you can set background color nil or any color
// dismiss
self.view.dismissLoader()
I prefer this method because you can use it any view in button ,
tableview , cell ...etc
I just wanted to create a view and when it shown then the whole background will be dimmed like an alert view controller. If it is possible then please guide me and if possible then provide me code.
Thank you
The simplest way for doing that is to add a semi-transparent background (e.g. black with alpha less than 1.0) view, which contains the alert view. The background view should cover all other views in the view controller.
You can also use a modal view controller which has such a background view as its view, and presenting this controller with presentation style Over Full Screen.
// Here is the wrapper code i use in most of my project now a days
protocol TransparentBackgroundProtocol {
associatedtype ContainedView
var containedNib: ContainedView? { get set }
}
extension TransparentBackgroundProtocol where ContainedView: UIView {
func dismiss() {
containedNib?.superview?.removeFromSuperview()
containedNib?.removeFromSuperview()
}
mutating func add(withFrame frame: CGRect, toView view: UIView, backGroundViewAlpha: CGFloat) {
containedNib?.frame = frame
let backgroundView = configureABlackBackGroundView(alpha: backGroundViewAlpha)
view.addSubview(backgroundView)
guard let containedNib = containedNib else {
print("No ContainedNib")
return
}
backgroundView.addSubview(containedNib)
}
private func configureABlackBackGroundView(alpha: CGFloat) -> UIView {
let blackBackgroundView = UIView(frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.size.width, height: UIScreen.main.bounds.size.height))
blackBackgroundView.backgroundColor = UIColor.black.withAlphaComponent(alpha)
return blackBackgroundView
}
}
// Sample View shown like alertView
class LogoutPopUpView: UIView, TransparentBackgroundProtocol {
// MARK: Variables
weak var containedNib: LogoutPopUpView?
typealias ContainedView = LogoutPopUpView
// MARK: Outlets
// MARK: Functions
class func initiate() -> LogoutPopUpView {
guard let nibView = Bundle.main.loadNibNamed("LogoutPopUpView", owner: self, options: nil)?[0] as? LogoutPopUpView else {
fatalError("Cann't able to load nib file.")
}
return nibView
}
}
// where u want to show pop Up
logOutPopup = LogoutPopUpView.instanciateFromNib()
let view = UIApplication.shared.keyWindow?.rootViewController?.view {
logOutPopup?.add(withFrame: CGRect(x: 30, y:(UIScreen.main.bounds.size.height-340)/2, width: UIScreen.main.bounds.size.width - 60, height: 300), toView: view, backGroundViewAlpha: 0.8)
}
// for dismiss
self.logOutPopup?.dismiss()
I'm developping application in Swift.
This application has many view and I would like to put a UIProgressView on all views
Can we get an array of all storyboard views ?
for exemple :
self.progressBar = UIProgressView(progressViewStyle: .Bar)
self.progressBar?.center = view.center
self.progressBar?.frame = CGRect(x: 0, y: 20, width: view.frame.width, height: CGFloat(1))
self.progressBar?.progress = 1/2
self.progressBar?.trackTintColor = UIColor.lightGrayColor();
self.progressBar?.tintColor = UIColor.redColor();
var arrayViewController : [UIViewController] = [...,...,...]
for controller in arrayViewController {
controller.view.addSubview(self.progressBar)
}
Thank you
Ysée
I assume that what you really want is to have the progress displayed on every view IF there is an operation in progress.
There are many ways to do that (using delegation, NSNotificationCenter, …) but the easiest I can think of would be to rely on viewWillAppear
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
// Check if there's an operation in progress and add progressView if relevant
}
For the user, it will effectively look like you added the progress view to all views.
Why not create a base class that has a lazy stored property of type UIProgressView ? Optionally you can have two methods setProgressViewHidden(hidden : Bool) in order to easily show and hide the progress view and setProgress(progress : Float) to update the progress. Then all your view controllers can subclass this base class and conveniently interact with the progress view.
class ProgressViewController : UIViewController {
lazy var progressView : UIProgressView = {
[unowned self] in
var view = UIProgressView(frame: CGRectMake(0, 20, self.view.frame.size.width, 3))
view.progress = 0.5
view.trackTintColor = UIColor.lightGrayColor()
view.tintColor = UIColor.redColor()
self.view.addSubview(view)
return view
}()
}
To read more about lazy stored properties, check: https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Properties.html