[ios] Added circular progress view.

This commit is contained in:
Ilya Grechuhin 2015-07-13 14:14:14 +03:00 committed by Alex Zolotarev
parent 8d214a0abd
commit 9e34589b71
14 changed files with 362 additions and 0 deletions

View file

@ -0,0 +1,27 @@
//
// MWMCircularProgress.h
// Maps
//
// Created by Ilya Grechuhin on 10.07.15.
// Copyright (c) 2015 MapsWithMe. All rights reserved.
//
#import <Foundation/Foundation.h>
@class MWMCircularProgress;
@protocol MWMCircularProgressDelegate <NSObject>
- (void)progressButtonPressed:(nonnull MWMCircularProgress *)progress;
@end
@interface MWMCircularProgress : NSObject
@property (nonatomic) CGFloat progress;
@property (nonatomic) BOOL failed;
- (nonnull instancetype)init __attribute__((unavailable("init is not available")));
- (nonnull instancetype)initWithParentView:(nonnull UIView *)parentView delegate:(nonnull id <MWMCircularProgressDelegate>)delegate;
@end

View file

@ -0,0 +1,87 @@
//
// MWMCircularProgress.m
// Maps
//
// Created by Ilya Grechuhin on 10.07.15.
// Copyright (c) 2015 MapsWithMe. All rights reserved.
//
#import "MWMCircularProgress.h"
#import "MWMCircularProgressView.h"
@interface MWMCircularProgress ()
@property (nonatomic) IBOutlet MWMCircularProgressView * rootView;
@property (nonatomic) IBOutlet UIButton * button;
@property (nonatomic) NSNumber * nextProgressToAnimate;
@property (nonnull, weak, nonatomic) id <MWMCircularProgressDelegate> delegate;
@end
@implementation MWMCircularProgress
- (nonnull instancetype)initWithParentView:(nonnull UIView *)parentView delegate:(nonnull id <MWMCircularProgressDelegate>)delegate
{
self = [super init];
if (self)
{
[[NSBundle mainBundle] loadNibNamed:NSStringFromClass(self.class) owner:self options:nil];
[parentView addSubview:self.rootView];
self.delegate = delegate;
[self setup];
}
return self;
}
- (void)setup
{
self.nextProgressToAnimate = nil;
}
#pragma mark - Animation
- (void)animationDidStop:(CABasicAnimation *)anim finished:(BOOL)flag
{
if (self.nextProgressToAnimate)
self.progress = self.nextProgressToAnimate.doubleValue;
}
#pragma mark - Actions
- (IBAction)buttonTouchUpInside:(UIButton *)sender
{
[self.delegate progressButtonPressed:self];
}
#pragma mark - Properties
- (void)setProgress:(CGFloat)progress
{
if (self.rootView.animating)
{
self.nextProgressToAnimate = @(progress);
}
else
{
[self.rootView animateFromValue:_progress toValue:progress];
_progress = progress;
}
}
- (void)setFailed:(BOOL)failed
{
if (self.button.selected == failed)
return;
self.button.selected = failed;
[self.rootView refreshProgress];
[self.rootView updatePath:self.progress];
}
- (BOOL)failed
{
return self.button.selected;
}
@end

View file

@ -0,0 +1,49 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="7706" systemVersion="14E46" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES">
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="7703"/>
</dependencies>
<objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner" customClass="MWMCircularProgress">
<connections>
<outlet property="button" destination="viF-Ee-7h0" id="v7b-pM-V7X"/>
<outlet property="rootView" destination="2DE-Qh-89K" id="1DR-Lj-Wv9"/>
</connections>
</placeholder>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<view contentMode="scaleToFill" id="2DE-Qh-89K" customClass="MWMCircularProgressView">
<rect key="frame" x="0.0" y="0.0" width="32" height="32"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<subviews>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" adjustsImageWhenHighlighted="NO" adjustsImageWhenDisabled="NO" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="viF-Ee-7h0">
<rect key="frame" x="-6" y="-6" width="44" height="44"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
<state key="normal" image="ic_stop_spinner">
<color key="titleShadowColor" white="0.5" alpha="1" colorSpace="calibratedWhite"/>
</state>
<state key="selected" image="ic_download_continue"/>
<connections>
<action selector="buttonTouchUpInside:" destination="-1" eventType="touchUpInside" id="aPG-4e-uSw"/>
</connections>
</button>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
<constraints>
<constraint firstItem="viF-Ee-7h0" firstAttribute="top" secondItem="2DE-Qh-89K" secondAttribute="top" constant="-6" id="3gS-24-Olj"/>
<constraint firstAttribute="trailing" secondItem="viF-Ee-7h0" secondAttribute="trailing" constant="-6" id="fQT-G3-rJF"/>
<constraint firstAttribute="bottom" secondItem="viF-Ee-7h0" secondAttribute="bottom" constant="-6" id="j4s-7V-lg9"/>
<constraint firstItem="viF-Ee-7h0" firstAttribute="leading" secondItem="2DE-Qh-89K" secondAttribute="leading" constant="-6" id="sHS-iu-Url"/>
</constraints>
<nil key="simulatedStatusBarMetrics"/>
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
<connections>
<outlet property="owner" destination="-1" id="0n7-PI-5tO"/>
</connections>
</view>
</objects>
<resources>
<image name="ic_download_continue" width="32" height="32"/>
<image name="ic_stop_spinner" width="32" height="32"/>
</resources>
</document>

View file

@ -0,0 +1,23 @@
//
// MWMCircularProgressView.h
// Maps
//
// Created by Ilya Grechuhin on 11.07.15.
// Copyright (c) 2015 MapsWithMe. All rights reserved.
//
#import <UIKit/UIKit.h>
@interface MWMCircularProgressView : UIView
@property (nonatomic, readonly) BOOL animating;
- (nonnull instancetype)initWithFrame:(CGRect)frame __attribute__((unavailable("initWithFrame is not available")));
- (nonnull instancetype)init __attribute__((unavailable("init is not available")));
- (void)animateFromValue:(CGFloat)fromValue toValue:(CGFloat)toValue;
- (void)refreshProgress;
- (void)updatePath:(CGFloat)progress;
@end

View file

@ -0,0 +1,105 @@
//
// MWMCircularProgressView.m
// Maps
//
// Created by Ilya Grechuhin on 11.07.15.
// Copyright (c) 2015 MapsWithMe. All rights reserved.
//
#import "MWMCircularProgress.h"
#import "MWMCircularProgressView.h"
#import "UIColor+MapsMeColor.h"
#import "UIKitCategories.h"
static CGFloat const kLineWidth = 2.0;
static NSString * const kAnimationKey = @"CircleAnimation";
static inline CGFloat angleWithProgress(CGFloat progress)
{
return 2.0 * M_PI * progress - M_PI_2;
}
@interface MWMCircularProgressView ()
@property (nonatomic) CAShapeLayer * backgroundLayer;
@property (nonatomic) CAShapeLayer * progressLayer;
@property (weak, nonatomic) IBOutlet MWMCircularProgress * owner;
@end
@implementation MWMCircularProgressView
- (void)awakeFromNib
{
self.backgroundLayer = [CAShapeLayer layer];
[self refreshBackground];
[self.layer addSublayer:self.backgroundLayer];
self.progressLayer = [CAShapeLayer layer];
[self refreshProgress];
[self.layer addSublayer:self.progressLayer];
}
- (void)refreshBackground
{
self.backgroundLayer.fillColor = [UIColor clearColor].CGColor;
self.backgroundLayer.lineWidth = kLineWidth;
self.backgroundLayer.strokeColor = [UIColor pressBackground].CGColor;
CGRect rect = CGRectInset(self.bounds, kLineWidth, kLineWidth);
self.backgroundLayer.path = [UIBezierPath bezierPathWithOvalInRect:rect].CGPath;
}
- (void)refreshProgress
{
self.progressLayer.fillColor = [UIColor clearColor].CGColor;
self.progressLayer.lineWidth = kLineWidth;
self.progressLayer.strokeColor = self.owner.failed ? [UIColor red].CGColor : [UIColor primary].CGColor;;
}
- (void)updatePath:(CGFloat)progress
{
CGFloat const outerRadius = self.width / 2.0;
CGPoint const center = CGPointMake(outerRadius, outerRadius);
CGFloat const radius = outerRadius - kLineWidth;
UIBezierPath * path = [UIBezierPath bezierPathWithArcCenter:center radius:radius startAngle:angleWithProgress(0.0) endAngle:angleWithProgress(progress) clockwise:YES];
self.progressLayer.path = path.CGPath;
}
#pragma mark - Animation
- (void)animateFromValue:(CGFloat)fromValue toValue:(CGFloat)toValue
{
[self updatePath:toValue];
CABasicAnimation * animation = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];
animation.duration = 0.3;
animation.repeatCount = 1;
animation.fromValue = @(fromValue / toValue);
animation.toValue = @1;
animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
animation.delegate = self.owner;
[self.progressLayer addAnimation:animation forKey:kAnimationKey];
}
- (void)layoutSubviews
{
[super layoutSubviews];
self.frame = self.superview.bounds;
}
#pragma mark - Properties
- (void)setFrame:(CGRect)frame
{
BOOL const needRefreshBackground = !CGRectEqualToRect(self.frame, frame);
super.frame = frame;
if (needRefreshBackground)
[self refreshBackground];
}
- (BOOL)animating
{
return [self.progressLayer animationForKey:kAnimationKey] != nil;
}
@end

View file

@ -0,0 +1,23 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x",
"filename" : "ic_download_continue.png"
},
{
"idiom" : "universal",
"scale" : "2x",
"filename" : "ic_download_continue@2x.png"
},
{
"idiom" : "universal",
"scale" : "3x",
"filename" : "ic_download_continue@3x.png"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 366 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 663 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 758 B

View file

@ -0,0 +1,23 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x",
"filename" : "ic_stop_spinner.png"
},
{
"idiom" : "universal",
"scale" : "2x",
"filename" : "ic_stop_spinner@2x.png"
},
{
"idiom" : "universal",
"scale" : "3x",
"filename" : "ic_stop_spinner@3x.png"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 134 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 204 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 261 B

View file

@ -38,6 +38,9 @@
3472747B1B0F4FF100756B37 /* me.maps.production.entitlements in Resources */ = {isa = PBXBuildFile; fileRef = 3472747A1B0F4FF100756B37 /* me.maps.production.entitlements */; };
3472EC051B4D44BE0085CB79 /* UIFont+MapsMeFonts.mm in Sources */ = {isa = PBXBuildFile; fileRef = 3472EC041B4D44BE0085CB79 /* UIFont+MapsMeFonts.mm */; };
348E578E1B0F3752000FA02A /* CoreLocation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EEB7E22111E9079400080A68 /* CoreLocation.framework */; };
349A357A1B53D4C9009677EE /* MWMCircularProgress.m in Sources */ = {isa = PBXBuildFile; fileRef = 349A35761B53D4C9009677EE /* MWMCircularProgress.m */; };
349A357B1B53D4C9009677EE /* MWMCircularProgress.xib in Resources */ = {isa = PBXBuildFile; fileRef = 349A35771B53D4C9009677EE /* MWMCircularProgress.xib */; };
349A357C1B53D4C9009677EE /* MWMCircularProgressView.m in Sources */ = {isa = PBXBuildFile; fileRef = 349A35791B53D4C9009677EE /* MWMCircularProgressView.m */; };
34A742FE1AE5461A00CE15EB /* index.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 34A742FD1AE5461A00CE15EB /* index.cpp */; };
34A743001AE5468200CE15EB /* storage.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 34A742FF1AE5468200CE15EB /* storage.cpp */; };
34BC72211B0DECAE0012A34B /* MWMLocationButton.mm in Sources */ = {isa = PBXBuildFile; fileRef = 34BC720C1B0DECAE0012A34B /* MWMLocationButton.mm */; };
@ -377,6 +380,11 @@
348E57981B0F49D8000FA02A /* maps.me dbg.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "maps.me dbg.app"; sourceTree = BUILT_PRODUCTS_DIR; };
348E57C81B0F49EE000FA02A /* maps.me WatchKit Extension WatchKit Extension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "maps.me WatchKit Extension WatchKit Extension.appex"; sourceTree = BUILT_PRODUCTS_DIR; };
348E57D71B0F49EE000FA02A /* maps.me WatchKit App.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "maps.me WatchKit App.app"; sourceTree = BUILT_PRODUCTS_DIR; };
349A35751B53D4C9009677EE /* MWMCircularProgress.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MWMCircularProgress.h; sourceTree = "<group>"; };
349A35761B53D4C9009677EE /* MWMCircularProgress.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MWMCircularProgress.m; sourceTree = "<group>"; };
349A35771B53D4C9009677EE /* MWMCircularProgress.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = MWMCircularProgress.xib; sourceTree = "<group>"; };
349A35781B53D4C9009677EE /* MWMCircularProgressView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MWMCircularProgressView.h; sourceTree = "<group>"; };
349A35791B53D4C9009677EE /* MWMCircularProgressView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MWMCircularProgressView.m; sourceTree = "<group>"; };
34A742FD1AE5461A00CE15EB /* index.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = index.cpp; path = ../../../storage/index.cpp; sourceTree = "<group>"; };
34A742FF1AE5468200CE15EB /* storage.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = storage.cpp; path = ../../../storage/storage.cpp; sourceTree = "<group>"; };
34BC720B1B0DECAE0012A34B /* MWMLocationButton.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MWMLocationButton.h; sourceTree = "<group>"; };
@ -1052,6 +1060,19 @@
path = Entitlements;
sourceTree = "<group>";
};
349A35741B53D4C9009677EE /* CircularProgress */ = {
isa = PBXGroup;
children = (
349A35751B53D4C9009677EE /* MWMCircularProgress.h */,
349A35761B53D4C9009677EE /* MWMCircularProgress.m */,
349A35771B53D4C9009677EE /* MWMCircularProgress.xib */,
349A35781B53D4C9009677EE /* MWMCircularProgressView.h */,
349A35791B53D4C9009677EE /* MWMCircularProgressView.m */,
);
name = CircularProgress;
path = CustomViews/CircularProgress;
sourceTree = "<group>";
};
34BC72091B0DECAE0012A34B /* MapViewControls */ = {
isa = PBXGroup;
children = (
@ -1216,6 +1237,7 @@
97B4E9271851DAB300BEC5D7 /* Custom Views */ = {
isa = PBXGroup;
children = (
349A35741B53D4C9009677EE /* CircularProgress */,
34BC72091B0DECAE0012A34B /* MapViewControls */,
B0E1FCD61A23386D00A8E08B /* Route */,
97A8000918B210DC000C07A2 /* Search */,
@ -1975,6 +1997,7 @@
F7E7BA241672328F00B4492E /* bank.png in Resources */,
34BC72231B0DECAE0012A34B /* MWMLocationButton.xib in Resources */,
F7E7BA251672328F00B4492E /* bank@2x.png in Resources */,
349A357B1B53D4C9009677EE /* MWMCircularProgress.xib in Resources */,
F7E7BA261672328F00B4492E /* entertainment.png in Resources */,
F6CB21621AEE902B00FB8963 /* PlacePageLinkCell.xib in Resources */,
F7E7BA271672328F00B4492E /* entertainment@2x.png in Resources */,
@ -2150,9 +2173,11 @@
F653D4231AE9398700282659 /* MWMPlacePageViewManager.mm in Sources */,
F65243351B0B634F00BFA9D4 /* MWMPlacePage+Animation.mm in Sources */,
976D86F519CB21BD00C920EF /* RouteView.mm in Sources */,
349A357C1B53D4C9009677EE /* MWMCircularProgressView.m in Sources */,
FA29FDAA141E77F8004ADF66 /* Preferences.mm in Sources */,
F6588E371B15D87A00EE1E58 /* MWMBookmarkColorCell.mm in Sources */,
F6D409FA1B319BD70041730F /* ContextViews.mm in Sources */,
349A357A1B53D4C9009677EE /* MWMCircularProgress.m in Sources */,
97A8000C18B21363000C07A2 /* SearchView.mm in Sources */,
FAA5C2A2144F135F005337F6 /* LocationManager.mm in Sources */,
F66A8FAC1B09F137001B9C97 /* MWMiPadPlacePage.mm in Sources */,