Steven пре 2 година
родитељ
комит
03b3b81afd
20 измењених фајлова са 1637 додато и 34 уклоњено
  1. 34 0
      KulexiuForStudent/KulexiuForStudent.xcodeproj/project.pbxproj
  2. BIN
      KulexiuForStudent/KulexiuForStudent.xcworkspace/xcuserdata/wangzhi.xcuserdatad/UserInterfaceState.xcuserstate
  3. 22 0
      KulexiuForStudent/KulexiuForStudent/Assets.xcassets/Home/TuningImage/tuning_transfer.imageset/Contents.json
  4. BIN
      KulexiuForStudent/KulexiuForStudent/Assets.xcassets/Home/TuningImage/tuning_transfer.imageset/tuning_transfer@2x.png
  5. BIN
      KulexiuForStudent/KulexiuForStudent/Assets.xcassets/Home/TuningImage/tuning_transfer.imageset/tuning_transfer@3x.png
  6. 14 14
      KulexiuForStudent/KulexiuForStudent/Common/Define/PrefixHeader.pch
  7. 34 3
      KulexiuForStudent/KulexiuForStudent/Module/Widget/Controller/ToneTuningViewController.m
  8. 1 1
      KulexiuForStudent/KulexiuForStudent/Module/Widget/Model/TuningFunction/Tuner.swift
  9. 77 0
      KulexiuForStudent/KulexiuForStudent/Module/Widget/View/WMGaugeView/WMGaugeView.h
  10. 820 0
      KulexiuForStudent/KulexiuForStudent/Module/Widget/View/WMGaugeView/WMGaugeView.m
  11. 58 0
      KulexiuForStudent/KulexiuForStudent/Module/Widget/View/WMGaugeView/WMGaugeViewStyle.h
  12. 32 0
      KulexiuForStudent/KulexiuForStudent/Module/Widget/View/WMGaugeView/WMGaugeViewStyle3D.h
  13. 131 0
      KulexiuForStudent/KulexiuForStudent/Module/Widget/View/WMGaugeView/WMGaugeViewStyle3D.m
  14. 29 0
      KulexiuForStudent/KulexiuForStudent/Module/Widget/View/WMGaugeView/WMGaugeViewStyleFlatThin.h
  15. 128 0
      KulexiuForStudent/KulexiuForStudent/Module/Widget/View/WMGaugeView/WMGaugeViewStyleFlatThin.m
  16. 19 0
      KulexiuForStudent/KulexiuForStudent/Module/Widget/View/toneTuning/DialPlateView.h
  17. 65 0
      KulexiuForStudent/KulexiuForStudent/Module/Widget/View/toneTuning/DialPlateView.m
  18. 16 0
      KulexiuForStudent/KulexiuForStudent/Module/Widget/View/toneTuning/ToneTuningBodyView.h
  19. 12 10
      KulexiuForStudent/KulexiuForStudent/Module/Widget/View/toneTuning/ToneTuningBodyView.m
  20. 145 6
      KulexiuForStudent/KulexiuForStudent/Module/Widget/View/toneTuning/ToneTuningBodyView.xib

+ 34 - 0
KulexiuForStudent/KulexiuForStudent.xcodeproj/project.pbxproj

@@ -429,6 +429,7 @@
 		BC27A076280FF61300F91E27 /* AccompanyDetailBottomView.xib in Resources */ = {isa = PBXBuildFile; fileRef = BC27A075280FF61300F91E27 /* AccompanyDetailBottomView.xib */; };
 		BC27A079280FFA2200F91E27 /* EvaluateDetailModel.m in Sources */ = {isa = PBXBuildFile; fileRef = BC27A078280FFA2200F91E27 /* EvaluateDetailModel.m */; };
 		BC28582B2809036D0024697C /* StudentInfoModel.m in Sources */ = {isa = PBXBuildFile; fileRef = BC28582A2809036D0024697C /* StudentInfoModel.m */; };
+		BC2BE91C28F951DE00CB5F92 /* DialPlateView.m in Sources */ = {isa = PBXBuildFile; fileRef = BC2BE91B28F951DE00CB5F92 /* DialPlateView.m */; };
 		BC2DFF4B28BDFE740056105A /* HomeTeacherLiveModel.m in Sources */ = {isa = PBXBuildFile; fileRef = BC2DFF4A28BDFE730056105A /* HomeTeacherLiveModel.m */; };
 		BC2DFF4E28BE068D0056105A /* TeacherStyleFlowLayout.m in Sources */ = {isa = PBXBuildFile; fileRef = BC2DFF4D28BE068D0056105A /* TeacherStyleFlowLayout.m */; };
 		BC2DFF5728BE143A0056105A /* HomeTempLiveCell.m in Sources */ = {isa = PBXBuildFile; fileRef = BC2DFF5528BE143A0056105A /* HomeTempLiveCell.m */; };
@@ -871,6 +872,9 @@
 		BCD9294F28F8FCA4006793E4 /* AudioKit in Frameworks */ = {isa = PBXBuildFile; productRef = BCD9294E28F8FCA4006793E4 /* AudioKit */; };
 		BCD9295228F90202006793E4 /* ToneTuningBodyView.m in Sources */ = {isa = PBXBuildFile; fileRef = BCD9295128F90202006793E4 /* ToneTuningBodyView.m */; };
 		BCD9295428F90209006793E4 /* ToneTuningBodyView.xib in Resources */ = {isa = PBXBuildFile; fileRef = BCD9295328F90209006793E4 /* ToneTuningBodyView.xib */; };
+		BCD9295D28F9447B006793E4 /* WMGaugeViewStyleFlatThin.m in Sources */ = {isa = PBXBuildFile; fileRef = BCD9295828F9447B006793E4 /* WMGaugeViewStyleFlatThin.m */; };
+		BCD9295E28F9447B006793E4 /* WMGaugeViewStyle3D.m in Sources */ = {isa = PBXBuildFile; fileRef = BCD9295A28F9447B006793E4 /* WMGaugeViewStyle3D.m */; };
+		BCD9295F28F9447B006793E4 /* WMGaugeView.m in Sources */ = {isa = PBXBuildFile; fileRef = BCD9295B28F9447B006793E4 /* WMGaugeView.m */; };
 		BCD959C928DB071B00B70314 /* MusicTagView.m in Sources */ = {isa = PBXBuildFile; fileRef = BCD959C828DB071B00B70314 /* MusicTagView.m */; };
 		BCD959CC28DB0BAB00B70314 /* KSImageShareViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = BCD959CB28DB0BAB00B70314 /* KSImageShareViewController.m */; };
 		BCDE35862893B0E200A9A560 /* KSLoadingSuccessView.m in Sources */ = {isa = PBXBuildFile; fileRef = BCDE35852893B0E200A9A560 /* KSLoadingSuccessView.m */; };
@@ -1753,6 +1757,8 @@
 		BC27A078280FFA2200F91E27 /* EvaluateDetailModel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = EvaluateDetailModel.m; sourceTree = "<group>"; };
 		BC2858292809036C0024697C /* StudentInfoModel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = StudentInfoModel.h; sourceTree = "<group>"; };
 		BC28582A2809036D0024697C /* StudentInfoModel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = StudentInfoModel.m; sourceTree = "<group>"; };
+		BC2BE91A28F951DE00CB5F92 /* DialPlateView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DialPlateView.h; sourceTree = "<group>"; };
+		BC2BE91B28F951DE00CB5F92 /* DialPlateView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DialPlateView.m; sourceTree = "<group>"; };
 		BC2DFF4928BDFE730056105A /* HomeTeacherLiveModel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HomeTeacherLiveModel.h; sourceTree = "<group>"; };
 		BC2DFF4A28BDFE730056105A /* HomeTeacherLiveModel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HomeTeacherLiveModel.m; sourceTree = "<group>"; };
 		BC2DFF4C28BE068D0056105A /* TeacherStyleFlowLayout.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TeacherStyleFlowLayout.h; sourceTree = "<group>"; };
@@ -2444,6 +2450,13 @@
 		BCD9295028F90202006793E4 /* ToneTuningBodyView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ToneTuningBodyView.h; sourceTree = "<group>"; };
 		BCD9295128F90202006793E4 /* ToneTuningBodyView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ToneTuningBodyView.m; sourceTree = "<group>"; };
 		BCD9295328F90209006793E4 /* ToneTuningBodyView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ToneTuningBodyView.xib; sourceTree = "<group>"; };
+		BCD9295628F9447B006793E4 /* WMGaugeViewStyle3D.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WMGaugeViewStyle3D.h; sourceTree = "<group>"; };
+		BCD9295728F9447B006793E4 /* WMGaugeViewStyle.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WMGaugeViewStyle.h; sourceTree = "<group>"; };
+		BCD9295828F9447B006793E4 /* WMGaugeViewStyleFlatThin.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = WMGaugeViewStyleFlatThin.m; sourceTree = "<group>"; };
+		BCD9295928F9447B006793E4 /* WMGaugeView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WMGaugeView.h; sourceTree = "<group>"; };
+		BCD9295A28F9447B006793E4 /* WMGaugeViewStyle3D.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = WMGaugeViewStyle3D.m; sourceTree = "<group>"; };
+		BCD9295B28F9447B006793E4 /* WMGaugeView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = WMGaugeView.m; sourceTree = "<group>"; };
+		BCD9295C28F9447B006793E4 /* WMGaugeViewStyleFlatThin.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WMGaugeViewStyleFlatThin.h; sourceTree = "<group>"; };
 		BCD959C728DB071B00B70314 /* MusicTagView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MusicTagView.h; sourceTree = "<group>"; };
 		BCD959C828DB071B00B70314 /* MusicTagView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MusicTagView.m; sourceTree = "<group>"; };
 		BCD959CA28DB0BAB00B70314 /* KSImageShareViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KSImageShareViewController.h; sourceTree = "<group>"; };
@@ -4556,6 +4569,7 @@
 		BC4CF28B28D058FB00961C61 /* View */ = {
 			isa = PBXGroup;
 			children = (
+				BCD9295528F9447B006793E4 /* WMGaugeView */,
 				BCFEED7028F7F16C0078A2B7 /* toneTuning */,
 				BCFEED4E28F7E4910078A2B7 /* Metronome */,
 				BCFEED6828F7E4F40078A2B7 /* SmallToolBodyView.h */,
@@ -5985,6 +5999,20 @@
 			path = images;
 			sourceTree = "<group>";
 		};
+		BCD9295528F9447B006793E4 /* WMGaugeView */ = {
+			isa = PBXGroup;
+			children = (
+				BCD9295928F9447B006793E4 /* WMGaugeView.h */,
+				BCD9295B28F9447B006793E4 /* WMGaugeView.m */,
+				BCD9295728F9447B006793E4 /* WMGaugeViewStyle.h */,
+				BCD9295628F9447B006793E4 /* WMGaugeViewStyle3D.h */,
+				BCD9295A28F9447B006793E4 /* WMGaugeViewStyle3D.m */,
+				BCD9295C28F9447B006793E4 /* WMGaugeViewStyleFlatThin.h */,
+				BCD9295828F9447B006793E4 /* WMGaugeViewStyleFlatThin.m */,
+			);
+			path = WMGaugeView;
+			sourceTree = "<group>";
+		};
 		BCFDA62128BC94250022B497 /* HomeNav */ = {
 			isa = PBXGroup;
 			children = (
@@ -6119,6 +6147,8 @@
 				BCD9295028F90202006793E4 /* ToneTuningBodyView.h */,
 				BCD9295128F90202006793E4 /* ToneTuningBodyView.m */,
 				BCD9295328F90209006793E4 /* ToneTuningBodyView.xib */,
+				BC2BE91A28F951DE00CB5F92 /* DialPlateView.h */,
+				BC2BE91B28F951DE00CB5F92 /* DialPlateView.m */,
 			);
 			path = toneTuning;
 			sourceTree = "<group>";
@@ -6792,6 +6822,7 @@
 				BCB6355C27F6D2A300ACFDCF /* DisplayCommandMessage.m in Sources */,
 				BCB6356A27F6D2A300ACFDCF /* AccompanyDownloadCallbackMessage.m in Sources */,
 				BCB6359827F6D2AB00ACFDCF /* KSTipsView.m in Sources */,
+				BCD9295F28F9447B006793E4 /* WMGaugeView.m in Sources */,
 				2779354C27E324A70010E277 /* KSGifRefreshHeader.m in Sources */,
 				2779358727E324A80010E277 /* LLCollectionViewCell.m in Sources */,
 				2779352627E324A60010E277 /* UIAlertController+Extend.m in Sources */,
@@ -6873,6 +6904,7 @@
 				BCFE53EC2812897600AD6786 /* HomeLiveCouseCell.m in Sources */,
 				277935A527E324A80010E277 /* MSSBrowseLoadingImageView.m in Sources */,
 				2779353E27E324A60010E277 /* UIDevice+TFDevice.m in Sources */,
+				BC2BE91C28F951DE00CB5F92 /* DialPlateView.m in Sources */,
 				2723B5C027F157B100E0B90B /* ChatAddressHeaderView.m in Sources */,
 				2723B68127F15D3D00E0B90B /* ModifyNameViewController.m in Sources */,
 				2779359927E324A80010E277 /* NSBundle+TZImagePicker.m in Sources */,
@@ -6902,6 +6934,7 @@
 				BC3A4EB428DAE202001C4428 /* UIImage+KSScreenShot.m in Sources */,
 				BC11927A280FB07F00A716F7 /* AccompanyArrangeCell.m in Sources */,
 				277935B827E324A90010E277 /* FSCalendar.m in Sources */,
+				BCD9295E28F9447B006793E4 /* WMGaugeViewStyle3D.m in Sources */,
 				BCB6356C27F6D2A300ACFDCF /* Whiteboard.m in Sources */,
 				275FA23527E7356B00CFEA2E /* UserInfoManager.m in Sources */,
 				BC4CF26A28D025E200961C61 /* HomeQualityMusic.m in Sources */,
@@ -7121,6 +7154,7 @@
 				277935BB27E324A90010E277 /* FSCalendarSeparatorDecorationView.m in Sources */,
 				BC8A45A9283DC33400094BBB /* KSCloudBeatView.m in Sources */,
 				275FA1DF27E7351900CFEA2E /* RCConnectionManager.m in Sources */,
+				BCD9295D28F9447B006793E4 /* WMGaugeViewStyleFlatThin.m in Sources */,
 				275FA22B27E7356B00CFEA2E /* HomeViewController.m in Sources */,
 				BCFE53F12812898700AD6786 /* HomeVideoCourseCell.m in Sources */,
 				BCB908F02850B08D00F5FF69 /* KSChatLiveShareCell.m in Sources */,

BIN
KulexiuForStudent/KulexiuForStudent.xcworkspace/xcuserdata/wangzhi.xcuserdatad/UserInterfaceState.xcuserstate


+ 22 - 0
KulexiuForStudent/KulexiuForStudent/Assets.xcassets/Home/TuningImage/tuning_transfer.imageset/Contents.json

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

BIN
KulexiuForStudent/KulexiuForStudent/Assets.xcassets/Home/TuningImage/tuning_transfer.imageset/tuning_transfer@2x.png


BIN
KulexiuForStudent/KulexiuForStudent/Assets.xcassets/Home/TuningImage/tuning_transfer.imageset/tuning_transfer@3x.png


+ 14 - 14
KulexiuForStudent/KulexiuForStudent/Common/Define/PrefixHeader.pch

@@ -139,23 +139,23 @@ shouldPrevent = NO; \
 
 //#ifdef DEBUG
 
-//#define hostURL (@"https://dev.colexiu.com")
-//#define SEALCLASSHOST (@"https://dev.colexiu.com/api-classroom")
-//#define WEBHOST (@"https://dev.colexiu.com/student")
-//#define SOCKET_URL (@"wss://dev.colexiu.com/audioAnalysis")
-//#define JSPUSH_ENVIRONMENT (NO)
-//#define RCIM_KEY (@"0vnjpoad0jbdz")
-//#define SUBMIT_UUID (YES)
+#define hostURL (@"https://dev.colexiu.com")
+#define SEALCLASSHOST (@"https://dev.colexiu.com/api-classroom")
+#define WEBHOST (@"https://dev.colexiu.com/student")
+#define SOCKET_URL (@"wss://dev.colexiu.com/audioAnalysis")
+#define JSPUSH_ENVIRONMENT (NO)
+#define RCIM_KEY (@"0vnjpoad0jbdz")
+#define SUBMIT_UUID (YES)
 
 // 预生产环境
 
-#define hostURL (@"https://ponline.colexiu.com")
-#define SEALCLASSHOST (@"https://ponline.colexiu.com/api-classroom")
-#define WEBHOST (@"https://ponline.colexiu.com/student")
-#define SOCKET_URL (@"wss://ponline.colexiu.com/audioAnalysis")
-#define JSPUSH_ENVIRONMENT (YES)
-#define RCIM_KEY (@"e5t4ouvpe42pa")
-#define SUBMIT_UUID (YES)
+//#define hostURL (@"https://ponline.colexiu.com")
+//#define SEALCLASSHOST (@"https://ponline.colexiu.com/api-classroom")
+//#define WEBHOST (@"https://ponline.colexiu.com/student")
+//#define SOCKET_URL (@"wss://ponline.colexiu.com/audioAnalysis")
+//#define JSPUSH_ENVIRONMENT (YES)
+//#define RCIM_KEY (@"e5t4ouvpe42pa")
+//#define SUBMIT_UUID (YES)
 
 //#else
 

+ 34 - 3
KulexiuForStudent/KulexiuForStudent/Module/Widget/Controller/ToneTuningViewController.m

@@ -9,6 +9,7 @@
 #import "TuningNavView.h"
 #import "KulexiuForStudent-swift.h"
 #import "ToneTuningBodyView.h"
+#import "DialPlateView.h"
 
 @interface ToneTuningViewController ()<TunerDelegate>
 
@@ -21,6 +22,10 @@
 
 @property (nonatomic, strong) ToneTuningBodyView *bodyView;
 
+@property (nonatomic, strong) DialPlateView *plateView;
+
+@property (nonatomic, assign) BOOL isTransfer;
+
 @end
 
 @implementation ToneTuningViewController
@@ -56,10 +61,21 @@
         make.top.mas_equalTo(self.navView.mas_bottom);
         make.bottom.mas_equalTo(self.view.mas_bottom).offset(-iPhoneXSafeBottomMargin);
     }];
+    [self.bodyView.plateView addSubview:self.plateView];
+    [self.plateView mas_makeConstraints:^(MASConstraintMaker *make) {
+        make.top.bottom.left.right.mas_equalTo(self.bodyView.plateView);
+    }];
 }
 
+- (void)viewDidAppear:(BOOL)animated {
+    [super viewDidAppear:animated];
+    [self startTuner];
+}
 
-
+- (void)viewWillDisappear:(BOOL)animated {
+    [super viewWillDisappear:animated];
+    [self stopTuner];
+}
 
 - (void)clickAction:(UIButton *)sender {
     if (self.isRuning) {
@@ -129,9 +145,17 @@
         
     }
     else {
-        
+//        self.plateView.gaugeView.value = output.distance;
+        [self.plateView.gaugeView setValue:output.distance animated:YES];
+        if (self.isTransfer == NO) {
+            self.bodyView.nomalPitch.text = output.pitch;
+            self.bodyView.pitchFrequenceLabel.text = [NSString stringWithFormat:@"%@%zd:%.0fHz", output.pitch, output.octave, output.frequency];
+        }
+        else {
+            
+        }
     }
-    NSLog(@"-------- %@%zd --- distance :%f" , output.pitch, output.octave, output.distance);
+    NSLog(@"-------- %@%zd --- distance :%f frequence : %f" , output.pitch, output.octave, output.distance, output.frequency);
     
 }
 
@@ -144,6 +168,13 @@
     }
     return _bodyView;
 }
+
+- (DialPlateView *)plateView {
+    if (!_plateView) {
+        _plateView = [[DialPlateView alloc] init];
+    }
+    return _plateView;
+}
 /*
 #pragma mark - Navigation
 

+ 1 - 1
KulexiuForStudent/KulexiuForStudent/Module/Widget/Model/TuningFunction/Tuner.swift

@@ -125,7 +125,7 @@ private let frequencies: [Float] = [
          8192 (20次/秒)
          4096 (40次/秒)  def
          */
-        pitchTap = PitchTap(microphone!, bufferSize: 4096, handler: tap_handler)
+        pitchTap = PitchTap(microphone!, bufferSize: 16384, handler: tap_handler)
         microphone!.start()
         pitchTap!.start()
         engine!.output = silence

+ 77 - 0
KulexiuForStudent/KulexiuForStudent/Module/Widget/View/WMGaugeView/WMGaugeView.h

@@ -0,0 +1,77 @@
+/*
+ * WMGaugeView.h
+ *
+ * Copyright (C) 2014 William Markezana <william.markezana@me.com>
+ *
+ */
+
+#import <UIKit/UIKit.h>
+#import "WMGaugeViewStyle.h"
+#import "WMGaugeViewStyleFlatThin.h"
+#import "WMGaugeViewStyle3D.h"
+
+/**
+ * Styling enumerations
+ */
+typedef NS_ENUM(NSUInteger, WMGaugeViewSubdivisionsAlignment) {
+    WMGaugeViewSubdivisionsAlignmentTop,
+    WMGaugeViewSubdivisionsAlignmentCenter,
+    WMGaugeViewSubdivisionsAlignmentBottom
+};
+
+/**
+ * WMGaugeView class
+ */
+@interface WMGaugeView : UIView
+
+/**
+ * WMGaugeView properties
+ */
+@property (nonatomic, readwrite, assign) bool showInnerBackground;
+@property (nonatomic, readwrite, assign) bool showInnerRim;
+@property (nonatomic, readwrite, assign) CGFloat innerRimWidth;
+@property (nonatomic, readwrite, assign) CGFloat innerRimBorderWidth;
+@property (nonatomic, readwrite, assign) CGFloat scalePosition;
+@property (nonatomic, readwrite, assign) CGFloat scaleStartAngle;
+@property (nonatomic, readwrite, assign) CGFloat scaleEndAngle;
+@property (nonatomic, readwrite, assign) CGFloat scaleDivisions;
+@property (nonatomic, readwrite, assign) CGFloat scaleSubdivisions;
+@property (nonatomic, readwrite, assign) bool showScaleShadow;
+@property (nonatomic, readwrite, assign) bool showScale;
+@property (nonatomic, readwrite, assign) bool showScaleValues;
+@property (nonatomic, readwrite, assign) WMGaugeViewSubdivisionsAlignment scalesubdivisionsAligment;
+@property (nonatomic, readwrite, assign) CGFloat scaleDivisionsLength;
+@property (nonatomic, readwrite, assign) CGFloat scaleDivisionsWidth;
+@property (nonatomic, readwrite, assign) CGFloat scaleSubdivisionsLength;
+@property (nonatomic, readwrite, assign) CGFloat scaleSubdivisionsWidth;
+@property (nonatomic, readwrite, strong) UIColor *scaleDivisionColor;
+@property (nonatomic, readwrite, strong) UIColor *scaleSubDivisionColor;
+@property (nonatomic, readwrite, strong) UIFont *scaleFont;
+@property (nonatomic, readwrite, assign) float value;
+@property (nonatomic, readwrite, assign) float minValue;
+@property (nonatomic, readwrite, assign) float maxValue;
+@property (nonatomic, readwrite, assign) bool showRangeLabels;
+@property (nonatomic, readwrite, assign) CGFloat rangeLabelsWidth;
+@property (nonatomic, readwrite, strong) UIFont *rangeLabelsFont;
+@property (nonatomic, readwrite, assign) BOOL adjustRangeLabelSizeToFitWidth;
+@property (nonatomic, readwrite, strong) UIColor *rangeLabelsFontColor;
+@property (nonatomic, readwrite, assign) CGFloat rangeLabelsFontKerning;
+@property (nonatomic, readwrite, strong) NSArray<NSNumber*> *rangeValues;
+@property (nonatomic, readwrite, strong) NSArray<UIColor*> *rangeColors;
+@property (nonatomic, readwrite, strong) NSArray<NSString*> *rangeLabels;
+@property (nonatomic, readwrite, strong) UIColor *unitOfMeasurementColor;
+@property (nonatomic, readwrite, assign) CGFloat unitOfMeasurementVerticalOffset;
+@property (nonatomic, readwrite, strong) UIFont *unitOfMeasurementFont;
+@property (nonatomic, readwrite, strong) NSString *unitOfMeasurement;
+@property (nonatomic, readwrite, assign) bool showUnitOfMeasurement;
+@property (nonatomic, readwrite, strong) id<WMGaugeViewStyle> style;
+
+/**
+ * WMGaugeView public functions
+ */
+- (void)setValue:(float)value animated:(BOOL)animated;
+- (void)setValue:(float)value animated:(BOOL)animated completion:(void (^)(BOOL finished))completion;
+- (void)setValue:(float)value animated:(BOOL)animated duration:(NSTimeInterval)duration;
+- (void)setValue:(float)value animated:(BOOL)animated duration:(NSTimeInterval)duration completion:(void (^)(BOOL finished))completion;
+
+@end

+ 820 - 0
KulexiuForStudent/KulexiuForStudent/Module/Widget/View/WMGaugeView/WMGaugeView.m

@@ -0,0 +1,820 @@
+/*
+ * WMGaugeView.h
+ *
+ * Copyright (C) 2014 William Markezana <william.markezana@me.com>
+ *
+ */
+
+#import "WMGaugeView.h"
+
+/* Scale conversion macro from [0-1] range to view  real size range */
+#define FULL_SCALE(x,y)    (x)*self.bounds.size.width, (y)*self.bounds.size.height
+
+@implementation WMGaugeView
+{
+    /* Drawing rects */
+    CGRect fullRect;
+    CGRect innerRimRect;
+    CGRect innerRimBorderRect;
+    CGRect faceRect;
+    CGRect rangeLabelsRect;
+    CGRect scaleRect;
+
+    /* View center */
+    CGPoint center;
+
+    /* Scale variables */
+    CGFloat scaleRotation;    
+    CGFloat divisionValue;
+    CGFloat subdivisionValue;
+    CGFloat subdivisionAngle;
+    
+    /* Background image */
+    UIImage *background;
+    
+    /* Needle layer */
+    CALayer *rootNeedleLayer;
+
+    /* Annimation completion */
+    void (^animationCompletion)(BOOL);
+}
+
+#pragma mark - Initialization
+
+- (id)initWithFrame:(CGRect)frame
+{
+    self = [super initWithFrame:frame];
+    if (self)
+    {
+        [self initialize];
+    }
+    return self;
+}
+
+- (void)awakeFromNib
+{
+    [super awakeFromNib];
+    [self initialize];
+}
+
+/**
+ *  Set all properties to default values
+ *  Set all private variables to default values
+ */
+- (void)initialize;
+{
+    _style = nil;
+    _showInnerRim = NO;
+    _showInnerBackground = YES;
+    _innerRimWidth = 0.05;
+    _innerRimBorderWidth = 0.005;
+    
+    _scalePosition = 0.025;
+    _scaleStartAngle = 30.0;
+    _scaleEndAngle = 330.0;
+    _scaleDivisions = 12.0;
+    _scaleSubdivisions = 10.0;
+    _showScale = YES;
+    _showScaleShadow = YES;
+    _showScaleValues = YES;
+    _scalesubdivisionsAligment = WMGaugeViewSubdivisionsAlignmentTop;
+    _scaleDivisionsLength = 0.045;
+    _scaleDivisionsWidth = 0.01;
+    _scaleSubdivisionsLength = 0.015;
+    _scaleSubdivisionsWidth = 0.01;
+    
+    _value = 0.0;
+    _minValue = 0.0;
+    _maxValue = 240.0;
+
+    background = nil;
+    
+    _showRangeLabels = NO;
+    _rangeLabelsWidth = 0.05;
+    _rangeLabelsFont = [UIFont fontWithName:@"Helvetica" size:0.05];
+    _adjustRangeLabelSizeToFitWidth = NO;
+    _rangeLabelsFontColor = [UIColor whiteColor];
+    _rangeLabelsFontKerning = 1.0;
+    _rangeValues = nil;
+    _rangeColors = nil;
+    _rangeLabels = nil;
+    
+    _scaleDivisionColor = RGB(68, 84, 105);
+    _scaleSubDivisionColor = RGB(217, 217, 217);
+    
+    _scaleFont = nil;
+    
+    _unitOfMeasurementVerticalOffset = 0.6;
+    _unitOfMeasurementColor = [UIColor whiteColor];
+    _unitOfMeasurementFont = [UIFont fontWithName:@"Helvetica" size:0.04];
+    _unitOfMeasurement = @"";
+    _showUnitOfMeasurement = NO;
+    
+    animationCompletion = nil;
+
+    [self initDrawingRects];
+    [self initScale];
+}
+
+/**
+ *  Initialize all drawing rects and center point
+ */
+- (void)initDrawingRects
+{
+    center = CGPointMake(0.5, 0.5);
+    fullRect = CGRectMake(0.0, 0.0, 1.0, 1.0);
+    
+    _innerRimBorderWidth = _showInnerRim ? _innerRimBorderWidth : 0.0;
+    _innerRimWidth = _showInnerRim ? _innerRimWidth : 0.0;
+    
+    innerRimRect = fullRect;
+    innerRimBorderRect = CGRectMake(innerRimRect.origin.x + _innerRimBorderWidth,
+                                    innerRimRect.origin.y + _innerRimBorderWidth,
+                                    innerRimRect.size.width - 2 * _innerRimBorderWidth,
+                                    innerRimRect.size.height - 2 * _innerRimBorderWidth);
+    faceRect = CGRectMake(innerRimRect.origin.x + _innerRimWidth,
+                          innerRimRect.origin.y + _innerRimWidth,
+                          innerRimRect.size.width - 2 * _innerRimWidth,
+                          innerRimRect.size.height - 2 * _innerRimWidth);
+    rangeLabelsRect = CGRectMake(faceRect.origin.x + (_showRangeLabels ? _rangeLabelsWidth : 0.0),
+                                 faceRect.origin.y + (_showRangeLabels ? _rangeLabelsWidth : 0.0),
+                                 faceRect.size.width - 2 * (_showRangeLabels ? _rangeLabelsWidth : 0.0),
+                                 faceRect.size.height - 2 * (_showRangeLabels ? _rangeLabelsWidth : 0.0));
+    scaleRect = CGRectMake(rangeLabelsRect.origin.x + _scalePosition,
+                           rangeLabelsRect.origin.y + _scalePosition,
+                           rangeLabelsRect.size.width - 2 * _scalePosition,
+                           rangeLabelsRect.size.height - 2 * _scalePosition);
+}
+
+#pragma mark - Drawing
+
+/**
+ * Main drawing entry point 
+ */
+- (void)drawRect:(CGRect)rect
+{
+    if (background == nil)
+    {
+        // Create image context
+        UIGraphicsBeginImageContextWithOptions(rect.size, NO, [UIScreen mainScreen].scale);
+        CGContextRef context = UIGraphicsGetCurrentContext();
+        
+        // Scale context for [0-1] drawings
+        CGContextScaleCTM(context, rect.size.width , rect.size.height);
+
+        // Draw gauge background in image context
+        [self drawGauge:context];
+        
+        // Save background
+        background = UIGraphicsGetImageFromCurrentImageContext();
+        UIGraphicsEndImageContext();
+    }
+    
+    // Drawing background in view
+    [background drawInRect:rect];
+    
+    if (rootNeedleLayer == nil)
+    {
+        // Initialize needle layer
+        rootNeedleLayer = [CALayer new];
+
+        // For performance puporse, the needle layer is not scaled to [0-1] range
+        rootNeedleLayer.frame = self.bounds;
+        [self.layer addSublayer:rootNeedleLayer];
+        
+        // Draw needle
+        [self drawNeedle];
+        
+        // Set needle current value
+        [self setValue:_value animated:NO];
+    }    
+}
+
+/**
+ *  Gauge background drawing
+ */
+- (void)drawGauge:(CGContextRef)context
+{
+    [self drawRim:context];
+
+    if (_showInnerBackground)
+        [self drawFace:context];
+
+    if (_showUnitOfMeasurement)
+        [self drawText:context];
+
+    if (_showScale)
+        [self drawScale:context];
+
+    if (_showRangeLabels)
+        [self drawRangeLabels:context];
+}
+
+/**
+ *  Gauge external rim drawing
+ */
+- (void)drawRim:(CGContextRef)context
+{
+    // TODO
+}
+
+/**
+ *  Gauge inner background drawing
+ */
+- (void)drawFace:(CGContextRef)context
+{
+    if ([_style conformsToProtocol:@protocol(WMGaugeViewStyle)]) {
+        [_style drawFaceWithContext:context inRect:faceRect];
+    }
+}
+
+/**
+ *  Unit of measurement drawing
+ */
+- (void)drawText:(CGContextRef)context
+{
+    CGContextSetShadow(context, CGSizeMake(0.05, 0.05), 2.0);
+    UIFont* font = _unitOfMeasurementFont ? _unitOfMeasurementFont : [UIFont fontWithName:@"Helvetica" size:0.04];
+    UIColor* color = _unitOfMeasurementColor ? _unitOfMeasurementColor : [UIColor whiteColor];
+    NSDictionary* stringAttrs = @{ NSFontAttributeName : font, NSForegroundColorAttributeName : color };
+    NSAttributedString* attrStr = [[NSAttributedString alloc] initWithString:_unitOfMeasurement attributes:stringAttrs];
+    CGSize fontWidth;
+    fontWidth = [_unitOfMeasurement sizeWithAttributes:stringAttrs];
+
+    [attrStr drawAtPoint:CGPointMake(0.5 - fontWidth.width / 2.0, _unitOfMeasurementVerticalOffset)];
+}
+
+/**
+ * Scale drawing 
+ */
+- (void)drawScale:(CGContextRef)context
+{
+    CGContextSaveGState(context);
+    [self rotateContext:context fromCenter:center withAngle:DEGREES_TO_RADIANS(180 + _scaleStartAngle)];
+    
+    int totalTicks = _scaleDivisions * _scaleSubdivisions + 1;
+    for (int i = 0; i < totalTicks; i++)
+    {
+        CGFloat offset = 0.0;
+        if (_scalesubdivisionsAligment == WMGaugeViewSubdivisionsAlignmentCenter) offset = (_scaleDivisionsLength - _scaleSubdivisionsLength) / 2.0;
+        if (_scalesubdivisionsAligment == WMGaugeViewSubdivisionsAlignmentBottom) offset = _scaleDivisionsLength - _scaleSubdivisionsLength;
+        
+        CGFloat y1 = scaleRect.origin.y;
+        CGFloat y2 = y1 + _scaleSubdivisionsLength;
+        CGFloat y3 = y1 + _scaleDivisionsLength;
+        
+        float value = [self valueForTick:i];
+        float div = (_maxValue - _minValue) / _scaleDivisions;
+        float mod = (int)value % (int)div;
+        
+        // Division
+        if ((fabsf(mod - 0) < 0.000001) || (fabsf(mod - div) < 0.000001))
+        {
+            // Initialize Core Graphics settings
+            UIColor *color = (_rangeValues && _rangeColors) ? [self rangeColorForValue:value] : _scaleDivisionColor;
+            CGContextSetStrokeColorWithColor(context, color.CGColor);
+            CGContextSetLineWidth(context, _scaleDivisionsWidth);
+            CGContextSetShadow(context, CGSizeMake(0.05, 0.05), _showScaleShadow ? 2.0 : 0.0);
+            
+            // Draw tick
+            CGContextMoveToPoint(context, 0.5, y1);
+            CGContextAddLineToPoint(context, 0.5, y3);
+            CGContextStrokePath(context);
+            
+            // Draw label
+            if(_showScaleValues) {
+                NSString *valueString = [NSString stringWithFormat:@"%0.0f",value];
+                UIFont* font = _scaleFont ? _scaleFont : [UIFont fontWithName:@"Helvetica-Bold" size:0.05];
+                NSDictionary* stringAttrs = @{ NSFontAttributeName : font, NSForegroundColorAttributeName : color };
+                NSAttributedString* attrStr = [[NSAttributedString alloc] initWithString:valueString attributes:stringAttrs];
+                CGSize fontWidth;
+                fontWidth = [valueString sizeWithAttributes:stringAttrs];
+                
+                [attrStr drawAtPoint:CGPointMake(0.5 - fontWidth.width / 2.0, y3 + 0.005)];
+            }
+        }
+        // Subdivision
+        else
+        {
+            // Initialize Core Graphics settings
+            UIColor *color = (_rangeValues && _rangeColors) ? [self rangeColorForValue:value] : _scaleSubDivisionColor;
+            CGContextSetStrokeColorWithColor(context, color.CGColor);
+            CGContextSetLineWidth(context, _scaleSubdivisionsWidth);
+            CGContextMoveToPoint(context, 0.5, y1);
+            if (_showScaleShadow) CGContextSetShadow(context, CGSizeMake(0.05, 0.05), 2.0);
+            
+            // Draw tick
+            CGContextMoveToPoint(context, 0.5, y1 + offset);
+            CGContextAddLineToPoint(context, 0.5, y2 + offset);
+            CGContextStrokePath(context);
+        }
+        
+        // Rotate to next tick
+        [self rotateContext:context fromCenter:center withAngle:DEGREES_TO_RADIANS(subdivisionAngle)];
+    }
+    CGContextRestoreGState(context);
+}
+
+/**
+ * scale range labels drawing 
+ */
+- (void)drawRangeLabels:(CGContextRef)context
+{
+    CGContextSaveGState(context);
+    [self rotateContext:context fromCenter:center withAngle:DEGREES_TO_RADIANS(90 + _scaleStartAngle)];
+    CGContextSetShadow(context, CGSizeMake(0.0, 0.0), 0.0);
+    
+    CGFloat maxAngle = _scaleEndAngle - _scaleStartAngle;
+    CGFloat lastStartAngle = 0.0f;
+
+    for (int i = 0; i < _rangeValues.count; i ++)
+    {
+        // Range value
+        float value = ((NSNumber*)[_rangeValues objectAtIndex:i]).floatValue;
+        float valueAngle = (value - _minValue) / (_maxValue - _minValue) * maxAngle;
+        
+        // Range curved shape
+        UIBezierPath *path = [UIBezierPath bezierPath];
+        [path addArcWithCenter:center radius:rangeLabelsRect.size.width / 2.0 startAngle:DEGREES_TO_RADIANS(lastStartAngle) endAngle:DEGREES_TO_RADIANS(valueAngle) - 0.01 clockwise:YES];
+        
+        UIColor *color = _rangeColors[i];
+        [color setStroke];
+        path.lineWidth = _rangeLabelsWidth;
+        [path stroke];
+        
+        // Range curved label
+        [self drawStringAtContext:context string:(NSString*)_rangeLabels[i] withCenter:center radius:rangeLabelsRect.size.width / 2.0 startAngle:DEGREES_TO_RADIANS(lastStartAngle) endAngle:DEGREES_TO_RADIANS(valueAngle)];
+        
+        lastStartAngle = valueAngle;
+    }
+    
+    CGContextRestoreGState(context);
+}
+
+/**
+ * Needle drawing 
+ */
+- (void)drawNeedle
+{
+    if ([_style conformsToProtocol:@protocol(WMGaugeViewStyle)]) {
+        [_style drawNeedleOnLayer:rootNeedleLayer inRect:self.bounds];
+    }
+}
+
+#pragma mark - Tools
+
+/**
+ * Core Graphics rotation in context
+ */
+- (void)rotateContext:(CGContextRef)context fromCenter:(CGPoint)center_ withAngle:(CGFloat)angle
+{
+    CGContextTranslateCTM(context, center_.x, center_.y);
+    CGContextRotateCTM(context, angle);
+    CGContextTranslateCTM(context, -center_.x, -center_.y);
+}
+
+/**
+ * Needle angle computation
+ */
+- (CGFloat)needleAngleForValue:(double)value
+{
+    return DEGREES_TO_RADIANS(_scaleStartAngle + (value - _minValue) / (_maxValue - _minValue) * (_scaleEndAngle - _scaleStartAngle)) + M_PI;
+}
+
+/**
+ * Initialize scale helper values
+ */
+- (void)initScale
+{
+    scaleRotation = (int)(_scaleStartAngle + 180) % 360;
+    divisionValue = (_maxValue - _minValue) / _scaleDivisions;
+    subdivisionValue = divisionValue / _scaleSubdivisions;
+    subdivisionAngle = (_scaleEndAngle - _scaleStartAngle) / (_scaleDivisions * _scaleSubdivisions);
+}
+
+/**
+ * Scale tick value computation
+ */
+- (float)valueForTick:(int)tick
+{
+    return tick * (divisionValue / _scaleSubdivisions) + _minValue;
+}
+
+/**
+ * Scale range label color for value
+ */
+- (UIColor*)rangeColorForValue:(float)value
+{
+    NSInteger length = _rangeValues.count;
+    for (int i = 0; i < length - 1; i++)
+    {
+        if (value < [_rangeValues[i] floatValue])
+            return _rangeColors[i];
+    }
+    if (value <= [_rangeValues[length - 1] floatValue])
+        return _rangeColors[length - 1];
+    return nil;
+}
+
+/**
+ * Draw curved NSSring in context
+ */
+- (void)drawStringAtContext:(CGContextRef) context string:(NSString*)text withCenter:(CGPoint)center_ radius:(CGFloat)radius startAngle:(CGFloat)startAngle endAngle:(CGFloat)endAngle
+{
+    CGContextSaveGState(context);
+    
+    UIFont* font = _rangeLabelsFont ? _rangeLabelsFont : [UIFont fontWithName:@"Helvetica" size:0.05];
+    UIColor* color = _rangeLabelsFontColor ? _rangeLabelsFontColor : [UIColor whiteColor];
+
+    //add extra character to ensure there is some padding
+    NSString* testText = [text stringByAppendingString:@"$"];
+    while(_adjustRangeLabelSizeToFitWidth) {
+    
+        CGFloat maxWidth = radius * (endAngle - startAngle);
+        CGSize textSize = [testText boundingRectWithSize:CGSizeMake(maxWidth, CGFLOAT_MAX)
+                                      options:NSStringDrawingUsesLineFragmentOrigin
+                                   attributes:@{NSFontAttributeName: font}
+                                       context:nil].size;
+
+        if(textSize.height < _rangeLabelsWidth) {
+            break;
+        }
+        
+        font = [font fontWithSize:font.pointSize * 0.9];
+    }
+    
+    NSDictionary* stringAttrs = @{ NSFontAttributeName : font, NSForegroundColorAttributeName : color };
+    CGSize textSize = [text sizeWithAttributes:stringAttrs];
+    
+    float perimeter = 2 * M_PI * radius;
+    float textAngle = textSize.width / perimeter * 2 * M_PI * _rangeLabelsFontKerning;
+    float offset = ((endAngle - startAngle) - textAngle) / 2.0;
+
+    float letterPosition = 0;
+    NSString *lastLetter = @"";
+    
+    [self rotateContext:context fromCenter:center withAngle:startAngle + offset];
+    for (int index = 0; index < [text length]; index++)
+    {
+        NSRange range = {index, 1};
+        NSString* letter = [text substringWithRange:range];
+        NSAttributedString* attrStr = [[NSAttributedString alloc] initWithString:letter attributes:stringAttrs];
+        CGSize charSize = [letter sizeWithAttributes:stringAttrs];
+        float totalWidth = [[NSString stringWithFormat:@"%@%@",lastLetter, letter] sizeWithAttributes:stringAttrs].width;
+        float currentLetterWidth = [letter sizeWithAttributes:stringAttrs].width;
+        float lastLetterWidth = [lastLetter sizeWithAttributes:stringAttrs].width;
+        float kerning = (lastLetterWidth) ? 0.0 : ((currentLetterWidth + lastLetterWidth) - totalWidth);
+        
+        letterPosition += (charSize.width / 2) - kerning;
+        float angle = (letterPosition / perimeter * 2 * M_PI) * _rangeLabelsFontKerning;
+        CGPoint letterPoint = CGPointMake((radius - charSize.height / 2.0) * cos(angle) + center_.x, (radius - charSize.height / 2.0) * sin(angle) + center_.y);
+        
+        CGContextSaveGState(context);
+        CGContextTranslateCTM(context, letterPoint.x, letterPoint.y);
+        CGAffineTransform rotationTransform = CGAffineTransformMakeRotation(angle + M_PI_2);
+        CGContextConcatCTM(context, rotationTransform);
+        CGContextTranslateCTM(context, -letterPoint.x, -letterPoint.y);
+
+        [attrStr drawAtPoint:CGPointMake(letterPoint.x - charSize.width/2 , letterPoint.y - charSize.height)];
+        
+        CGContextRestoreGState(context);
+        
+        letterPosition += charSize.width / 2;
+        lastLetter = letter;
+    }
+    CGContextRestoreGState(context);
+}
+
+/**
+ * Invalidate background
+ * Background will be regenerated during next draw rect
+ */
+- (void)invalidateBackground
+{
+    background = nil;
+    [self initDrawingRects];
+    [self initScale];
+    [self setNeedsDisplay];
+}
+
+
+/**
+ * Invalidate Needle
+ * Needle will be regenerated during next draw rect
+ */
+- (void)invalidateNeedle
+{
+    [rootNeedleLayer removeAllAnimations];
+    rootNeedleLayer.sublayers = nil;
+    rootNeedleLayer = nil;
+    
+    [self setNeedsDisplay];
+}
+
+#pragma mark - Value update
+
+/**
+ * Update gauge value
+ */
+- (void)updateValue:(float)value
+{
+    // Clamp value if out of range
+    if (value > _maxValue)
+        value = _maxValue;
+    else if (value < _minValue)
+        value = _minValue;
+    else
+        value = value;
+    
+    // Set value
+    _value = value;
+}
+
+/**
+ * Update gauge value with animation
+ */
+- (void)setValue:(float)value animated:(BOOL)animated
+{
+    [self setValue:value animated:animated duration:0.8];
+}
+
+/**
+ * Update gauge value with animation and fire a completion block
+ */
+- (void)setValue:(float)value animated:(BOOL)animated completion:(void (^)(BOOL finished))completion
+{
+    [self setValue:value animated:animated duration:0.8 completion:completion];
+}
+
+/**
+ * Update gauge value with animation and duration
+ */
+- (void)setValue:(float)value animated:(BOOL)animated duration:(NSTimeInterval)duration
+{
+    [self setValue:value animated:animated duration:duration completion:nil];
+}
+
+/**
+ * Update gauge value with animation, duration and fire a completion block
+ */
+- (void)setValue:(float)value animated:(BOOL)animated duration:(NSTimeInterval)duration completion:(void (^)(BOOL finished))completion
+{
+    animationCompletion = completion;
+    
+    double lastValue = _value;
+    
+    [self updateValue:value];
+    double middleValue = lastValue + (((lastValue + (_value - lastValue) / 2.0) >= 0) ? (_value - lastValue) / 2.0 : (lastValue - _value) / 2.0);
+    
+    // Needle animation to target value
+    // An intermediate "middle" value is used to make sure the needle will follow the right rotation direction
+    
+    CAKeyframeAnimation * animation = [CAKeyframeAnimation animationWithKeyPath:@"transform"];
+    animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
+    animation.removedOnCompletion = YES;
+    animation.duration = animated ? duration : 0.0;
+    animation.delegate = (id<CAAnimationDelegate>)self;
+    animation.values = @[[NSValue valueWithCATransform3D:CATransform3DMakeRotation([self needleAngleForValue:lastValue]  , 0, 0, 1.0)],
+                         [NSValue valueWithCATransform3D:CATransform3DMakeRotation([self needleAngleForValue:middleValue], 0, 0, 1.0)],
+                         [NSValue valueWithCATransform3D:CATransform3DMakeRotation([self needleAngleForValue:_value]     , 0, 0, 1.0)]];
+    
+    if ([_style conformsToProtocol:@protocol(WMGaugeViewStyle)] == NO || [_style needleLayer:rootNeedleLayer willMoveAnimated:animated duration:duration animation:animation] == NO)
+    {
+        rootNeedleLayer.transform = [[animation.values lastObject] CATransform3DValue];
+        [rootNeedleLayer addAnimation:animation forKey:kCATransition];
+    }
+}
+
+#pragma mark - CAAnimation delegate
+
+- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag
+{
+    if (animationCompletion)
+        animationCompletion(flag);
+    
+    animationCompletion = nil;
+}
+
+#pragma mark - Properties
+
+- (void)setValue:(float)value
+{
+    [self setValue:value animated:YES];
+}
+
+- (void)setShowInnerBackground:(bool)showInnerBackground
+{
+    _showInnerBackground = showInnerBackground;
+    [self invalidateBackground];
+}
+
+- (void)setShowInnerRim:(bool)showInnerRim
+{
+    _showInnerRim = showInnerRim;
+    [self invalidateBackground];
+}
+
+- (void)setInnerRimWidth:(CGFloat)innerRimWidth
+{
+    _innerRimWidth = innerRimWidth;
+    [self invalidateBackground];
+}
+
+- (void)setInnerRimBordeWidth:(CGFloat)innerRimBorderWidth
+{
+    _innerRimBorderWidth = innerRimBorderWidth;
+    [self invalidateBackground];
+}
+
+- (void)setScalePosition:(CGFloat)scalePosition
+{
+    _scalePosition = scalePosition;
+    [self invalidateBackground];
+}
+
+- (void)setScaleStartAngle:(CGFloat)scaleStartAngle
+{
+    _scaleStartAngle = scaleStartAngle;
+    [self invalidateBackground];
+}
+
+- (void)setScaleEndAngle:(CGFloat)scaleEndAngle
+{
+    _scaleEndAngle = scaleEndAngle;
+    [self invalidateBackground];
+}
+
+- (void)setScaleDivisions:(CGFloat)scaleDivisions
+{
+    _scaleDivisions = scaleDivisions;
+    [self invalidateBackground];
+}
+
+- (void)setScaleSubdivisions:(CGFloat)scaleSubdivisions
+{
+    _scaleSubdivisions = scaleSubdivisions;
+    [self invalidateBackground];
+}
+
+- (void)setShowScaleShadow:(bool)showScaleShadow
+{
+    _showScaleShadow = showScaleShadow;
+    [self invalidateBackground];
+}
+
+- (void)setShowScale:(bool)showScale
+{
+    _showScale = showScale;
+    [self invalidateBackground];
+}
+
+-(void)setShowScaleValues:(bool)showScaleValues {
+    _showScaleValues = showScaleValues;
+    [self invalidateBackground];
+}
+
+- (void)setScalesubdivisionsAligment:(WMGaugeViewSubdivisionsAlignment)scalesubdivisionsAligment
+{
+    _scalesubdivisionsAligment = scalesubdivisionsAligment;
+    [self invalidateBackground];
+}
+
+- (void)setScaleDivisionsLength:(CGFloat)scaleDivisionsLength
+{
+    _scaleDivisionsLength = scaleDivisionsLength;
+    [self invalidateBackground];
+}
+
+- (void)setScaleDivisionsWidth:(CGFloat)scaleDivisionsWidth
+{
+    _scaleDivisionsWidth = scaleDivisionsWidth;
+    [self invalidateBackground];
+}
+
+- (void)setScaleSubdivisionsLength:(CGFloat)scaleSubdivisionsLength
+{
+    _scaleSubdivisionsLength = scaleSubdivisionsLength;
+    [self invalidateBackground];
+}
+
+- (void)setScaleSubdivisionsWidth:(CGFloat)scaleSubdivisionsWidth
+{
+    _scaleSubdivisionsWidth = scaleSubdivisionsWidth;
+    [self invalidateBackground];
+}
+
+- (void)setScaleDivisionColor:(UIColor *)scaleDivisionColor
+{
+    _scaleDivisionColor = scaleDivisionColor;
+    [self invalidateBackground];
+}
+
+- (void)setScaleSubDivisionColor:(UIColor *)scaleSubDivisionColor
+{
+    _scaleSubDivisionColor = scaleSubDivisionColor;
+    [self invalidateBackground];
+}
+
+- (void)setScaleFont:(UIFont *)scaleFont
+{
+    _scaleFont = scaleFont;
+    [self invalidateBackground];
+}
+
+- (void)setRangeLabelsWidth:(CGFloat)rangeLabelsWidth
+{
+    _rangeLabelsWidth = rangeLabelsWidth;
+    [self invalidateBackground];
+}
+
+- (void)setMinValue:(float)minValue
+{
+    _minValue = minValue;
+    [self invalidateBackground];
+}
+
+- (void)setMaxValue:(float)maxValue
+{
+    _maxValue = maxValue;
+    [self invalidateBackground];
+}
+
+- (void)setRangeValues:(NSArray *)rangeValues
+{
+    _rangeValues = rangeValues;
+    [self invalidateBackground];
+}
+
+- (void)setRangeColors:(NSArray *)rangeColors
+{
+    _rangeColors = rangeColors;
+    [self invalidateBackground];
+}
+
+- (void)setRangeLabels:(NSArray *)rangeLabels
+{
+    _rangeLabels = rangeLabels;
+    [self invalidateBackground];
+}
+
+- (void)setUnitOfMeasurement:(NSString *)unitOfMeasurement
+{
+    _unitOfMeasurement = unitOfMeasurement;
+    [self invalidateBackground];
+}
+
+- (void)setShowUnitOfMeasurement:(bool)showUnitOfMeasurement
+{
+    _showUnitOfMeasurement = showUnitOfMeasurement;
+    [self invalidateBackground];
+}
+
+- (void)setShowRangeLabels:(bool)showRangeLabels
+{
+    _showRangeLabels = showRangeLabels;
+    [self invalidateBackground];
+}
+
+- (void)setRangeLabelsFontKerning:(CGFloat)rangeLabelsFontKerning
+{
+    _rangeLabelsFontKerning = rangeLabelsFontKerning;
+    [self invalidateBackground];
+}
+
+- (void)setUnitOfMeasurementFont:(UIFont *)unitOfMeasurementFont
+{
+    _unitOfMeasurementFont = unitOfMeasurementFont;
+    [self invalidateBackground];
+}
+
+- (void)setRangeLabelsFont:(UIFont *)rangeLabelsFont
+{
+    _rangeLabelsFont = rangeLabelsFont;
+    [self invalidateBackground];
+}
+
+- (void)setUnitOfMeasurementVerticalOffset:(CGFloat)unitOfMeasurementVerticalOffset
+{
+    _unitOfMeasurementVerticalOffset = unitOfMeasurementVerticalOffset;
+    [self invalidateBackground];
+}
+
+- (void)setUnitOfMeasurementColor:(UIColor *)unitOfMeasurementColor
+{
+    _unitOfMeasurementColor = unitOfMeasurementColor;
+    [self invalidateBackground];
+}
+
+- (void)setRangeLabelsFontColor:(UIColor *)rangeLabelsFontColor
+{
+    _rangeLabelsFontColor = rangeLabelsFontColor;
+    [self invalidateBackground];
+}
+
+- (void)setStyle:(id<WMGaugeViewStyle>)style {
+    _style = style;
+    [self invalidateBackground];
+    [self invalidateNeedle];
+}
+
+@end

+ 58 - 0
KulexiuForStudent/KulexiuForStudent/Module/Widget/View/WMGaugeView/WMGaugeViewStyle.h

@@ -0,0 +1,58 @@
+/*
+ * WMGaugeViewStyle.h
+ *
+ * Copyright (C) 2015 William Markezana <william.markezana@me.com>
+ *
+ */
+
+#import <Foundation/Foundation.h>
+#import <UIKit/UIKit.h>
+
+/* Degrees to radians conversion macro */
+#define DEGREES_TO_RADIANS(degrees) (degrees) / 180.0 * M_PI
+
+/* Colors creation macros */
+#define RGB(r,g,b) [UIColor colorWithRed:r/255.0 green:g/255.0 blue:b/255.0 alpha:1.0]
+#define RGBA(r,g,b,a) [UIColor colorWithRed:r/255.0 green:g/255.0 blue:b/255.0 alpha:a/255.0]
+#define CGRGB(r,g,b) RGB(r,g,b).CGColor
+#define iCGRGB(r,g,b) (id)CGRGB(r,g,b)
+#define CGRGBA(r,g,b,a) RGBA(r,g,b,a).CGColor
+#define iCGRGBA(r,g,b,a) (id)CGRGBA(r,g,b,a)
+
+static inline
+NSArray* CGColorArray(NSArray<UIColor*>*c) {
+    NSMutableArray* _c = [NSMutableArray array];
+    for(UIColor* col in c) {
+        [_c addObject: (id)col.CGColor];
+    }
+    return _c.copy;
+}
+
+static inline
+CGFloat* CGFloatCArray(NSArray<NSNumber*>*a) {
+    CGFloat* ar = malloc(sizeof(*ar) * a.count);
+    [a enumerateObjectsUsingBlock:^(NSNumber* obj, NSUInteger idx, BOOL * _Nonnull stop) {
+        ar[idx] = (CGFloat)obj.doubleValue;
+    }];
+    return ar;
+}
+
+/* Position macros */
+#define FULL_RECT           CGRectMake(0.0, 0.0, 1.0, 1.0)
+#define kCenterX            0.5
+#define kCenterY            0.5
+#define kCenterPoint        CGPointMake(kCenterX, kCenterY)
+
+/* Scale conversion macro from [0-1] range to view  real size range */
+#define FULLSCALE(x,y)    (x)*rect.size.width, (y)*rect.size.height
+
+/**
+ * WMGaugeView style protocol
+ */
+@protocol WMGaugeViewStyle <NSObject>
+@required
+- (void)drawNeedleOnLayer:(CALayer*)layer inRect:(CGRect)rect;
+- (void)drawFaceWithContext:(CGContextRef)context inRect:(CGRect)rect;
+- (BOOL)needleLayer:(CALayer*)layer willMoveAnimated:(BOOL)animated duration:(NSTimeInterval)duration animation:(CAKeyframeAnimation*)animation;
+
+@end

+ 32 - 0
KulexiuForStudent/KulexiuForStudent/Module/Widget/View/WMGaugeView/WMGaugeViewStyle3D.h

@@ -0,0 +1,32 @@
+/*
+ * WMGaugeViewStyle3D.h
+ *
+ * Copyright (C) 2015 William Markezana <william.markezana@me.com>
+ *
+ */
+
+#import <Foundation/Foundation.h>
+#import "WMGaugeViewStyle.h"
+
+@interface WMGaugeViewStyle3D : NSObject<WMGaugeViewStyle>
+
+@property (nonatomic) CGFloat needleWidth;
+@property (nonatomic) CGFloat needleHeight;
+@property (nonatomic) CGFloat needleScrewRadius;
+
+@property (nonatomic) UIColor* leftNeedleColor;
+@property (nonatomic) UIColor* rightNeedleColor;
+
+@property (nonatomic) UIColor* screwFillColor;
+@property (nonatomic) UIColor* screwStrokeColor;
+
+@property (nonatomic) UIColor* borderColor;
+@property (nonatomic) CGFloat borderWidth;
+
+@property (nonatomic) NSArray<UIColor*>* faceGradientColors;
+@property (nonatomic) NSArray<NSNumber*>* faceGradientPositions;
+@property (nonatomic) NSArray<UIColor*>* faceShadowColors;
+@property (nonatomic) NSArray<NSNumber*>* faceShadowPositions;
+
+@end
+

+ 131 - 0
KulexiuForStudent/KulexiuForStudent/Module/Widget/View/WMGaugeView/WMGaugeViewStyle3D.m

@@ -0,0 +1,131 @@
+//
+//  WMGaugeViewStyleFlat3D.m
+//  WMGaugeView
+//
+//  Created by Markezana, William on 25/10/15.
+//  Copyright © 2015 Will™. All rights reserved.
+//
+
+#import "WMGaugeViewStyle3D.h"
+
+@implementation WMGaugeViewStyle3D
+
+- (instancetype)init
+{
+    if (self = [super init]) {
+        _needleWidth = 0.035;
+        _needleHeight = 0.34;
+        _needleScrewRadius = 0.04;
+        
+        _leftNeedleColor = RGB(176, 10, 19);
+        _rightNeedleColor = RGB(252, 18, 30);
+        
+        _screwFillColor = RGB(171, 171, 171);
+        _screwStrokeColor = RGBA(81, 84, 89, 100);
+        
+        _borderColor = RGBA(81, 84, 89, 160);
+        _borderWidth = 0.005;
+        
+        _faceGradientColors = @[RGB(96, 96, 96), RGB(68, 68, 68), RGB(32, 32, 32)];
+        _faceGradientPositions = @[@0.35, @0.96, @0.99];
+        _faceShadowColors = @[RGBA(40, 96, 170, 60), RGBA(15, 34, 98, 80), RGBA(0, 0, 0, 120), RGBA(0, 0, 0, 140)];
+        _faceShadowPositions = @[@0.60, @0.85, @0.96, @0.99];
+    }
+    return self;
+}
+
+- (void)drawNeedleOnLayer:(CALayer*)layer inRect:(CGRect)rect
+{
+    // Left Needle
+    CAShapeLayer *leftNeedleLayer = [CAShapeLayer layer];
+    UIBezierPath *leftNeedlePath = [UIBezierPath bezierPath];
+    [leftNeedlePath moveToPoint:CGPointMake(FULLSCALE(kCenterX, kCenterY))];
+    [leftNeedlePath addLineToPoint:CGPointMake(FULLSCALE(kCenterX - _needleWidth, kCenterY))];
+    [leftNeedlePath addLineToPoint:CGPointMake(FULLSCALE(kCenterX, kCenterY - _needleHeight))];
+    [leftNeedlePath closePath];
+    
+    leftNeedleLayer.path = leftNeedlePath.CGPath;
+    leftNeedleLayer.backgroundColor = [[UIColor clearColor] CGColor];
+    leftNeedleLayer.fillColor = _leftNeedleColor.CGColor;
+    
+    [layer addSublayer:leftNeedleLayer];
+    
+    // Right Needle
+    CAShapeLayer *rightNeedleLayer = [CAShapeLayer layer];
+    UIBezierPath *rightNeedlePath = [UIBezierPath bezierPath];
+    [rightNeedlePath moveToPoint:CGPointMake(FULLSCALE(kCenterX, kCenterY))];
+    [rightNeedlePath addLineToPoint:CGPointMake(FULLSCALE(kCenterX + _needleWidth, kCenterY))];
+    [rightNeedlePath addLineToPoint:CGPointMake(FULLSCALE(kCenterX, kCenterY - _needleHeight))];
+    [rightNeedlePath closePath];
+    
+    rightNeedleLayer.path = rightNeedlePath.CGPath;
+    rightNeedleLayer.backgroundColor = [[UIColor clearColor] CGColor];
+    rightNeedleLayer.fillColor = _rightNeedleColor.CGColor;
+    
+    [layer addSublayer:rightNeedleLayer];
+    
+    // Needle shadow
+    [layer setShadowColor:[[UIColor blackColor] CGColor]];
+    [layer setShadowOffset:CGSizeMake(0, 0)];
+    [layer setShadowOpacity:0.5];
+    [layer setShadowRadius:2.0];
+    
+    // Screw drawing
+    CAShapeLayer *screwLayer = [CAShapeLayer layer];
+    screwLayer.bounds = CGRectMake(FULLSCALE(kCenterX - _needleScrewRadius, kCenterY - _needleScrewRadius), FULLSCALE(_needleScrewRadius * 2.0, _needleScrewRadius * 2.0));
+    screwLayer.position = CGPointMake(FULLSCALE(kCenterX, kCenterY));
+    screwLayer.path = [UIBezierPath bezierPathWithOvalInRect:screwLayer.bounds].CGPath;
+    screwLayer.fillColor = _screwFillColor.CGColor;
+    screwLayer.strokeColor = _screwStrokeColor.CGColor;
+    screwLayer.lineWidth = 1.5;
+    
+    // Screw shadow
+    screwLayer.shadowColor = [[UIColor blackColor] CGColor];
+    screwLayer.shadowOffset = CGSizeMake(0.0, 0.0);
+    screwLayer.shadowOpacity = 0.1;
+    screwLayer.shadowRadius = 2.0;
+    
+    [layer addSublayer:screwLayer];
+}
+
+- (void)drawFaceWithContext:(CGContextRef)context inRect:(CGRect)rect
+{
+    // Default Face
+    NSAssert(_faceGradientPositions.count == _faceGradientColors.count, @"Must have same amout of colors as positions");
+    CGColorSpaceRef baseSpace = CGColorSpaceCreateDeviceRGB();
+    CGFloat* positions = CGFloatCArray(_faceGradientPositions);
+    CGGradientRef gradient = CGGradientCreateWithColors(baseSpace, (CFArrayRef)CGColorArray(_faceGradientColors), positions);
+    CGColorSpaceRelease(baseSpace);
+    baseSpace = NULL;
+    CGContextAddEllipseInRect(context, rect);
+    CGContextClip(context);
+    CGContextDrawRadialGradient(context, gradient, kCenterPoint, 0, kCenterPoint, rect.size.width / 2.0, kCGGradientDrawsAfterEndLocation);
+    CGGradientRelease(gradient);
+    gradient = NULL;
+    free(positions); positions = NULL;
+    
+    // Shadow
+    NSAssert(_faceShadowPositions.count == _faceShadowColors.count, @"Must have same amount of colors as positions");
+    baseSpace = CGColorSpaceCreateDeviceRGB();
+    positions = CGFloatCArray(_faceShadowPositions);
+    gradient = CGGradientCreateWithColors(baseSpace, (CFArrayRef)CGColorArray(_faceShadowColors), positions);
+    CGColorSpaceRelease(baseSpace); baseSpace = NULL;
+    CGContextAddEllipseInRect(context, rect);
+    CGContextClip(context);
+    CGContextDrawRadialGradient(context, gradient, kCenterPoint, 0, kCenterPoint, rect.size.width / 2.0, kCGGradientDrawsAfterEndLocation);
+    CGGradientRelease(gradient); gradient = NULL;
+    free(positions); positions = NULL;
+    
+    // Border
+    CGContextSetLineWidth(context, _borderWidth);
+    CGContextSetStrokeColorWithColor(context, _borderColor.CGColor);
+    CGContextAddEllipseInRect(context, rect);
+    CGContextStrokePath(context);
+}
+
+- (BOOL)needleLayer:(CALayer*)layer willMoveAnimated:(BOOL)animated duration:(NSTimeInterval)duration animation:(CAKeyframeAnimation*)animation
+{
+    return NO;
+}
+
+@end

+ 29 - 0
KulexiuForStudent/KulexiuForStudent/Module/Widget/View/WMGaugeView/WMGaugeViewStyleFlatThin.h

@@ -0,0 +1,29 @@
+/*
+ * WMGaugeViewStyleFlatThin.h
+ *
+ * Copyright (C) 2015 William Markezana <william.markezana@me.com>
+ *
+ */
+
+#import <Foundation/Foundation.h>
+#import "WMGaugeViewStyle.h"
+
+@interface WMGaugeViewStyleFlatThin : NSObject<WMGaugeViewStyle>
+
+@property (nonatomic) CGFloat needleWidth;
+@property (nonatomic) CGFloat needleHeight;
+@property (nonatomic) CGFloat needleScrewRadius;
+
+@property (nonatomic) UIColor* needleColor;
+@property (nonatomic) UIColor* needleScrewColor;
+
+@property (nonatomic) CGFloat externalRingRadius;
+@property (nonatomic) CGFloat internalRingRadius;
+
+@property (nonatomic) UIColor* externalFaceColor;
+@property (nonatomic) UIColor* internalFaceColor;
+
+@property (nonatomic) UIColor* borderColor;
+@property (nonatomic) CGFloat borderWidth;
+
+@end

+ 128 - 0
KulexiuForStudent/KulexiuForStudent/Module/Widget/View/WMGaugeView/WMGaugeViewStyleFlatThin.m

@@ -0,0 +1,128 @@
+//
+//  WMGaugeViewStyleFlatThin.m
+//  WMGaugeView
+//
+//  Created by Markezana, William on 25/10/15.
+//  Copyright © 2015 Will™. All rights reserved.
+//
+
+#import "WMGaugeViewStyleFlatThin.h"
+
+@interface WMGaugeViewStyleFlatThin ()
+
+@property (nonatomic) CAShapeLayer *needleLayer;
+
+@end
+
+@implementation WMGaugeViewStyleFlatThin
+
+- (instancetype)init
+{
+    if (self = [super init]) {
+        _needleWidth = 0.012;
+        _needleHeight = 0.4;
+        _needleScrewRadius = 0.05;
+//        _needleColor = RGB(255, 104, 97);//橘红色
+        _needleColor = [[UIColor redColor] colorWithAlphaComponent:0.2];
+//        _needleScrewColor = RGB(68, 84, 105);//蓝紫灰色
+        _needleScrewColor = [UIColor systemTealColor];
+        
+        _externalRingRadius = 0.24;
+//        _externalFaceColor = RGB(255, 104, 97);
+        _externalFaceColor = [[UIColor redColor] colorWithAlphaComponent:0.2];
+
+        _internalRingRadius = 0.1;
+//        _internalFaceColor = RGB(242, 99, 92);//亮橘红色
+        _internalFaceColor = [[UIColor yellowColor] colorWithAlphaComponent:0.2];
+        
+//        _borderColor = RGBA(81, 84, 89, 160);//火山灰色
+        _borderColor = [UIColor blackColor];
+        _borderWidth = 0;
+    }
+    return self;
+}
+
+- (void)drawNeedleOnLayer:(CALayer*)layer inRect:(CGRect)rect
+{
+    _needleLayer = [CAShapeLayer layer];
+    UIBezierPath *needlePath = [UIBezierPath bezierPath];
+    [needlePath moveToPoint:CGPointMake(FULLSCALE(kCenterX - _needleWidth, kCenterY))];
+    [needlePath addLineToPoint:CGPointMake(FULLSCALE(kCenterX + _needleWidth, kCenterY))];
+    [needlePath addLineToPoint:CGPointMake(FULLSCALE(kCenterX, kCenterY - _needleHeight))];
+    [needlePath closePath];
+    
+    _needleLayer.path = needlePath.CGPath;
+    _needleLayer.backgroundColor = [[UIColor clearColor] CGColor];
+    _needleLayer.fillColor = _needleColor.CGColor;
+    _needleLayer.strokeColor = _needleColor.CGColor;
+    _needleLayer.lineWidth = 1.2;
+    
+    // Needle shadow
+    _needleLayer.shadowColor = [[UIColor blackColor] CGColor];
+    _needleLayer.shadowOffset = CGSizeMake(-2.0, -2.0);
+    _needleLayer.shadowOpacity = 0.2;
+    _needleLayer.shadowRadius = 1.2;
+    
+    [layer addSublayer:_needleLayer];
+    
+    // Screw drawing
+    CAShapeLayer *screwLayer = [CAShapeLayer layer];
+    screwLayer.bounds = CGRectMake(FULLSCALE(kCenterX - _needleScrewRadius, kCenterY - _needleScrewRadius), FULLSCALE(_needleScrewRadius * 2.0, _needleScrewRadius * 2.0));
+    screwLayer.position = CGPointMake(FULLSCALE(kCenterX, kCenterY));
+    screwLayer.path = [UIBezierPath bezierPathWithOvalInRect:screwLayer.bounds].CGPath;
+    screwLayer.fillColor = _needleScrewColor.CGColor;
+    
+    // Screw shadow
+    screwLayer.shadowColor = [[UIColor blackColor] CGColor];
+    screwLayer.shadowOffset = CGSizeMake(0.0, 0.0);
+    screwLayer.shadowOpacity = 0.2;
+    screwLayer.shadowRadius = 2.0;
+    
+    [layer addSublayer:screwLayer];
+}
+
+- (void)drawFaceWithContext:(CGContextRef)context inRect:(CGRect)rect
+{
+    // External circle
+    CGRect externalRect = CGRectMake(kCenterX - _externalRingRadius, kCenterY - _externalRingRadius, _externalRingRadius * 2.0, _externalRingRadius * 2.0);
+    CGContextSetFillColorWithColor(context, _externalFaceColor.CGColor);
+    CGContextFillEllipseInRect(context, externalRect);
+
+    // Inner circle
+    CGRect internalRect = CGRectMake(kCenterX - _internalRingRadius, kCenterY - _internalRingRadius, _internalRingRadius * 2.0, _internalRingRadius * 2.0);
+    CGContextSetFillColorWithColor(context, _internalFaceColor.CGColor);
+    CGContextFillEllipseInRect(context, internalRect);
+    
+    // Border
+    CGRect borderRect = CGRectInset(rect, _borderWidth * 0.5, _borderWidth * 0.5);
+    CGContextSetLineWidth(context, _borderWidth);
+    CGContextSetStrokeColorWithColor(context, _borderColor.CGColor);
+    CGContextStrokeEllipseInRect(context, borderRect);
+}
+
+- (BOOL)needleLayer:(CALayer*)layer willMoveAnimated:(BOOL)animated duration:(NSTimeInterval)duration animation:(CAKeyframeAnimation*)animation
+{
+    layer.transform = [[animation.values objectAtIndex:0] CATransform3DValue];
+    CGAffineTransform affineTransform1 = [layer affineTransform];
+    layer.transform = [[animation.values objectAtIndex:1] CATransform3DValue];
+    CGAffineTransform affineTransform2 = [layer affineTransform];
+    layer.transform = [[animation.values lastObject] CATransform3DValue];
+    CGAffineTransform affineTransform3 = [layer affineTransform];
+    
+    _needleLayer.shadowOffset = CGSizeApplyAffineTransform(CGSizeMake(-2.0, -2.0), affineTransform3);
+    
+    [layer addAnimation:animation forKey:kCATransition];
+    
+    CAKeyframeAnimation * animationShadowOffset = [CAKeyframeAnimation animationWithKeyPath:@"shadowOffset"];
+    animationShadowOffset.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
+    animationShadowOffset.removedOnCompletion = YES;
+    animationShadowOffset.duration = animated ? duration : 0.0;
+    animationShadowOffset.values = @[[NSValue valueWithCGSize:CGSizeApplyAffineTransform(CGSizeMake(-2.0, -2.0), affineTransform1)],
+                                     [NSValue valueWithCGSize:CGSizeApplyAffineTransform(CGSizeMake(-2.0, -2.0), affineTransform2)],
+                                     [NSValue valueWithCGSize:CGSizeApplyAffineTransform(CGSizeMake(-2.0, -2.0), affineTransform3)]];
+    [_needleLayer addAnimation:animationShadowOffset forKey:kCATransition];
+    
+    return YES;
+}
+
+@end

+ 19 - 0
KulexiuForStudent/KulexiuForStudent/Module/Widget/View/toneTuning/DialPlateView.h

@@ -0,0 +1,19 @@
+//
+//  DialPlateView.h
+//  KulexiuForStudent
+//
+//  Created by 王智 on 2022/10/14.
+//
+
+#import <UIKit/UIKit.h>
+#import "WMGaugeView.h"
+NS_ASSUME_NONNULL_BEGIN
+
+/// 表盘View
+@interface DialPlateView : UIView
+
+@property (nonatomic, strong) WMGaugeView *gaugeView;
+
+@end
+
+NS_ASSUME_NONNULL_END

+ 65 - 0
KulexiuForStudent/KulexiuForStudent/Module/Widget/View/toneTuning/DialPlateView.m

@@ -0,0 +1,65 @@
+//
+//  DialPlateView.m
+//  KulexiuForStudent
+//
+//  Created by 王智 on 2022/10/14.
+//
+
+#import "DialPlateView.h"
+#import "WMGaugeViewStyleFlatThin.h"
+
+@interface DialPlateView ()
+
+
+@end
+
+@implementation DialPlateView
+
+- (instancetype)initWithFrame:(CGRect)frame {
+    self = [super initWithFrame:frame];
+    if (self) {
+        [self configUI];
+    }
+    return self;
+}
+
+- (void)configUI {
+    self.backgroundColor = [UIColor clearColor];
+    [self addSubview:self.gaugeView];
+    [self.gaugeView mas_makeConstraints:^(MASConstraintMaker *make) {
+        make.left.top.bottom.right.mas_equalTo(self);
+    }];
+    
+}
+
+- (WMGaugeView *)gaugeView {
+    if (!_gaugeView) {
+        _gaugeView = [[WMGaugeView alloc] init];
+        _gaugeView.style = [[WMGaugeViewStyleFlatThin alloc] init];
+        _gaugeView.maxValue = 50.0f;
+        _gaugeView.minValue = -50.0f;
+        _gaugeView.scaleDivisions = 10;
+        _gaugeView.scaleEndAngle = 270;
+        _gaugeView.scaleStartAngle = 90;
+        _gaugeView.scaleSubdivisions = 5;
+        _gaugeView.showScaleShadow = NO;
+        _gaugeView.scaleDivisionsLength = 0.05;
+        _gaugeView.scaleDivisionsWidth = 0.007;
+        _gaugeView.scaleSubdivisionsLength = 0.02;
+        _gaugeView.scaleSubdivisionsWidth = 0.002;
+        _gaugeView.backgroundColor = [UIColor clearColor];
+        _gaugeView.scaleFont = [UIFont systemFontOfSize:0.05 weight:UIFontWeightUltraLight];
+    }
+    return _gaugeView;
+}
+
+
+/*
+// Only override drawRect: if you perform custom drawing.
+// An empty implementation adversely affects performance during animation.
+- (void)drawRect:(CGRect)rect {
+    // Drawing code
+}
+*/
+
+@end

+ 16 - 0
KulexiuForStudent/KulexiuForStudent/Module/Widget/View/toneTuning/ToneTuningBodyView.h

@@ -11,8 +11,24 @@ NS_ASSUME_NONNULL_BEGIN
 
 @interface ToneTuningBodyView : UIView
 
+@property (weak, nonatomic) IBOutlet UIView *plateView;
+
+@property (weak, nonatomic) IBOutlet UIView *pitchBgView;
+
+@property (weak, nonatomic) IBOutlet UILabel *nomalPitch;
+
+@property (weak, nonatomic) IBOutlet UIView *transferView;
+
+@property (weak, nonatomic) IBOutlet UILabel *transferNomalPitch;
+
+@property (weak, nonatomic) IBOutlet UILabel *transferPitch;
+
+@property (weak, nonatomic) IBOutlet UILabel *transferDesc;
+
 @property (weak, nonatomic) IBOutlet UILabel *A4FrequenceLabel;
 
+@property (weak, nonatomic) IBOutlet UILabel *pitchFrequenceLabel;
+
 @property (nonatomic, assign) NSInteger A4Frequence;
 
 

+ 12 - 10
KulexiuForStudent/KulexiuForStudent/Module/Widget/View/toneTuning/ToneTuningBodyView.m

@@ -9,13 +9,14 @@
 
 @interface ToneTuningBodyView ()
 
-@property (weak, nonatomic) IBOutlet UIView *plateView;
+@property (weak, nonatomic) IBOutlet UIView *minusView;
 
 @property (weak, nonatomic) IBOutlet UIButton *minusButton;
 
+@property (weak, nonatomic) IBOutlet UIView *addView;
+
 @property (weak, nonatomic) IBOutlet UIButton *addButton;
 
-@property (weak, nonatomic) IBOutlet UIView *pitchBgView;
 
 @end
 
@@ -29,15 +30,16 @@
     layer.masksToBounds = YES;
     [self.pitchBgView.layer addSublayer:layer];
     
-    CAGradientLayer *buttonLayer = [self createGradientLayerFromColor:HexRGB(0x3C495F) startPoint:CGPointMake(0.5, 0) endColor:HexRGB(0x1C2331) endPoint:CGPointMake(0.5, 1) bounds:CGRectMake(0, 0, 50, 31)];
-    buttonLayer.cornerRadius = 15.5f;
-    buttonLayer.masksToBounds = YES;
-    [self.minusButton.layer addSublayer:buttonLayer];
+    CAGradientLayer *minusLayer = [self createGradientLayerFromColor:HexRGB(0x3C495F) startPoint:CGPointMake(0.5, 0) endColor:HexRGB(0x1C2331) endPoint:CGPointMake(0.5, 1) bounds:CGRectMake(0, 0, 50, 31)];
+    minusLayer.cornerRadius = 15.5f;
+    minusLayer.masksToBounds = YES;
+    [self.minusView.layer addSublayer:minusLayer];
     
-    CAGradientLayer *buttonLayer2 = [self createGradientLayerFromColor:HexRGB(0x3C495F) startPoint:CGPointMake(0.5, 0) endColor:HexRGB(0x1C2331) endPoint:CGPointMake(0.5, 1) bounds:CGRectMake(0, 0, 50, 31)];
-    buttonLayer2.cornerRadius = 15.5f;
-    buttonLayer2.masksToBounds = YES;
-    [self.addButton.layer addSublayer:buttonLayer2];
+    CAGradientLayer *addViewLayer = [self createGradientLayerFromColor:HexRGB(0x3C495F) startPoint:CGPointMake(0.5, 0) endColor:HexRGB(0x1C2331) endPoint:CGPointMake(0.5, 1) bounds:CGRectMake(0, 0, 50, 31)];
+    addViewLayer.cornerRadius = 15.5f;
+    addViewLayer.masksToBounds = YES;
+    [self.addView.layer addSublayer:addViewLayer];
+    self.transferView.hidden = YES;
     
 }
 

+ 145 - 6
KulexiuForStudent/KulexiuForStudent/Module/Widget/View/toneTuning/ToneTuningBodyView.xib

@@ -36,10 +36,17 @@
                         <constraint firstAttribute="width" secondItem="SXB-cz-Qdh" secondAttribute="height" multiplier="187:127" id="ixC-MD-R46"/>
                     </constraints>
                 </imageView>
-                <view contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Zrx-Pe-2NT">
-                    <rect key="frame" x="56" y="81" width="276" height="175"/>
-                    <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
-                    <color key="backgroundColor" systemColor="systemBackgroundColor"/>
+                <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Zrx-Pe-2NT">
+                    <rect key="frame" x="43.666666666666657" y="88" width="303" height="303"/>
+                    <color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
+                    <constraints>
+                        <constraint firstAttribute="width" constant="303" id="93S-gk-24h"/>
+                        <constraint firstAttribute="height" constant="303" id="rD4-eg-11F"/>
+                    </constraints>
+                </view>
+                <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="g4h-vE-BMO">
+                    <rect key="frame" x="107" y="322.66666666666669" width="50" height="31"/>
+                    <color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
                 </view>
                 <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Cai-ev-v5E">
                     <rect key="frame" x="107" y="322.66666666666669" width="50" height="31"/>
@@ -67,6 +74,10 @@
                         </userDefinedRuntimeAttribute>
                     </userDefinedRuntimeAttributes>
                 </button>
+                <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="uwR-ra-cDH">
+                    <rect key="frame" x="233" y="322.66666666666669" width="50" height="31"/>
+                    <color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
+                </view>
                 <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="VJU-LE-Pdq">
                     <rect key="frame" x="233" y="322.66666666666669" width="50" height="31"/>
                     <constraints>
@@ -124,6 +135,103 @@
                         </userDefinedRuntimeAttribute>
                     </userDefinedRuntimeAttributes>
                 </view>
+                <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="ZeX-qM-LJD">
+                    <rect key="frame" x="14" y="380" width="362" height="170"/>
+                    <subviews>
+                        <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="A#" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="uIc-iL-EXg">
+                            <rect key="frame" x="155" y="56" width="52" height="46"/>
+                            <fontDescription key="fontDescription" type="boldSystem" pointSize="38"/>
+                            <color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
+                            <nil key="highlightedColor"/>
+                        </label>
+                        <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="6FP-hm-dfl">
+                            <rect key="frame" x="11" y="154" width="340" height="5"/>
+                            <subviews>
+                                <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="i3w-7a-0hL">
+                                    <rect key="frame" x="0.0" y="0.0" width="0.0" height="5"/>
+                                    <color key="backgroundColor" systemColor="systemBackgroundColor"/>
+                                    <constraints>
+                                        <constraint firstAttribute="width" id="Ndf-lc-KO2"/>
+                                    </constraints>
+                                </view>
+                            </subviews>
+                            <color key="backgroundColor" red="0.1764705882352941" green="0.21176470588235294" blue="0.28235294117647058" alpha="1" colorSpace="calibratedRGB"/>
+                            <constraints>
+                                <constraint firstAttribute="height" constant="5" id="3Xi-hl-yD4"/>
+                                <constraint firstAttribute="bottom" secondItem="i3w-7a-0hL" secondAttribute="bottom" id="54u-rc-9uK"/>
+                                <constraint firstItem="i3w-7a-0hL" firstAttribute="top" secondItem="6FP-hm-dfl" secondAttribute="top" id="KB8-r8-QVf"/>
+                                <constraint firstItem="i3w-7a-0hL" firstAttribute="leading" secondItem="6FP-hm-dfl" secondAttribute="leading" id="qxd-Xa-3KK"/>
+                            </constraints>
+                        </view>
+                        <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="ya5-kp-sTW">
+                            <rect key="frame" x="0.0" y="0.0" width="362" height="143"/>
+                            <subviews>
+                                <imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="tuning_transfer" translatesAutoresizingMaskIntoConstraints="NO" id="guW-zj-dkh">
+                                    <rect key="frame" x="171" y="80" width="20" height="23"/>
+                                    <constraints>
+                                        <constraint firstAttribute="width" constant="20" id="3Ic-Ab-Zdu"/>
+                                        <constraint firstAttribute="height" constant="23" id="Jad-yu-6YO"/>
+                                    </constraints>
+                                </imageView>
+                                <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="A#" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="MNR-s0-re6">
+                                    <rect key="frame" x="68" y="65" width="52" height="53"/>
+                                    <constraints>
+                                        <constraint firstAttribute="height" constant="53" id="rPI-W9-m7n"/>
+                                    </constraints>
+                                    <fontDescription key="fontDescription" type="boldSystem" pointSize="38"/>
+                                    <color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
+                                    <nil key="highlightedColor"/>
+                                </label>
+                                <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="C#" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="vku-Yz-AEN">
+                                    <rect key="frame" x="242" y="65" width="53" height="53"/>
+                                    <constraints>
+                                        <constraint firstAttribute="height" constant="53" id="vCR-NZ-KzZ"/>
+                                    </constraints>
+                                    <fontDescription key="fontDescription" type="boldSystem" pointSize="38"/>
+                                    <color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
+                                    <nil key="highlightedColor"/>
+                                </label>
+                                <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Concert" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="FLn-wI-hyg">
+                                    <rect key="frame" x="71.666666666666671" y="38" width="45" height="17"/>
+                                    <fontDescription key="fontDescription" name="DINAlternate-Bold" family="DIN Alternate" pointSize="14"/>
+                                    <color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
+                                    <nil key="highlightedColor"/>
+                                </label>
+                                <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="F# ins." textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="tD3-d1-rfd">
+                                    <rect key="frame" x="249" y="38" width="39" height="17"/>
+                                    <fontDescription key="fontDescription" name="DINAlternate-Bold" family="DIN Alternate" pointSize="14"/>
+                                    <color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
+                                    <nil key="highlightedColor"/>
+                                </label>
+                            </subviews>
+                            <color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
+                            <constraints>
+                                <constraint firstItem="MNR-s0-re6" firstAttribute="top" secondItem="FLn-wI-hyg" secondAttribute="bottom" constant="10" id="CiQ-l5-VOE"/>
+                                <constraint firstItem="guW-zj-dkh" firstAttribute="top" secondItem="ya5-kp-sTW" secondAttribute="top" constant="80" id="KZl-F4-IKz"/>
+                                <constraint firstItem="vku-Yz-AEN" firstAttribute="leading" secondItem="guW-zj-dkh" secondAttribute="trailing" constant="51" id="Lab-An-ath"/>
+                                <constraint firstItem="guW-zj-dkh" firstAttribute="centerY" secondItem="MNR-s0-re6" secondAttribute="centerY" id="N1i-3X-n6R"/>
+                                <constraint firstItem="MNR-s0-re6" firstAttribute="centerX" secondItem="FLn-wI-hyg" secondAttribute="centerX" id="Ozw-yh-mZ2"/>
+                                <constraint firstItem="vku-Yz-AEN" firstAttribute="centerY" secondItem="MNR-s0-re6" secondAttribute="centerY" id="Sxq-0v-HVY"/>
+                                <constraint firstItem="guW-zj-dkh" firstAttribute="centerX" secondItem="ya5-kp-sTW" secondAttribute="centerX" id="U6o-GX-a5D"/>
+                                <constraint firstItem="vku-Yz-AEN" firstAttribute="top" secondItem="tD3-d1-rfd" secondAttribute="bottom" constant="10" id="tg6-Gb-dMJ"/>
+                                <constraint firstItem="vku-Yz-AEN" firstAttribute="centerX" secondItem="tD3-d1-rfd" secondAttribute="centerX" id="wTA-Ek-KF6"/>
+                                <constraint firstItem="guW-zj-dkh" firstAttribute="leading" secondItem="MNR-s0-re6" secondAttribute="trailing" constant="51" id="x7v-5G-Dlt"/>
+                            </constraints>
+                        </view>
+                    </subviews>
+                    <color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
+                    <constraints>
+                        <constraint firstItem="uIc-iL-EXg" firstAttribute="centerX" secondItem="ZeX-qM-LJD" secondAttribute="centerX" id="2Bp-Ty-UhN"/>
+                        <constraint firstAttribute="bottom" secondItem="6FP-hm-dfl" secondAttribute="bottom" constant="11" id="31N-v4-lk3"/>
+                        <constraint firstItem="ya5-kp-sTW" firstAttribute="top" secondItem="ZeX-qM-LJD" secondAttribute="top" id="8zF-cX-C0k"/>
+                        <constraint firstAttribute="trailing" secondItem="ya5-kp-sTW" secondAttribute="trailing" id="T8f-BR-fRF"/>
+                        <constraint firstItem="ya5-kp-sTW" firstAttribute="leading" secondItem="ZeX-qM-LJD" secondAttribute="leading" id="beh-Nw-l30"/>
+                        <constraint firstItem="6FP-hm-dfl" firstAttribute="leading" secondItem="ZeX-qM-LJD" secondAttribute="leading" constant="11" id="dcC-0h-BqT"/>
+                        <constraint firstItem="6FP-hm-dfl" firstAttribute="top" secondItem="ya5-kp-sTW" secondAttribute="bottom" constant="11" id="ioo-lM-ERL"/>
+                        <constraint firstItem="uIc-iL-EXg" firstAttribute="top" secondItem="ZeX-qM-LJD" secondAttribute="top" constant="56" id="kgc-uR-hoE"/>
+                        <constraint firstAttribute="trailing" secondItem="6FP-hm-dfl" secondAttribute="trailing" constant="11" id="lZt-Ec-22l"/>
+                    </constraints>
+                </view>
                 <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Tkl-Cw-Mdz">
                     <rect key="frame" x="6" y="562" width="189" height="90"/>
                     <constraints>
@@ -133,7 +241,7 @@
                     <state key="normal" image="fork_unchoose" backgroundImage="forkButton_bg"/>
                 </button>
                 <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="p0z-qL-bMm">
-                    <rect key="frame" x="195" y="560" width="189" height="90"/>
+                    <rect key="frame" x="195" y="562" width="189" height="90"/>
                     <constraints>
                         <constraint firstAttribute="height" constant="90" id="D2z-en-N9g"/>
                     </constraints>
@@ -175,11 +283,19 @@
                         <constraint firstAttribute="height" constant="32" id="qMW-a2-T7G"/>
                     </constraints>
                 </imageView>
+                <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="PnS-Ku-vsa">
+                    <rect key="frame" x="195" y="76" width="0.0" height="0.0"/>
+                    <fontDescription key="fontDescription" type="system" pointSize="14"/>
+                    <color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
+                    <nil key="highlightedColor"/>
+                </label>
             </subviews>
             <color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
             <constraints>
                 <constraint firstItem="sZj-ZO-VHR" firstAttribute="leading" secondItem="Cai-ev-v5E" secondAttribute="trailing" constant="17" id="0D9-VV-0Lx"/>
-                <constraint firstItem="p0z-qL-bMm" firstAttribute="top" secondItem="7Em-FC-3EL" secondAttribute="bottom" constant="10" id="4SR-6A-ipm"/>
+                <constraint firstItem="7Em-FC-3EL" firstAttribute="leading" secondItem="ZeX-qM-LJD" secondAttribute="leading" id="1nv-ne-dbI"/>
+                <constraint firstItem="g4h-vE-BMO" firstAttribute="bottom" secondItem="Cai-ev-v5E" secondAttribute="bottom" id="3KO-qD-HZ2"/>
+                <constraint firstItem="p0z-qL-bMm" firstAttribute="top" secondItem="7Em-FC-3EL" secondAttribute="bottom" constant="12" id="4SR-6A-ipm"/>
                 <constraint firstItem="p0z-qL-bMm" firstAttribute="leading" secondItem="Tkl-Cw-Mdz" secondAttribute="trailing" id="5EB-Vw-dj2"/>
                 <constraint firstItem="Cai-ev-v5E" firstAttribute="leading" secondItem="v4Z-dA-iqI" secondAttribute="trailing" constant="22" id="6b8-ki-CY9"/>
                 <constraint firstItem="7Em-FC-3EL" firstAttribute="leading" secondItem="iN0-l3-epB" secondAttribute="leading" constant="14" id="7ZV-OX-B1b"/>
@@ -187,25 +303,39 @@
                 <constraint firstItem="IXi-hL-in1" firstAttribute="centerX" secondItem="iN0-l3-epB" secondAttribute="centerX" id="Au1-eF-R25"/>
                 <constraint firstItem="VJU-LE-Pdq" firstAttribute="leading" secondItem="sZj-ZO-VHR" secondAttribute="trailing" constant="17" id="C3a-5i-dtf"/>
                 <constraint firstItem="p0z-qL-bMm" firstAttribute="width" secondItem="Tkl-Cw-Mdz" secondAttribute="width" id="Dk9-Wv-4mD"/>
+                <constraint firstItem="PnS-Ku-vsa" firstAttribute="centerX" secondItem="iN0-l3-epB" secondAttribute="centerX" id="FLp-jq-kpX"/>
                 <constraint firstItem="SXB-cz-Qdh" firstAttribute="top" secondItem="Ux8-2g-RqK" secondAttribute="bottom" constant="10" id="FYQ-Rq-QtT"/>
+                <constraint firstItem="7Em-FC-3EL" firstAttribute="top" secondItem="ZeX-qM-LJD" secondAttribute="top" id="FlV-cS-OgW"/>
+                <constraint firstItem="g4h-vE-BMO" firstAttribute="trailing" secondItem="Cai-ev-v5E" secondAttribute="trailing" id="N2Y-VV-UPi"/>
                 <constraint firstItem="SXB-cz-Qdh" firstAttribute="leading" secondItem="iN0-l3-epB" secondAttribute="leading" constant="8" id="O3Y-u0-MV3"/>
                 <constraint firstItem="sZj-ZO-VHR" firstAttribute="centerX" secondItem="iN0-l3-epB" secondAttribute="centerX" id="PEf-sX-DzU"/>
                 <constraint firstItem="Tkl-Cw-Mdz" firstAttribute="leading" secondItem="iN0-l3-epB" secondAttribute="leading" constant="6" id="Q9g-JT-4o6"/>
+                <constraint firstItem="VJU-LE-Pdq" firstAttribute="top" secondItem="uwR-ra-cDH" secondAttribute="top" id="RZq-Hd-B0P"/>
                 <constraint firstAttribute="trailing" secondItem="p0z-qL-bMm" secondAttribute="trailing" constant="6" id="RoG-8u-1ap"/>
                 <constraint firstItem="V2v-I8-jzm" firstAttribute="centerY" secondItem="VJU-LE-Pdq" secondAttribute="centerY" id="T1g-e0-zDn"/>
+                <constraint firstItem="VJU-LE-Pdq" firstAttribute="trailing" secondItem="uwR-ra-cDH" secondAttribute="trailing" id="Tew-m4-zeK"/>
+                <constraint firstItem="Zrx-Pe-2NT" firstAttribute="top" secondItem="SXB-cz-Qdh" secondAttribute="top" constant="46" id="Xd5-nj-nbj"/>
                 <constraint firstItem="V2v-I8-jzm" firstAttribute="leading" secondItem="VJU-LE-Pdq" secondAttribute="trailing" constant="22" id="YYc-Q5-aGD"/>
                 <constraint firstItem="IXi-hL-in1" firstAttribute="top" secondItem="Tkl-Cw-Mdz" secondAttribute="bottom" constant="7" id="YgN-1n-xha"/>
                 <constraint firstAttribute="trailing" secondItem="7Em-FC-3EL" secondAttribute="trailing" constant="14" id="a3n-fN-euB"/>
+                <constraint firstItem="Zrx-Pe-2NT" firstAttribute="top" secondItem="PnS-Ku-vsa" secondAttribute="bottom" constant="12" id="aSM-PX-vLj"/>
+                <constraint firstItem="g4h-vE-BMO" firstAttribute="leading" secondItem="Cai-ev-v5E" secondAttribute="leading" id="btp-Oe-y1i"/>
                 <constraint firstItem="sZj-ZO-VHR" firstAttribute="centerY" secondItem="Cai-ev-v5E" secondAttribute="centerY" id="cIh-IQ-xRr"/>
                 <constraint firstItem="Tkl-Cw-Mdz" firstAttribute="top" secondItem="7Em-FC-3EL" secondAttribute="bottom" constant="12" id="cJp-sC-nM0"/>
                 <constraint firstItem="Sbe-L2-liH" firstAttribute="centerY" secondItem="Ux8-2g-RqK" secondAttribute="centerY" id="cmi-yM-djK"/>
+                <constraint firstItem="7Em-FC-3EL" firstAttribute="bottom" secondItem="ZeX-qM-LJD" secondAttribute="bottom" id="coV-Qi-RkQ"/>
                 <constraint firstItem="Ux8-2g-RqK" firstAttribute="leading" secondItem="iN0-l3-epB" secondAttribute="leading" constant="22" id="cwN-DO-wkV"/>
+                <constraint firstItem="VJU-LE-Pdq" firstAttribute="bottom" secondItem="uwR-ra-cDH" secondAttribute="bottom" id="e0m-sL-feH"/>
                 <constraint firstItem="Ux8-2g-RqK" firstAttribute="top" secondItem="iN0-l3-epB" secondAttribute="top" constant="10" id="ev8-FX-ve4"/>
+                <constraint firstItem="VJU-LE-Pdq" firstAttribute="leading" secondItem="uwR-ra-cDH" secondAttribute="leading" id="hAr-OA-5ce"/>
                 <constraint firstItem="Sbe-L2-liH" firstAttribute="leading" secondItem="Ux8-2g-RqK" secondAttribute="trailing" constant="5" id="meC-LK-DKS"/>
+                <constraint firstItem="g4h-vE-BMO" firstAttribute="top" secondItem="Cai-ev-v5E" secondAttribute="top" id="nxA-Si-aiG"/>
+                <constraint firstItem="7Em-FC-3EL" firstAttribute="trailing" secondItem="ZeX-qM-LJD" secondAttribute="trailing" id="oKn-54-J78"/>
                 <constraint firstItem="7Em-FC-3EL" firstAttribute="top" secondItem="sZj-ZO-VHR" secondAttribute="bottom" constant="32" id="pZl-CI-zNY"/>
                 <constraint firstItem="VJU-LE-Pdq" firstAttribute="centerY" secondItem="Cai-ev-v5E" secondAttribute="centerY" id="usx-pc-WFX"/>
                 <constraint firstItem="sZj-ZO-VHR" firstAttribute="top" secondItem="SXB-cz-Qdh" secondAttribute="bottom" constant="32" id="v2V-ca-D8A"/>
                 <constraint firstAttribute="trailing" secondItem="SXB-cz-Qdh" secondAttribute="trailing" constant="8" id="vQ0-wP-c03"/>
+                <constraint firstItem="Zrx-Pe-2NT" firstAttribute="centerX" secondItem="iN0-l3-epB" secondAttribute="centerX" id="wef-Xs-dNx"/>
             </constraints>
             <nil key="simulatedTopBarMetrics"/>
             <nil key="simulatedBottomBarMetrics"/>
@@ -213,9 +343,17 @@
             <connections>
                 <outlet property="A4FrequenceLabel" destination="Ux8-2g-RqK" id="Jra-7M-iTB"/>
                 <outlet property="addButton" destination="VJU-LE-Pdq" id="Ap2-3f-er6"/>
+                <outlet property="addView" destination="uwR-ra-cDH" id="aTz-zS-V4w"/>
                 <outlet property="minusButton" destination="Cai-ev-v5E" id="7T1-RS-zM3"/>
+                <outlet property="minusView" destination="g4h-vE-BMO" id="SOP-VD-G0z"/>
+                <outlet property="nomalPitch" destination="uIc-iL-EXg" id="i9k-OK-L2i"/>
                 <outlet property="pitchBgView" destination="7Em-FC-3EL" id="0rG-SE-app"/>
+                <outlet property="pitchFrequenceLabel" destination="PnS-Ku-vsa" id="LlJ-w5-az7"/>
                 <outlet property="plateView" destination="Zrx-Pe-2NT" id="ppk-X8-oX5"/>
+                <outlet property="transferDesc" destination="tD3-d1-rfd" id="vBI-n9-F5P"/>
+                <outlet property="transferNomalPitch" destination="MNR-s0-re6" id="sVk-tj-gtm"/>
+                <outlet property="transferPitch" destination="vku-Yz-AEN" id="ca0-HH-hao"/>
+                <outlet property="transferView" destination="ya5-kp-sTW" id="VPm-KO-4q0"/>
             </connections>
             <point key="canvasLocation" x="93.84615384615384" y="-12.085308056872037"/>
         </view>
@@ -230,6 +368,7 @@
         <image name="playButton_bg" width="182" height="90"/>
         <image name="toning_bottom" width="338" height="29"/>
         <image name="tuning_decorate" width="48" height="32"/>
+        <image name="tuning_transfer" width="20" height="23"/>
         <image name="tuning_warning" width="13" height="12"/>
         <systemColor name="systemBackgroundColor">
             <color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>