Using same UIActivityIndicatorView in many view controllers - ios

I have a simple iOS app with various view controllers.
Each view controller has different functionality but each view controller has 'load' button, that when triggered, sending a request and getting a result to delegate method.
I want to use an UIActivityIndicatorView that will start when the user will click the button and will stop on the delegate method.
Obviously, I want the indicator to look the same on each VC, so I've made property of it, and on each viewDidLoad method I am using this code:
self.indicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
self.indicator.backgroundColor = [UIColor colorWithWhite:0.0f alpha:0.6f];
self.indicator.frame = CGRectMake(40.0, 20.0, 100.0, 100.0);
self.indicator.center = self.view.center;
The problem is, I am using the same parameters, on the same object, copping and pasting these lines on every view controller.
Let's say I want to change the style in the next version, I need to change it 10 times.
What would be the best way to use some kind of static indicator that would be set with these parameters and would be set on and off by demand?

Here is the one i use in swift 4.1
import UIKit
class ProgressView {
// MARK: - Variables
private var containerView = UIView()
private var progressView = UIView()
private var activityIndicator = UIActivityIndicatorView()
static var shared = ProgressView()
// To close for instantiation
private init() {}
// MARK: - Functions
func startAnimating(view: UIView = (UIApplication.shared.keyWindow?.rootViewController?.view)!) {
containerView.center = view.center
containerView.frame = view.frame
containerView.backgroundColor = UIColor(hex: 0xffffff, alpha: 0.5)
progressView.frame = CGRect(x: 0, y: 0, width: 80, height: 80)
progressView.center = containerView.center
progressView.backgroundColor = UIColor(hex: 0x444444, alpha: 0.7)
progressView.clipsToBounds = true
progressView.cornerRadius = 10
activityIndicator.frame = CGRect(x: 0, y: 0, width: 60, height: 60)
activityIndicator.center = CGPoint(x: progressView.bounds.width/2, y: progressView.bounds.height/2)
activityIndicator.style = .whiteLarge
view.addSubview(containerView)
containerView.addSubview(progressView)
progressView.addSubview(activityIndicator)
activityIndicator.startAnimating()
}
/// animate UIActivityIndicationView without blocking UI
func startSmoothAnimation(view: UIView = (UIApplication.shared.keyWindow?.rootViewController?.view)!) {
activityIndicator.frame = CGRect(x: 0, y: 0, width: 60, height: 60)
activityIndicator.center = view.center
activityIndicator.style = .whiteLarge
activityIndicator.color = UIColor.gray
view.addSubview(activityIndicator)
activityIndicator.startAnimating()
}
func stopAnimatimating() {
activityIndicator.stopAnimating()
containerView.removeFromSuperview()
}
}
extension UIColor {
convenience init(hex: UInt32, alpha: CGFloat) {
let red = CGFloat((hex & 0xFF0000) >> 16)/256.0
let green = CGFloat((hex & 0xFF00) >> 8)/256.0
let blue = CGFloat(hex & 0xFF)/256.0
self.init(red: red, green: green, blue: blue, alpha: alpha)
}
}
// usage
ProgressView.shared.startAnimating()
// to stop
ProgressView.shared.stopAnimatimating()
Hope it helps

I would suggest that you create a superclass to your view controllers and add the spinner functionality there, and let your view controllers inherit from it.
The superclass view controller would look something like this:
// .h-file
#interface SuperclassViewController : UIViewController
- (void)showIndicator;
- (void)hideIndicator;
#end
// .m file
#import "SuperclassViewController.h"
#interface SuperclassViewController ()
#property (nonatomic, strong) UIActivityIndicatorView *indicator;
#end
#implementation SuperclassViewController
- (void)viewDidLoad {
[super viewDidLoad];
_indicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
self.indicator.backgroundColor = [UIColor colorWithWhite:0.0f alpha:0.6f];
self.indicator.frame = CGRectMake(40.0, 20.0, 100.0, 100.0);
self.indicator.layer.cornerRadius = 6;
self.indicator.center = self.view.center;
[self.indicator startAnimating];
}
- (void)showIndicator {
[self.view addSubview:self.indicator];
}
- (void)hideIndicator {
[self.indicator removeFromSuperview];
}
#end
Now, to inherit it do the following in your view controllers .h file:
#import "SuperclassViewController.h"
#interface YourViewController : SuperclassViewController;
/** properties and methods */
#end
Then you can call [self showIndicator] and [self hideIndicator] in your view controllers whenever needed without any extra coding.

You can create single view controller to display loading indicator in all view controller. You need to write code once, put following code in AppDelegate file.
Note: I'm not working in Objective-C, following code in Swift. So you need to transform code in objective C.
First add following code in ProgressVC:
ProgressVC.swift:
class func viewController() -> ProgressVC {
return UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "ProgressVC") as! ProgressVC
}
Add following code in your AppDelegate.
AppDelegate.swift:
var progressVC : ProgressVC?
static let shared = UIApplication.shared.delegate as! AppDelegate
func showLoading(isShow: Bool) {
if isShow {
// Remove progress view if already exist
if progressVC != nil {
progressVC?.view.removeFromSuperview()
progressVC = nil
}
progressVC = ProgressVC.viewController()
AppDelegate.shared.window?.addSubview((progressVC?.view)!)
} else {
if progressVC != nil {
progressVC?.view.removeFromSuperview()
}
}
}
Now, you need to call just above method with AppDelegate's shared instance. Enable animated property of UIActivityIndicatorView from storyboard.
Show:
AppDelegate.shared.showLoading(isShow: true)
Hide:
AppDelegate.shared.showLoading(isShow: false)
Screenshot:

You could create the activity indicator in the UIWindow and then you could show/hide it from any UIViewController.
To get the window use:
UIApplication.shared.keyWindow

Thank you all for your assistance,
I decided to make a singleton class that has a variable of UIActivityIndicatorView.
This is the declaration of the class:
#import "ProgressView.h"
#interface ProgressView()
#property (nonatomic) UIActivityIndicatorView *indicator;
+(ProgressView *)shared;
#end
#implementation ProgressView
+ (ProgressView *)shared {
static ProgressView* sharedVC = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedVC = [[self alloc] init];
});
return sharedVC;
}
- (instancetype)init {
self = [super init];
if (self) {
self.indicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
self.indicator.backgroundColor = [UIColor colorWithWhite:0.0f alpha:0.6f];
self.indicator.frame = CGRectMake(40.0, 20.0, 100.0, 100.0);
}
return self;
}
- (void)startAnimation:(UIView *)view {
self.indicator.center = view.center;
self.indicator.hidden = NO;
[self.indicator startAnimating];
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, 12 * NSEC_PER_SEC);
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
if ([self.indicator isAnimating])
[self stopAnimation];
});
[view addSubview:self.indicator];
}
- (void)stopAnimation {
if ([self.indicator isAnimating]) {
[self.indicator stopAnimating];
[self.indicator removeFromSuperview];
}
}
#end
Please note I have added a rule that if the indicator didn't get triggered to stop in 12 seconds the class would stop the indicator by itself.
Now, all I have to do is to add this line in every place in my code where I would like to start the indicator:
[[ProgressView shared] startAnimation:self.view];
And to add this line to stop it:
[[ProgressView shared] stopAnimation];

Related

How to change volume programmatically on iOS 11.4

Before, I was setting sound volume programmatically using this approach:
MPVolumeView *volumeView = [[MPVolumeView alloc] init];
UISlider *volumeViewSlider = nil;
for (UIView *view in [volumeView subviews])
{
if ([view.class.description isEqualToString:#"MPVolumeSlider"])
{
volumeViewSlider = (UISlider *)view;
break;
}
}
[volumeViewSlider setValue:0.5 animated:YES];
[volumeViewSlider sendActionsForControlEvents:UIControlEventTouchUpInside];
Till iOS 11.4 it was working well (even on iOS 11.3), but on iOS 11.4 it doesn't. Volume value remains unchanged. Can someone help with this issue? Thanks.
Changing volumeViewSlider.value after a small delay resolves problem.
- (IBAction)increase:(id)sender {
MPVolumeView *volumeView = [[MPVolumeView alloc] init];
UISlider *volumeViewSlider = nil;
for (UIView *view in volumeView.subviews) {
if ([view isKindOfClass:[UISlider class]]) {
volumeViewSlider = (UISlider *)view;
break;
}
}
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.01 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
volumeViewSlider.value = 0.5f;
});
}
Swift version
I solved it by adding new MPVolumeView to my UIViewController view, otherwise it didn't set the volume anymore. As I added it to the controller I also need to set the volume view position to be outside of the screen to hide it from the user.
I prefer not to use delayed volume setting as it make things more complicated especially if you need to play sound immediately after setting the volume.
The code is in Swift 4:
let volumeControl = MPVolumeView(frame: CGRect(x: 0, y: 0, width: 120, height: 120))
override func viewDidLoad() {
self.view.addSubview(volumeControl);
}
override func viewDidLayoutSubviews() {
volumeControl.frame = CGRect(x: -120, y: -120, width: 100, height: 100);
}
func setVolume(_ volume: Float) {
let lst = volumeControl.subviews.filter{NSStringFromClass($0.classForCoder) == "MPVolumeSlider"}
let slider = lst.first as? UISlider
slider?.setValue(volume, animated: false)
}
I just added the MPVolumeView as a subview to another view (that was never drawn on screen).
This had to be done prior to any attempt to set or get the volume.
private let containerView = UIView()
private let volumeView = MPVolumeView()
func prepareWorkaround() {
self.containerView.addSubview(self.volumeView)
}
I had to have a MPVolumeView as subview to a view in the hierarchy for the hud not to show up on iOS 12. It needs to be slightly visible:
let volume = MPVolumeView(frame: .zero)
volume.setVolumeThumbImage(UIImage(), for: UIControl.State())
volume.isUserInteractionEnabled = false
volumelume.alpha = 0.0001
volume.showsRouteButton = false
view.addSubview(volume)
When setting the volume I get the slider from MPVolumeView as with previous posters and set the value:
func setVolumeLevel(_ volumeLevel: Float) {
guard let slider = volume.subviews.compactMap({ $0 as? UISlider }).first else {
return
}
slider.value = volumeLevel
}

Negative spacer for UIBarButtonItem in navigation bar on iOS 11

In iOS 10 and below, there was a way to add a negative spacer to the buttons array in the navigation bar, like so:
UIBarButtonItem *negativeSpacer = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFixedSpace target:nil action:nil];
negativeSpacer.width = -8;
self.navigationItem.leftBarButtonItems = #[negativeSpacer, [self backButtonItem]];
This no longer works on iOS 11 (the spacer becomes positive, instead of negative). I have inspected the view hierarchy of the bar button item, and it is now embedded into _UIButtonBarStackView. How to adjust the position of the bar button on iOS 11?
EDIT:
This may no longer work as of iOS 13. You may get the error:
Client error attempting to change layout margins of a private view
OLD ANSWER:
I found a somewhat hacky solution on the Apple developer forums:
https://forums.developer.apple.com/thread/80075
It looks like the problem comes from how iOS 11 handles the UIBarButtonItem .fixedSpace buttons and how a UINavigationBar is laid out in iOS 11. The navigation bars now use autolayout and the layout margins to layout the buttons. The solution presented in that post (at the bottom) was to set all the layout margins to some value you want.
class InsetButtonsNavigationBar: UINavigationBar {
override func layoutSubviews() {
super.layoutSubviews()
for view in subviews {
// Setting the layout margins to 0 lines the bar buttons items up at
// the edges of the screen. You can set this to any number to change
// the spacing.
view.layoutMargins = .zero
}
}
}
To use this new nav bar with custom button spacing, you will need to update where you create any navigation controllers with the following code:
let navController = UINavigationController(navigationBarClass: InsetButtonsNavigationBar.self,
toolbarClass: UIToolbar.self)
navController.viewControllers = [yourRootViewController]
Just a workaround for my case, it might be helpful to some people. I would like to achieve this:
and previously I was using the negativeSpacer as well. Now I figured out this solution:
let logoImage = UIImage(named: "your_image")
let logoImageView = UIImageView(image: logoImage)
logoImageView.frame = CGRect(x: -16, y: 0, width: 150, height: 44)
logoImageView.contentMode = .scaleAspectFit
let logoView = UIView(frame: CGRect(x: 0, y: 0, width: 10, height: 44))
**logoView.clipsToBounds = false**
logoView.addSubview(logoImageView)
let logoItem = UIBarButtonItem(customView: logoView)
navigationItem.leftBarButtonItem = logoItem
Based on keithbhunter's answer I've created a custom UINavigationBar:
NavigationBarCustomMargins.h:
#import <UIKit/UIKit.h>
#interface NavigationBarCustomMargins : UINavigationBar
#property (nonatomic) IBInspectable CGFloat leftMargin;
#property (nonatomic) IBInspectable CGFloat rightMargin;
#end
NavigationBarCustomMargins.m:
#import "NavigationBarCustomMargins.h"
#define DefaultMargin 16
#define NegativeSpacerTag 87236223
#interface NavigationBarCustomMargins ()
#property (nonatomic) BOOL leftMarginIsSet;
#property (nonatomic) BOOL rightMarginIsSet;
#end
#implementation NavigationBarCustomMargins
#synthesize leftMargin = _leftMargin;
#synthesize rightMargin = _rightMargin;
- (void)layoutSubviews {
[super layoutSubviews];
if (([[[UIDevice currentDevice] systemVersion] compare:#"11.0" options:NSNumericSearch] != NSOrderedAscending)) {
BOOL isRTL = [UIApplication sharedApplication].userInterfaceLayoutDirection == UIUserInterfaceLayoutDirectionRightToLeft;
for (UIView *view in self.subviews) {
view.layoutMargins = UIEdgeInsetsMake(0, isRTL ? self.rightMargin : self.leftMargin, 0, isRTL ? self.leftMargin : self.rightMargin);
}
} else {
//left
NSMutableArray *leftItems = [self.topItem.leftBarButtonItems mutableCopy];
if (((UIBarButtonItem *)leftItems.firstObject).tag != NegativeSpacerTag) {
UIBarButtonItem *negativeSpacer = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFixedSpace target:nil action:nil];
negativeSpacer.tag = NegativeSpacerTag;
negativeSpacer.width = self.leftMargin - DefaultMargin;
[leftItems insertObject:negativeSpacer atIndex:0];
[self.topItem setLeftBarButtonItems:[leftItems copy] animated:NO];
}
//right
NSMutableArray *rightItems = [self.topItem.rightBarButtonItems mutableCopy];
if (((UIBarButtonItem *)rightItems.firstObject).tag != NegativeSpacerTag) {
UIBarButtonItem *negativeSpacer = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFixedSpace target:nil action:nil];
negativeSpacer.tag = NegativeSpacerTag;
negativeSpacer.width = self.rightMargin - DefaultMargin;
[rightItems insertObject:negativeSpacer atIndex:0];
[self.topItem setRightBarButtonItems:[rightItems copy] animated:NO];
}
}
}
- (CGFloat)leftMargin {
if (_leftMarginIsSet) {
return _leftMargin;
}
return DefaultMargin;
}
- (CGFloat)rightMargin {
if (_rightMarginIsSet) {
return _rightMargin;
}
return DefaultMargin;
}
- (void)setLeftMargin:(CGFloat)leftMargin {
_leftMargin = leftMargin;
_leftMarginIsSet = YES;
}
- (void)setRightMargin:(CGFloat)rightMargin {
_rightMargin = rightMargin;
_rightMarginIsSet = YES;
}
#end
After that I set custom class to my UINavigationController in Interface Builder and just set needed margins:
Screenshot 1
Works fine. Supports RTL and iOS prior 11:
Screenshot 2
Another way is that , you can wrapper your content to a offset view
class CustomBarItemView : UIView {
var offsetContentView : UIView = UIView.init(frame: .zero)
override init(frame: CGRect) {
super.init(frame: frame)
addSubview(offsetContentView)
offsetContentView.snp.makeConstraints { make in
make.edges.equalToSuperview().inset(UIEdgeInsets(top: 0, left: -8, bottom: 0, right: 0))
}
// implement add your content on offsetContentView
// todo ...
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
because CustomBarItemView layer not use maskToBounds = true,so it looks like OK
let naviItem = UIBarButtonItem.init(customView: CustomBarItemView())
For me this answer help https://stackoverflow.com/a/44896832
In particular i've set both imageEdgeInsets and titleEdgeInsets because my button has image and title together

adding loader at login button

I have a login button which when clicked should show a loader. Here's what I have tried.
- (void)viewDidLoad {
loadingView.backgroundColor = [UIColor darkGrayColor];
loadingView.layer.cornerRadius = 5;
loader=[[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
loader.frame = CGRectMake(13, 3, 80, 60);
[loader startAnimating];
loader.tag = 100;
[loadingView addSubview:loader];
UILabel* lblLoading = [[UILabel alloc]initWithFrame:CGRectMake(62, 15, 150, 30)];
lblLoading.text = #"Signing in...";
lblLoading.textColor = [UIColor whiteColor];
lblLoading.font = [UIFont fontWithName:lblLoading.font.fontName size:15];
lblLoading.textAlignment = NSTextAlignmentCenter;
[loadingView addSubview:lblLoading];
[self.view addSubview:loadingView];
loadingView.hidden = YES;
}
-(void)signinBtnPressed {
loadingView.hidden = NO;
//api code
}
But there is no loader loaded when utton is clicked.It shows after some time when the API is called
Can anyone tell me how to show the loader as soon as button is clicked or any other alternative.
I have an example of NVActivityIndicatorView. Please have a look.
NSObject class:
import UIKit
import NVActivityIndicatorView
class Helper: NSObject
{
class func createLoaderView(_ view : UIView) -> NVActivityIndicatorView
{
var ViewFrame : CGRect!
ViewFrame = CGRect(x: 0, y: 0, width: 60 , height: 60)
let center = CGPoint(x: (view.frame).midX, y: (view.frame).midY)
let activityIndicatorView = NVActivityIndicatorView(frame: ViewFrame, type: .ballSpinFadeLoader , color: UIColor(red: 200/255, green: 58/255, blue: 60/255, alpha:1.0), padding: CGFloat(0))
activityIndicatorView.center = center
activityIndicatorView.startAnimating()
return activityIndicatorView
}
class func removeLoaderView(_ activityIndicatorView : NVActivityIndicatorView)
{
activityIndicatorView.stopAnimating()
}
class func addBlurView(_ inView : UIView) -> UIVisualEffectView
{
let blurEffect = UIBlurEffect(style: UIBlurEffectStyle.dark)
let blurEffectView = UIVisualEffectView(effect: blurEffect)
//always fill the view
blurEffectView.frame = inView.bounds
blurEffectView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
blurEffectView.alpha = 0.5
return blurEffectView
}
}
In view controller where you want to show the loader call method of Helper class. Like:
import UIKit
import NVActivityIndicatorView
class ViewController: UIViewController,NVActivityIndicatorViewable
{
var activityIndicatorView : NVActivityIndicatorView!
var blurEffectView : UIVisualEffectView!
override func viewDidLoad()
{
super.viewDidLoad()
func showProgressView()
{
activityIndicatorView = Helper.createLoaderView(self.navigationController!.view)
blurEffectView = Helper.addBlurView((self.navigationController?.view)!)
self.navigationController!.view.addSubview(blurEffectView)
self.navigationController!.view.addSubview(activityIndicatorView)
}
func hideProgressView()
{
Helper.removeLoaderView(activityIndicatorView)
blurEffectView.removeFromSuperview()
}
}
}
Note: In order to show loader you have to call "showProgressView". Similarly to hide loader call hide method. To import ** NVActivityIndicatorView** you have to install pod 'NVActivityIndicatorView'.
As per your code you have not allocate loadingView
just allocate it and set frame or center like this way
loadingView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];
loadingView.center = self.view.center;
apurv, there is so many readymade external lib.s are available. But if you want your own customization, then can try this:
In AppDelegate
-(void)showActivityIndicatorWithTitle:(NSString *)title andUserInteraction:(BOOL)interaction
{
_hudView = [[UIView alloc] initWithFrame:CGRectMake((self.window.frame.size.width/2)-60, (self.window.frame.size.height/2)-60, 120, 120)];
_hudView.backgroundColor = [UIColor blackColor];
_hudView.alpha = 0.85f;
_hudView.clipsToBounds = YES;
_hudView.layer.cornerRadius = 10.0;
self.acivityIndicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
self.acivityIndicator.frame = CGRectMake(40, 40, self.acivityIndicator.bounds.size.width, self.acivityIndicator.bounds.size.height);
[_hudView addSubview:self.acivityIndicator];
[self.acivityIndicator startAnimating];
_captionLabel = [[UILabel alloc] initWithFrame:CGRectMake(5, 80, 110, 40)];
_captionLabel.backgroundColor = [UIColor clearColor];
_captionLabel.textColor = [UIColor whiteColor];
_captionLabel.adjustsFontSizeToFitWidth = YES;
_captionLabel.numberOfLines = 0;
_captionLabel.lineBreakMode = NSLineBreakByWordWrapping;
_captionLabel.textAlignment = NSTextAlignmentCenter;
_captionLabel.font = [UIFont systemFontOfSize:12.0];
_captionLabel.text = title;
[_hudView addSubview:_captionLabel];
self.window.userInteractionEnabled = interaction;
[self.window addSubview:_hudView];
}
-(void)removeIndicator
{
self.window.userInteractionEnabled = YES;
[self.acivityIndicator removeFromSuperview];
[_captionLabel removeFromSuperview];
[_hudView removeFromSuperview];
}
set the property of _hudView, self.acivityIndicator and _captionLabel in AppDelegate.h file and add these two methods in .h file also.
use these two methods to add and remove the loader throughout the project when you need. Don't forget to instantiate the app delegate.
Thanks
Everything looks fine, you should post all code related to this view. I've created playground and looks like it's OK:
At the same time, I see no loadingView creation and [super viewDidLoad] call.

Loading overlay not appearing while going to another viewcontroller

My overlay public class is:
public class LoadingOverlay{
var overlayView = UIView()
var activityIndicator = UIActivityIndicatorView()
class var shared: LoadingOverlay {
struct Static {
static let instance: LoadingOverlay = LoadingOverlay()
}
return Static.instance
}
public func showOverlay(view: UIView) {
overlayView.frame = CGRectMake(0, 0, 80, 80)
overlayView.center = view.center
overlayView.backgroundColor = UIColor(white: 0.5, alpha: 0.7)
overlayView.clipsToBounds = true
overlayView.layer.cornerRadius = 10
activityIndicator.frame = CGRectMake(0, 0, 40, 40)
activityIndicator.activityIndicatorViewStyle = .WhiteLarge
activityIndicator.center = CGPointMake(overlayView.bounds.width / 2, overlayView.bounds.height / 2)
overlayView.addSubview(activityIndicator)
view.addSubview(overlayView)
activityIndicator.startAnimating()
}
public func hideOverlayView() {
activityIndicator.stopAnimating()
overlayView.removeFromSuperview()
}
}//end of class
This works fine but when I try to show this on click of UITableViewCell which pushes the user to next viewcontroller and next ViewController gets data from a database, the overlay is not displayed.
I want to display this when I click one of the cells in UITableView and then stop it when the viewdiddisappear or till the time the other view controller did appear. Because it is a rough 2-3 second gap sometimes until it loads. Also the same thing i would like to use while transitioning of tab bars to other controllers within tab bars (Those other controllers too get data from a database).

iOS Floating Video Window like Youtube App

Does anyone know of any existing library, or any techniques on how to get the same effect as is found on the Youtube App.
The video can be "minimised" and hovers at the bottom of the screen - which can then be swiped to close or touched to re-maximised.
See:
Video Playing Normally: https://www.dropbox.com/s/o8c1ntfkkp4pc4q/2014-06-07%2001.19.20.png
Video Minimized: https://www.dropbox.com/s/w0syp3infu21g08/2014-06-07%2001.19.27.png
(Notice how the video is now in a small floating window on the bottom right of the screen).
Anyone have any idea how this was achieved, and if there are any existing tutorials or libraries that can be used to get this same effect?
It sounded fun, so I looked at youtube. The video looks like it plays in a 16:9 box at the top, with a "see also" list below. When user minimizes the video, the player drops to the lower right corner along with the "see also" view. At the same time, that "see also" view fades to transparent.
1) Setup the views like that and created outlets. Here's what it looks like in IB. (Note that the two containers are siblings)
2) Give the video view a swipe up and swipe down gesture recognizer:
#interface ViewController ()
#property (weak, nonatomic) IBOutlet UIView *tallMpContainer;
#property (weak, nonatomic) IBOutlet UIView *mpContainer;
#end
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
UISwipeGestureRecognizer *swipeDown = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:#selector(swipeDown:)];
UISwipeGestureRecognizer *swipeUp = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:#selector(swipeUp:)];
swipeUp.direction = UISwipeGestureRecognizerDirectionUp;
swipeDown.direction = UISwipeGestureRecognizerDirectionDown;
[self.mpContainer addGestureRecognizer:swipeUp];
[self.mpContainer addGestureRecognizer:swipeDown];
}
- (void)swipeDown:(UIGestureRecognizer *)gr {
[self minimizeMp:YES animated:YES];
}
- (void)swipeUp:(UIGestureRecognizer *)gr {
[self minimizeMp:NO animated:YES];
}
3) And then a method to know about the current state, and change the current state.
- (BOOL)mpIsMinimized {
return self.tallMpContainer.frame.origin.y > 0;
}
- (void)minimizeMp:(BOOL)minimized animated:(BOOL)animated {
if ([self mpIsMinimized] == minimized) return;
CGRect tallContainerFrame, containerFrame;
CGFloat tallContainerAlpha;
if (minimized) {
CGFloat mpWidth = 160;
CGFloat mpHeight = 90; // 160:90 == 16:9
CGFloat x = 320-mpWidth;
CGFloat y = self.view.bounds.size.height - mpHeight;
tallContainerFrame = CGRectMake(x, y, 320, self.view.bounds.size.height);
containerFrame = CGRectMake(x, y, mpWidth, mpHeight);
tallContainerAlpha = 0.0;
} else {
tallContainerFrame = self.view.bounds;
containerFrame = CGRectMake(0, 0, 320, 180);
tallContainerAlpha = 1.0;
}
NSTimeInterval duration = (animated)? 0.5 : 0.0;
[UIView animateWithDuration:duration animations:^{
self.tallMpContainer.frame = tallContainerFrame;
self.mpContainer.frame = containerFrame;
self.tallMpContainer.alpha = tallContainerAlpha;
}];
}
I didn't add video to this project, but it should just drop in. Make the mpContainer the parent view of the MPMoviePlayerController's view and it should look pretty cool.
Use TFSwipeShrink and customize code for your project.
hope to help you.
Update new framwork FWDraggableSwipePlayer for drag uiview like YouTube app.
hope to help you.
This is a swift 3 version for the answer #danh had provided earlier.
https://stackoverflow.com/a/24107949/1211470
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var tallMpContainer: UIView!
#IBOutlet weak var mpContainer: UIView!
var swipeDown: UISwipeGestureRecognizer?
var swipeUp: UISwipeGestureRecognizer?
override func viewDidLoad() {
super.viewDidLoad()
swipeDown = UISwipeGestureRecognizer(target: self, action: #selector(swipeDownAction))
swipeUp = UISwipeGestureRecognizer(target: self, action: #selector(swipeUpAction))
swipeDown?.direction = .down
swipeUp?.direction = .up
self.mpContainer.addGestureRecognizer(swipeDown!)
self.mpContainer.addGestureRecognizer(swipeUp!)
}
#objc func swipeDownAction() {
minimizeWindow(minimized: true, animated: true)
}
#objc func swipeUpAction() {
minimizeWindow(minimized: false, animated: true)
}
func isMinimized() -> Bool {
return CGFloat((self.tallMpContainer?.frame.origin.y)!) > CGFloat(20)
}
func minimizeWindow(minimized: Bool, animated: Bool) {
if isMinimized() == minimized {
return
}
var tallContainerFrame: CGRect
var containerFrame: CGRect
var tallContainerAlpha: CGFloat
if minimized == true {
let mpWidth: CGFloat = 160
let mpHeight: CGFloat = 90
let x: CGFloat = 320-mpWidth
let y: CGFloat = self.view.bounds.size.height - mpHeight;
tallContainerFrame = CGRect(x: x, y: y, width: 320, height: self.view.bounds.size.height)
containerFrame = CGRect(x: x, y: y, width: mpWidth, height: mpHeight)
tallContainerAlpha = 0.0
} else {
tallContainerFrame = self.view.bounds
containerFrame = CGRect(x: 0, y: 0, width: 320, height: 180)
tallContainerAlpha = 1.0
}
let duration: TimeInterval = (animated) ? 0.5 : 0.0
UIView.animate(withDuration: duration, animations: {
self.tallMpContainer.frame = tallContainerFrame
self.mpContainer.frame = containerFrame
self.tallMpContainer.alpha = tallContainerAlpha
})
}
}

Resources