├── MCChatHUD ├── MCChatHUD.xcodeproj │ ├── project.pbxproj │ └── xcuserdata │ │ └── duwei.xcuserdatad │ │ └── xcschemes │ │ └── xcschememanagement.plist ├── MCChatHUD │ ├── AppDelegate.swift │ ├── Assets.xcassets │ │ └── AppIcon.appiconset │ │ │ └── Contents.json │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ ├── Controller │ │ ├── ChooseTypeViewController.swift │ │ └── RecordViewController.swift │ ├── Info.plist │ ├── Marco │ │ └── HUDMarco.swift │ └── View │ │ ├── MCProgressView.swift │ │ ├── MCRecordHUD.swift │ │ └── MCVolumeView.swift ├── MCChatHUDTests │ ├── Info.plist │ └── MCChatHUDTests.swift └── MCChatHUDUITests │ ├── Info.plist │ └── MCChatHUDUITests.swift └── README.md /MCChatHUD/MCChatHUD.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 48; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 5B1A80CC202708FD0065F21B /* ChooseTypeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B1A80CB202708FD0065F21B /* ChooseTypeViewController.swift */; }; 11 | 5B2DA023202050C7008AF29B /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B2DA022202050C7008AF29B /* AppDelegate.swift */; }; 12 | 5B2DA028202050C7008AF29B /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 5B2DA026202050C7008AF29B /* Main.storyboard */; }; 13 | 5B2DA02A202050C7008AF29B /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 5B2DA029202050C7008AF29B /* Assets.xcassets */; }; 14 | 5B2DA02D202050C7008AF29B /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 5B2DA02B202050C7008AF29B /* LaunchScreen.storyboard */; }; 15 | 5B2DA038202050C7008AF29B /* MCChatHUDTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B2DA037202050C7008AF29B /* MCChatHUDTests.swift */; }; 16 | 5B2DA043202050C7008AF29B /* MCChatHUDUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B2DA042202050C7008AF29B /* MCChatHUDUITests.swift */; }; 17 | 5B2DA05220205141008AF29B /* MCRecordHUD.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B2DA05120205141008AF29B /* MCRecordHUD.swift */; }; 18 | 5B2DA05420205167008AF29B /* MCProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B2DA05320205167008AF29B /* MCProgressView.swift */; }; 19 | 5B2DA0562020517D008AF29B /* MCVolumeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B2DA0552020517D008AF29B /* MCVolumeView.swift */; }; 20 | 5B2DA059202053D0008AF29B /* HUDMarco.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B2DA058202053D0008AF29B /* HUDMarco.swift */; }; 21 | 5B6A6CEB2021E07E00F16718 /* RecordViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B6A6CEA2021E07E00F16718 /* RecordViewController.swift */; }; 22 | /* End PBXBuildFile section */ 23 | 24 | /* Begin PBXContainerItemProxy section */ 25 | 5B2DA034202050C7008AF29B /* PBXContainerItemProxy */ = { 26 | isa = PBXContainerItemProxy; 27 | containerPortal = 5B2DA017202050C7008AF29B /* Project object */; 28 | proxyType = 1; 29 | remoteGlobalIDString = 5B2DA01E202050C7008AF29B; 30 | remoteInfo = MCChatHUD; 31 | }; 32 | 5B2DA03F202050C7008AF29B /* PBXContainerItemProxy */ = { 33 | isa = PBXContainerItemProxy; 34 | containerPortal = 5B2DA017202050C7008AF29B /* Project object */; 35 | proxyType = 1; 36 | remoteGlobalIDString = 5B2DA01E202050C7008AF29B; 37 | remoteInfo = MCChatHUD; 38 | }; 39 | /* End PBXContainerItemProxy section */ 40 | 41 | /* Begin PBXFileReference section */ 42 | 5B1A80CB202708FD0065F21B /* ChooseTypeViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChooseTypeViewController.swift; sourceTree = ""; }; 43 | 5B2DA01F202050C7008AF29B /* MCChatHUD.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MCChatHUD.app; sourceTree = BUILT_PRODUCTS_DIR; }; 44 | 5B2DA022202050C7008AF29B /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 45 | 5B2DA027202050C7008AF29B /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 46 | 5B2DA029202050C7008AF29B /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 47 | 5B2DA02C202050C7008AF29B /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 48 | 5B2DA02E202050C7008AF29B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 49 | 5B2DA033202050C7008AF29B /* MCChatHUDTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MCChatHUDTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 50 | 5B2DA037202050C7008AF29B /* MCChatHUDTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MCChatHUDTests.swift; sourceTree = ""; }; 51 | 5B2DA039202050C7008AF29B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 52 | 5B2DA03E202050C7008AF29B /* MCChatHUDUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MCChatHUDUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 53 | 5B2DA042202050C7008AF29B /* MCChatHUDUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MCChatHUDUITests.swift; sourceTree = ""; }; 54 | 5B2DA044202050C7008AF29B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 55 | 5B2DA05120205141008AF29B /* MCRecordHUD.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MCRecordHUD.swift; sourceTree = ""; }; 56 | 5B2DA05320205167008AF29B /* MCProgressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MCProgressView.swift; sourceTree = ""; }; 57 | 5B2DA0552020517D008AF29B /* MCVolumeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MCVolumeView.swift; sourceTree = ""; }; 58 | 5B2DA058202053D0008AF29B /* HUDMarco.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HUDMarco.swift; sourceTree = ""; }; 59 | 5B6A6CEA2021E07E00F16718 /* RecordViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecordViewController.swift; sourceTree = ""; }; 60 | /* End PBXFileReference section */ 61 | 62 | /* Begin PBXFrameworksBuildPhase section */ 63 | 5B2DA01C202050C7008AF29B /* Frameworks */ = { 64 | isa = PBXFrameworksBuildPhase; 65 | buildActionMask = 2147483647; 66 | files = ( 67 | ); 68 | runOnlyForDeploymentPostprocessing = 0; 69 | }; 70 | 5B2DA030202050C7008AF29B /* Frameworks */ = { 71 | isa = PBXFrameworksBuildPhase; 72 | buildActionMask = 2147483647; 73 | files = ( 74 | ); 75 | runOnlyForDeploymentPostprocessing = 0; 76 | }; 77 | 5B2DA03B202050C7008AF29B /* Frameworks */ = { 78 | isa = PBXFrameworksBuildPhase; 79 | buildActionMask = 2147483647; 80 | files = ( 81 | ); 82 | runOnlyForDeploymentPostprocessing = 0; 83 | }; 84 | /* End PBXFrameworksBuildPhase section */ 85 | 86 | /* Begin PBXGroup section */ 87 | 5B2DA016202050C7008AF29B = { 88 | isa = PBXGroup; 89 | children = ( 90 | 5B2DA021202050C7008AF29B /* MCChatHUD */, 91 | 5B2DA036202050C7008AF29B /* MCChatHUDTests */, 92 | 5B2DA041202050C7008AF29B /* MCChatHUDUITests */, 93 | 5B2DA020202050C7008AF29B /* Products */, 94 | ); 95 | sourceTree = ""; 96 | }; 97 | 5B2DA020202050C7008AF29B /* Products */ = { 98 | isa = PBXGroup; 99 | children = ( 100 | 5B2DA01F202050C7008AF29B /* MCChatHUD.app */, 101 | 5B2DA033202050C7008AF29B /* MCChatHUDTests.xctest */, 102 | 5B2DA03E202050C7008AF29B /* MCChatHUDUITests.xctest */, 103 | ); 104 | name = Products; 105 | sourceTree = ""; 106 | }; 107 | 5B2DA021202050C7008AF29B /* MCChatHUD */ = { 108 | isa = PBXGroup; 109 | children = ( 110 | 5B6A6CE92021DF6F00F16718 /* Controller */, 111 | 5B2DA05020205122008AF29B /* View */, 112 | 5B2DA057202053B6008AF29B /* Marco */, 113 | 5B2DA022202050C7008AF29B /* AppDelegate.swift */, 114 | 5B2DA026202050C7008AF29B /* Main.storyboard */, 115 | 5B2DA029202050C7008AF29B /* Assets.xcassets */, 116 | 5B2DA02B202050C7008AF29B /* LaunchScreen.storyboard */, 117 | 5B2DA02E202050C7008AF29B /* Info.plist */, 118 | ); 119 | path = MCChatHUD; 120 | sourceTree = ""; 121 | }; 122 | 5B2DA036202050C7008AF29B /* MCChatHUDTests */ = { 123 | isa = PBXGroup; 124 | children = ( 125 | 5B2DA037202050C7008AF29B /* MCChatHUDTests.swift */, 126 | 5B2DA039202050C7008AF29B /* Info.plist */, 127 | ); 128 | path = MCChatHUDTests; 129 | sourceTree = ""; 130 | }; 131 | 5B2DA041202050C7008AF29B /* MCChatHUDUITests */ = { 132 | isa = PBXGroup; 133 | children = ( 134 | 5B2DA042202050C7008AF29B /* MCChatHUDUITests.swift */, 135 | 5B2DA044202050C7008AF29B /* Info.plist */, 136 | ); 137 | path = MCChatHUDUITests; 138 | sourceTree = ""; 139 | }; 140 | 5B2DA05020205122008AF29B /* View */ = { 141 | isa = PBXGroup; 142 | children = ( 143 | 5B2DA05120205141008AF29B /* MCRecordHUD.swift */, 144 | 5B2DA05320205167008AF29B /* MCProgressView.swift */, 145 | 5B2DA0552020517D008AF29B /* MCVolumeView.swift */, 146 | ); 147 | path = View; 148 | sourceTree = ""; 149 | }; 150 | 5B2DA057202053B6008AF29B /* Marco */ = { 151 | isa = PBXGroup; 152 | children = ( 153 | 5B2DA058202053D0008AF29B /* HUDMarco.swift */, 154 | ); 155 | path = Marco; 156 | sourceTree = ""; 157 | }; 158 | 5B6A6CE92021DF6F00F16718 /* Controller */ = { 159 | isa = PBXGroup; 160 | children = ( 161 | 5B6A6CEA2021E07E00F16718 /* RecordViewController.swift */, 162 | 5B1A80CB202708FD0065F21B /* ChooseTypeViewController.swift */, 163 | ); 164 | path = Controller; 165 | sourceTree = ""; 166 | }; 167 | /* End PBXGroup section */ 168 | 169 | /* Begin PBXNativeTarget section */ 170 | 5B2DA01E202050C7008AF29B /* MCChatHUD */ = { 171 | isa = PBXNativeTarget; 172 | buildConfigurationList = 5B2DA047202050C7008AF29B /* Build configuration list for PBXNativeTarget "MCChatHUD" */; 173 | buildPhases = ( 174 | 5B2DA01B202050C7008AF29B /* Sources */, 175 | 5B2DA01C202050C7008AF29B /* Frameworks */, 176 | 5B2DA01D202050C7008AF29B /* Resources */, 177 | ); 178 | buildRules = ( 179 | ); 180 | dependencies = ( 181 | ); 182 | name = MCChatHUD; 183 | productName = MCChatHUD; 184 | productReference = 5B2DA01F202050C7008AF29B /* MCChatHUD.app */; 185 | productType = "com.apple.product-type.application"; 186 | }; 187 | 5B2DA032202050C7008AF29B /* MCChatHUDTests */ = { 188 | isa = PBXNativeTarget; 189 | buildConfigurationList = 5B2DA04A202050C7008AF29B /* Build configuration list for PBXNativeTarget "MCChatHUDTests" */; 190 | buildPhases = ( 191 | 5B2DA02F202050C7008AF29B /* Sources */, 192 | 5B2DA030202050C7008AF29B /* Frameworks */, 193 | 5B2DA031202050C7008AF29B /* Resources */, 194 | ); 195 | buildRules = ( 196 | ); 197 | dependencies = ( 198 | 5B2DA035202050C7008AF29B /* PBXTargetDependency */, 199 | ); 200 | name = MCChatHUDTests; 201 | productName = MCChatHUDTests; 202 | productReference = 5B2DA033202050C7008AF29B /* MCChatHUDTests.xctest */; 203 | productType = "com.apple.product-type.bundle.unit-test"; 204 | }; 205 | 5B2DA03D202050C7008AF29B /* MCChatHUDUITests */ = { 206 | isa = PBXNativeTarget; 207 | buildConfigurationList = 5B2DA04D202050C7008AF29B /* Build configuration list for PBXNativeTarget "MCChatHUDUITests" */; 208 | buildPhases = ( 209 | 5B2DA03A202050C7008AF29B /* Sources */, 210 | 5B2DA03B202050C7008AF29B /* Frameworks */, 211 | 5B2DA03C202050C7008AF29B /* Resources */, 212 | ); 213 | buildRules = ( 214 | ); 215 | dependencies = ( 216 | 5B2DA040202050C7008AF29B /* PBXTargetDependency */, 217 | ); 218 | name = MCChatHUDUITests; 219 | productName = MCChatHUDUITests; 220 | productReference = 5B2DA03E202050C7008AF29B /* MCChatHUDUITests.xctest */; 221 | productType = "com.apple.product-type.bundle.ui-testing"; 222 | }; 223 | /* End PBXNativeTarget section */ 224 | 225 | /* Begin PBXProject section */ 226 | 5B2DA017202050C7008AF29B /* Project object */ = { 227 | isa = PBXProject; 228 | attributes = { 229 | LastSwiftUpdateCheck = 0920; 230 | LastUpgradeCheck = 0920; 231 | ORGANIZATIONNAME = Dywane; 232 | TargetAttributes = { 233 | 5B2DA01E202050C7008AF29B = { 234 | CreatedOnToolsVersion = 9.2; 235 | ProvisioningStyle = Automatic; 236 | }; 237 | 5B2DA032202050C7008AF29B = { 238 | CreatedOnToolsVersion = 9.2; 239 | ProvisioningStyle = Automatic; 240 | TestTargetID = 5B2DA01E202050C7008AF29B; 241 | }; 242 | 5B2DA03D202050C7008AF29B = { 243 | CreatedOnToolsVersion = 9.2; 244 | ProvisioningStyle = Automatic; 245 | TestTargetID = 5B2DA01E202050C7008AF29B; 246 | }; 247 | }; 248 | }; 249 | buildConfigurationList = 5B2DA01A202050C7008AF29B /* Build configuration list for PBXProject "MCChatHUD" */; 250 | compatibilityVersion = "Xcode 8.0"; 251 | developmentRegion = en; 252 | hasScannedForEncodings = 0; 253 | knownRegions = ( 254 | en, 255 | Base, 256 | ); 257 | mainGroup = 5B2DA016202050C7008AF29B; 258 | productRefGroup = 5B2DA020202050C7008AF29B /* Products */; 259 | projectDirPath = ""; 260 | projectRoot = ""; 261 | targets = ( 262 | 5B2DA01E202050C7008AF29B /* MCChatHUD */, 263 | 5B2DA032202050C7008AF29B /* MCChatHUDTests */, 264 | 5B2DA03D202050C7008AF29B /* MCChatHUDUITests */, 265 | ); 266 | }; 267 | /* End PBXProject section */ 268 | 269 | /* Begin PBXResourcesBuildPhase section */ 270 | 5B2DA01D202050C7008AF29B /* Resources */ = { 271 | isa = PBXResourcesBuildPhase; 272 | buildActionMask = 2147483647; 273 | files = ( 274 | 5B2DA02D202050C7008AF29B /* LaunchScreen.storyboard in Resources */, 275 | 5B2DA02A202050C7008AF29B /* Assets.xcassets in Resources */, 276 | 5B2DA028202050C7008AF29B /* Main.storyboard in Resources */, 277 | ); 278 | runOnlyForDeploymentPostprocessing = 0; 279 | }; 280 | 5B2DA031202050C7008AF29B /* Resources */ = { 281 | isa = PBXResourcesBuildPhase; 282 | buildActionMask = 2147483647; 283 | files = ( 284 | ); 285 | runOnlyForDeploymentPostprocessing = 0; 286 | }; 287 | 5B2DA03C202050C7008AF29B /* Resources */ = { 288 | isa = PBXResourcesBuildPhase; 289 | buildActionMask = 2147483647; 290 | files = ( 291 | ); 292 | runOnlyForDeploymentPostprocessing = 0; 293 | }; 294 | /* End PBXResourcesBuildPhase section */ 295 | 296 | /* Begin PBXSourcesBuildPhase section */ 297 | 5B2DA01B202050C7008AF29B /* Sources */ = { 298 | isa = PBXSourcesBuildPhase; 299 | buildActionMask = 2147483647; 300 | files = ( 301 | 5B2DA0562020517D008AF29B /* MCVolumeView.swift in Sources */, 302 | 5B1A80CC202708FD0065F21B /* ChooseTypeViewController.swift in Sources */, 303 | 5B2DA05220205141008AF29B /* MCRecordHUD.swift in Sources */, 304 | 5B2DA059202053D0008AF29B /* HUDMarco.swift in Sources */, 305 | 5B6A6CEB2021E07E00F16718 /* RecordViewController.swift in Sources */, 306 | 5B2DA023202050C7008AF29B /* AppDelegate.swift in Sources */, 307 | 5B2DA05420205167008AF29B /* MCProgressView.swift in Sources */, 308 | ); 309 | runOnlyForDeploymentPostprocessing = 0; 310 | }; 311 | 5B2DA02F202050C7008AF29B /* Sources */ = { 312 | isa = PBXSourcesBuildPhase; 313 | buildActionMask = 2147483647; 314 | files = ( 315 | 5B2DA038202050C7008AF29B /* MCChatHUDTests.swift in Sources */, 316 | ); 317 | runOnlyForDeploymentPostprocessing = 0; 318 | }; 319 | 5B2DA03A202050C7008AF29B /* Sources */ = { 320 | isa = PBXSourcesBuildPhase; 321 | buildActionMask = 2147483647; 322 | files = ( 323 | 5B2DA043202050C7008AF29B /* MCChatHUDUITests.swift in Sources */, 324 | ); 325 | runOnlyForDeploymentPostprocessing = 0; 326 | }; 327 | /* End PBXSourcesBuildPhase section */ 328 | 329 | /* Begin PBXTargetDependency section */ 330 | 5B2DA035202050C7008AF29B /* PBXTargetDependency */ = { 331 | isa = PBXTargetDependency; 332 | target = 5B2DA01E202050C7008AF29B /* MCChatHUD */; 333 | targetProxy = 5B2DA034202050C7008AF29B /* PBXContainerItemProxy */; 334 | }; 335 | 5B2DA040202050C7008AF29B /* PBXTargetDependency */ = { 336 | isa = PBXTargetDependency; 337 | target = 5B2DA01E202050C7008AF29B /* MCChatHUD */; 338 | targetProxy = 5B2DA03F202050C7008AF29B /* PBXContainerItemProxy */; 339 | }; 340 | /* End PBXTargetDependency section */ 341 | 342 | /* Begin PBXVariantGroup section */ 343 | 5B2DA026202050C7008AF29B /* Main.storyboard */ = { 344 | isa = PBXVariantGroup; 345 | children = ( 346 | 5B2DA027202050C7008AF29B /* Base */, 347 | ); 348 | name = Main.storyboard; 349 | sourceTree = ""; 350 | }; 351 | 5B2DA02B202050C7008AF29B /* LaunchScreen.storyboard */ = { 352 | isa = PBXVariantGroup; 353 | children = ( 354 | 5B2DA02C202050C7008AF29B /* Base */, 355 | ); 356 | name = LaunchScreen.storyboard; 357 | sourceTree = ""; 358 | }; 359 | /* End PBXVariantGroup section */ 360 | 361 | /* Begin XCBuildConfiguration section */ 362 | 5B2DA045202050C7008AF29B /* Debug */ = { 363 | isa = XCBuildConfiguration; 364 | buildSettings = { 365 | ALWAYS_SEARCH_USER_PATHS = NO; 366 | CLANG_ANALYZER_NONNULL = YES; 367 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 368 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 369 | CLANG_CXX_LIBRARY = "libc++"; 370 | CLANG_ENABLE_MODULES = YES; 371 | CLANG_ENABLE_OBJC_ARC = YES; 372 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 373 | CLANG_WARN_BOOL_CONVERSION = YES; 374 | CLANG_WARN_COMMA = YES; 375 | CLANG_WARN_CONSTANT_CONVERSION = YES; 376 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 377 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 378 | CLANG_WARN_EMPTY_BODY = YES; 379 | CLANG_WARN_ENUM_CONVERSION = YES; 380 | CLANG_WARN_INFINITE_RECURSION = YES; 381 | CLANG_WARN_INT_CONVERSION = YES; 382 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 383 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 384 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 385 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 386 | CLANG_WARN_STRICT_PROTOTYPES = YES; 387 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 388 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 389 | CLANG_WARN_UNREACHABLE_CODE = YES; 390 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 391 | CODE_SIGN_IDENTITY = "iPhone Developer"; 392 | COPY_PHASE_STRIP = NO; 393 | DEBUG_INFORMATION_FORMAT = dwarf; 394 | ENABLE_STRICT_OBJC_MSGSEND = YES; 395 | ENABLE_TESTABILITY = YES; 396 | GCC_C_LANGUAGE_STANDARD = gnu11; 397 | GCC_DYNAMIC_NO_PIC = NO; 398 | GCC_NO_COMMON_BLOCKS = YES; 399 | GCC_OPTIMIZATION_LEVEL = 0; 400 | GCC_PREPROCESSOR_DEFINITIONS = ( 401 | "DEBUG=1", 402 | "$(inherited)", 403 | ); 404 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 405 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 406 | GCC_WARN_UNDECLARED_SELECTOR = YES; 407 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 408 | GCC_WARN_UNUSED_FUNCTION = YES; 409 | GCC_WARN_UNUSED_VARIABLE = YES; 410 | IPHONEOS_DEPLOYMENT_TARGET = 11.2; 411 | MTL_ENABLE_DEBUG_INFO = YES; 412 | ONLY_ACTIVE_ARCH = YES; 413 | SDKROOT = iphoneos; 414 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 415 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 416 | }; 417 | name = Debug; 418 | }; 419 | 5B2DA046202050C7008AF29B /* Release */ = { 420 | isa = XCBuildConfiguration; 421 | buildSettings = { 422 | ALWAYS_SEARCH_USER_PATHS = NO; 423 | CLANG_ANALYZER_NONNULL = YES; 424 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 425 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 426 | CLANG_CXX_LIBRARY = "libc++"; 427 | CLANG_ENABLE_MODULES = YES; 428 | CLANG_ENABLE_OBJC_ARC = YES; 429 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 430 | CLANG_WARN_BOOL_CONVERSION = YES; 431 | CLANG_WARN_COMMA = YES; 432 | CLANG_WARN_CONSTANT_CONVERSION = YES; 433 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 434 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 435 | CLANG_WARN_EMPTY_BODY = YES; 436 | CLANG_WARN_ENUM_CONVERSION = YES; 437 | CLANG_WARN_INFINITE_RECURSION = YES; 438 | CLANG_WARN_INT_CONVERSION = YES; 439 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 440 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 441 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 442 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 443 | CLANG_WARN_STRICT_PROTOTYPES = YES; 444 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 445 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 446 | CLANG_WARN_UNREACHABLE_CODE = YES; 447 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 448 | CODE_SIGN_IDENTITY = "iPhone Developer"; 449 | COPY_PHASE_STRIP = NO; 450 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 451 | ENABLE_NS_ASSERTIONS = NO; 452 | ENABLE_STRICT_OBJC_MSGSEND = YES; 453 | GCC_C_LANGUAGE_STANDARD = gnu11; 454 | GCC_NO_COMMON_BLOCKS = YES; 455 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 456 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 457 | GCC_WARN_UNDECLARED_SELECTOR = YES; 458 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 459 | GCC_WARN_UNUSED_FUNCTION = YES; 460 | GCC_WARN_UNUSED_VARIABLE = YES; 461 | IPHONEOS_DEPLOYMENT_TARGET = 11.2; 462 | MTL_ENABLE_DEBUG_INFO = NO; 463 | SDKROOT = iphoneos; 464 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 465 | VALIDATE_PRODUCT = YES; 466 | }; 467 | name = Release; 468 | }; 469 | 5B2DA048202050C7008AF29B /* Debug */ = { 470 | isa = XCBuildConfiguration; 471 | buildSettings = { 472 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 473 | CODE_SIGN_STYLE = Automatic; 474 | DEVELOPMENT_TEAM = 55F5T35K6E; 475 | INFOPLIST_FILE = MCChatHUD/Info.plist; 476 | IPHONEOS_DEPLOYMENT_TARGET = 10.0; 477 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 478 | PRODUCT_BUNDLE_IDENTIFIER = dywane.MCChatHUD; 479 | PRODUCT_NAME = "$(TARGET_NAME)"; 480 | SWIFT_VERSION = 4.0; 481 | TARGETED_DEVICE_FAMILY = "1,2"; 482 | }; 483 | name = Debug; 484 | }; 485 | 5B2DA049202050C7008AF29B /* Release */ = { 486 | isa = XCBuildConfiguration; 487 | buildSettings = { 488 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 489 | CODE_SIGN_STYLE = Automatic; 490 | DEVELOPMENT_TEAM = 55F5T35K6E; 491 | INFOPLIST_FILE = MCChatHUD/Info.plist; 492 | IPHONEOS_DEPLOYMENT_TARGET = 10.0; 493 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 494 | PRODUCT_BUNDLE_IDENTIFIER = dywane.MCChatHUD; 495 | PRODUCT_NAME = "$(TARGET_NAME)"; 496 | SWIFT_VERSION = 4.0; 497 | TARGETED_DEVICE_FAMILY = "1,2"; 498 | }; 499 | name = Release; 500 | }; 501 | 5B2DA04B202050C7008AF29B /* Debug */ = { 502 | isa = XCBuildConfiguration; 503 | buildSettings = { 504 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 505 | BUNDLE_LOADER = "$(TEST_HOST)"; 506 | CODE_SIGN_STYLE = Automatic; 507 | DEVELOPMENT_TEAM = 55F5T35K6E; 508 | INFOPLIST_FILE = MCChatHUDTests/Info.plist; 509 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 510 | PRODUCT_BUNDLE_IDENTIFIER = dywane.MCChatHUDTests; 511 | PRODUCT_NAME = "$(TARGET_NAME)"; 512 | SWIFT_VERSION = 4.0; 513 | TARGETED_DEVICE_FAMILY = "1,2"; 514 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/MCChatHUD.app/MCChatHUD"; 515 | }; 516 | name = Debug; 517 | }; 518 | 5B2DA04C202050C7008AF29B /* Release */ = { 519 | isa = XCBuildConfiguration; 520 | buildSettings = { 521 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 522 | BUNDLE_LOADER = "$(TEST_HOST)"; 523 | CODE_SIGN_STYLE = Automatic; 524 | DEVELOPMENT_TEAM = 55F5T35K6E; 525 | INFOPLIST_FILE = MCChatHUDTests/Info.plist; 526 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 527 | PRODUCT_BUNDLE_IDENTIFIER = dywane.MCChatHUDTests; 528 | PRODUCT_NAME = "$(TARGET_NAME)"; 529 | SWIFT_VERSION = 4.0; 530 | TARGETED_DEVICE_FAMILY = "1,2"; 531 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/MCChatHUD.app/MCChatHUD"; 532 | }; 533 | name = Release; 534 | }; 535 | 5B2DA04E202050C7008AF29B /* Debug */ = { 536 | isa = XCBuildConfiguration; 537 | buildSettings = { 538 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 539 | CODE_SIGN_STYLE = Automatic; 540 | DEVELOPMENT_TEAM = 55F5T35K6E; 541 | INFOPLIST_FILE = MCChatHUDUITests/Info.plist; 542 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 543 | PRODUCT_BUNDLE_IDENTIFIER = dywane.MCChatHUDUITests; 544 | PRODUCT_NAME = "$(TARGET_NAME)"; 545 | SWIFT_VERSION = 4.0; 546 | TARGETED_DEVICE_FAMILY = "1,2"; 547 | TEST_TARGET_NAME = MCChatHUD; 548 | }; 549 | name = Debug; 550 | }; 551 | 5B2DA04F202050C7008AF29B /* Release */ = { 552 | isa = XCBuildConfiguration; 553 | buildSettings = { 554 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 555 | CODE_SIGN_STYLE = Automatic; 556 | DEVELOPMENT_TEAM = 55F5T35K6E; 557 | INFOPLIST_FILE = MCChatHUDUITests/Info.plist; 558 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 559 | PRODUCT_BUNDLE_IDENTIFIER = dywane.MCChatHUDUITests; 560 | PRODUCT_NAME = "$(TARGET_NAME)"; 561 | SWIFT_VERSION = 4.0; 562 | TARGETED_DEVICE_FAMILY = "1,2"; 563 | TEST_TARGET_NAME = MCChatHUD; 564 | }; 565 | name = Release; 566 | }; 567 | /* End XCBuildConfiguration section */ 568 | 569 | /* Begin XCConfigurationList section */ 570 | 5B2DA01A202050C7008AF29B /* Build configuration list for PBXProject "MCChatHUD" */ = { 571 | isa = XCConfigurationList; 572 | buildConfigurations = ( 573 | 5B2DA045202050C7008AF29B /* Debug */, 574 | 5B2DA046202050C7008AF29B /* Release */, 575 | ); 576 | defaultConfigurationIsVisible = 0; 577 | defaultConfigurationName = Release; 578 | }; 579 | 5B2DA047202050C7008AF29B /* Build configuration list for PBXNativeTarget "MCChatHUD" */ = { 580 | isa = XCConfigurationList; 581 | buildConfigurations = ( 582 | 5B2DA048202050C7008AF29B /* Debug */, 583 | 5B2DA049202050C7008AF29B /* Release */, 584 | ); 585 | defaultConfigurationIsVisible = 0; 586 | defaultConfigurationName = Release; 587 | }; 588 | 5B2DA04A202050C7008AF29B /* Build configuration list for PBXNativeTarget "MCChatHUDTests" */ = { 589 | isa = XCConfigurationList; 590 | buildConfigurations = ( 591 | 5B2DA04B202050C7008AF29B /* Debug */, 592 | 5B2DA04C202050C7008AF29B /* Release */, 593 | ); 594 | defaultConfigurationIsVisible = 0; 595 | defaultConfigurationName = Release; 596 | }; 597 | 5B2DA04D202050C7008AF29B /* Build configuration list for PBXNativeTarget "MCChatHUDUITests" */ = { 598 | isa = XCConfigurationList; 599 | buildConfigurations = ( 600 | 5B2DA04E202050C7008AF29B /* Debug */, 601 | 5B2DA04F202050C7008AF29B /* Release */, 602 | ); 603 | defaultConfigurationIsVisible = 0; 604 | defaultConfigurationName = Release; 605 | }; 606 | /* End XCConfigurationList section */ 607 | }; 608 | rootObject = 5B2DA017202050C7008AF29B /* Project object */; 609 | } 610 | -------------------------------------------------------------------------------- /MCChatHUD/MCChatHUD.xcodeproj/xcuserdata/duwei.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | MCChatHUD.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /MCChatHUD/MCChatHUD/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // MCChatHUD 4 | // 5 | // Created by duwei on 2018/1/30. 6 | // Copyright © 2018年 Dywane. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | 17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { 18 | // Override point for customization after application launch. 19 | return true 20 | } 21 | 22 | func applicationWillResignActive(_ application: UIApplication) { 23 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 24 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 25 | } 26 | 27 | func applicationDidEnterBackground(_ application: UIApplication) { 28 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 29 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 30 | } 31 | 32 | func applicationWillEnterForeground(_ application: UIApplication) { 33 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. 34 | } 35 | 36 | func applicationDidBecomeActive(_ application: UIApplication) { 37 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 38 | } 39 | 40 | func applicationWillTerminate(_ application: UIApplication) { 41 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 42 | } 43 | 44 | 45 | } 46 | 47 | -------------------------------------------------------------------------------- /MCChatHUD/MCChatHUD/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "size" : "1024x1024", 91 | "scale" : "1x" 92 | } 93 | ], 94 | "info" : { 95 | "version" : 1, 96 | "author" : "xcode" 97 | } 98 | } -------------------------------------------------------------------------------- /MCChatHUD/MCChatHUD/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /MCChatHUD/MCChatHUD/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | -------------------------------------------------------------------------------- /MCChatHUD/MCChatHUD/Controller/ChooseTypeViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ChooseTypeViewController.swift 3 | // MCChatHUD 4 | // 5 | // Created by duwei on 2018/2/4. 6 | // Copyright © 2018年 Dywane. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class ChooseTypeViewController: UITableViewController { 12 | 13 | /// 类型数组 14 | private let typeArray = ["Bar style", "Line style"] 15 | 16 | /// 选择的类型 17 | private var selectedIndex = 0 18 | 19 | override func viewDidLoad() { 20 | super.viewDidLoad() 21 | } 22 | 23 | override func didReceiveMemoryWarning() { 24 | super.didReceiveMemoryWarning() 25 | // Dispose of any resources that can be recreated. 26 | } 27 | 28 | // MARK: - Table view data source 29 | 30 | override func numberOfSections(in tableView: UITableView) -> Int { 31 | return 1 32 | } 33 | 34 | override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 35 | return typeArray.count 36 | } 37 | 38 | override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 39 | let cell = tableView.dequeueReusableCell(withIdentifier: "ChooseTypeCellID", for: indexPath) 40 | cell.textLabel?.text = typeArray[indexPath.row] 41 | return cell 42 | } 43 | 44 | override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 45 | selectedIndex = indexPath.row 46 | performSegue(withIdentifier: "ShowRecordViewController", sender: self) 47 | } 48 | 49 | // MARK: - Navigation 50 | override func prepare(for segue: UIStoryboardSegue, sender: Any?) { 51 | if segue.identifier == "ShowRecordViewController" { 52 | guard let type = HUDType(rawValue: selectedIndex) else { 53 | return 54 | } 55 | let recordVC = segue.destination as! RecordViewController 56 | recordVC.HUDType = type 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /MCChatHUD/MCChatHUD/Controller/RecordViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RecordViewController.swift 3 | // MCChatHUD 4 | // 5 | // Created by duwei on 2018/1/31. 6 | // Copyright © 2018年 Dywane. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import AVFoundation 11 | 12 | class RecordViewController: UIViewController { 13 | 14 | /// HUD类型 15 | public var HUDType: HUDType = .bar 16 | 17 | @IBOutlet weak private var recordButton: UIButton! 18 | 19 | /// 录音框 20 | private var chatHUD: MCRecordHUD! 21 | 22 | /// 录音器 23 | private var recorder: AVAudioRecorder! 24 | /// 录音器设置 25 | private let recorderSetting = [AVSampleRateKey : NSNumber(value: Float(44100.0)),//声音采样率 26 | AVFormatIDKey : NSNumber(value: Int32(kAudioFormatMPEG4AAC)),//编码格式 27 | AVNumberOfChannelsKey : NSNumber(value: 1),//采集音轨 28 | AVEncoderAudioQualityKey : NSNumber(value: Int32(AVAudioQuality.medium.rawValue))]//声音质量 29 | /// 录音计时器 30 | private var timer: Timer? 31 | /// 波形更新间隔 32 | private let updateFequency = 0.05 33 | /// 声音数据数组 34 | private var soundMeters: [Float]! 35 | /// 声音数据数组容量 36 | private let soundMeterCount = 10 37 | /// 录音时间 38 | private var recordTime = 0.00 39 | 40 | override func viewDidLoad() { 41 | super.viewDidLoad() 42 | chatHUD = MCRecordHUD(type: HUDType) 43 | configRecord() 44 | setupButtonEvent() 45 | } 46 | 47 | override func didReceiveMemoryWarning() { 48 | super.didReceiveMemoryWarning() 49 | } 50 | } 51 | 52 | // MARK: - Record handlers 53 | extension RecordViewController: AVAudioRecorderDelegate { 54 | 55 | /// 开始录音 56 | @objc private func beginRecordVoice() { 57 | if recorder == nil { 58 | return 59 | } 60 | view.addSubview(chatHUD) 61 | view.isUserInteractionEnabled = false //录音时候禁止点击其他地方 62 | chatHUD.startCounting() 63 | soundMeters = [Float]() 64 | recorder.record() 65 | timer = Timer.scheduledTimer(timeInterval: updateFequency, target: self, selector: #selector(updateMeters), userInfo: nil, repeats: true) 66 | } 67 | 68 | /// 停止录音 69 | @objc private func endRecordVoice() { 70 | recorder.stop() 71 | timer?.invalidate() 72 | chatHUD.removeFromSuperview() 73 | view.isUserInteractionEnabled = true //录音完了才能点击其他地方 74 | chatHUD.stopCounting() 75 | soundMeters.removeAll() 76 | } 77 | 78 | /// 取消录音 79 | @objc private func cancelRecordVoice() { 80 | endRecordVoice() 81 | recorder.deleteRecording() 82 | } 83 | 84 | /// 上划取消录音 85 | @objc private func remindDragExit() { 86 | chatHUD.titleLabel.text = "Release to cancel" 87 | } 88 | 89 | /// 下滑继续录音 90 | @objc private func remindDragEnter() { 91 | chatHUD.titleLabel.text = "Slide up to cancel" 92 | } 93 | 94 | @objc private func updateMeters() { 95 | recorder.updateMeters() 96 | recordTime += updateFequency 97 | addSoundMeter(item: recorder.averagePower(forChannel: 0)) 98 | if recordTime >= 60.0 { 99 | endRecordVoice() 100 | } 101 | } 102 | 103 | private func addSoundMeter(item: Float) { 104 | if soundMeters.count < soundMeterCount { 105 | soundMeters.append(item) 106 | } else { 107 | for (index, _) in soundMeters.enumerated() { 108 | if index < soundMeterCount - 1 { 109 | soundMeters[index] = soundMeters[index + 1] 110 | } 111 | } 112 | // 插入新数据 113 | soundMeters[soundMeterCount - 1] = item 114 | NotificationCenter.default.post(name: NSNotification.Name.init("updateMeters"), object: soundMeters) 115 | } 116 | } 117 | } 118 | 119 | //MARK: - AVAudioRecorderDelegate 120 | extension RecordViewController { 121 | 122 | func audioRecorderDidFinishRecording(_ recorder: AVAudioRecorder, successfully flag: Bool) { 123 | if recordTime > 1.0 { 124 | if flag { 125 | do { 126 | let exists = try recorder.url.checkResourceIsReachable() 127 | if exists { 128 | print("finish record") 129 | } 130 | } 131 | catch { print("fail to load record")} 132 | } else { 133 | print("record failed") 134 | } 135 | } 136 | recordTime = 0 137 | } 138 | } 139 | 140 | // MARK: - Setup 141 | extension RecordViewController { 142 | 143 | private func setupButtonEvent() { 144 | recordButton.addTarget(self, action: #selector(beginRecordVoice), for: .touchDown) 145 | recordButton.addTarget(self, action: #selector(endRecordVoice), for: .touchUpInside) 146 | recordButton.addTarget(self, action: #selector(cancelRecordVoice), for: .touchUpOutside) 147 | recordButton.addTarget(self, action: #selector(cancelRecordVoice), for: .touchCancel) 148 | recordButton.addTarget(self, action: #selector(remindDragExit), for: .touchDragExit) 149 | recordButton.addTarget(self, action: #selector(remindDragEnter), for: .touchDragEnter) 150 | } 151 | 152 | private func configAVAudioSession() { 153 | let session = AVAudioSession.sharedInstance() 154 | do { try session.setCategory(AVAudioSessionCategoryRecord, with: .defaultToSpeaker) } 155 | catch { print("session config failed") } 156 | } 157 | 158 | private func configRecord() { 159 | AVAudioSession.sharedInstance().requestRecordPermission { (allowed) in 160 | if !allowed { 161 | return 162 | } 163 | } 164 | let session = AVAudioSession.sharedInstance() 165 | do { try session.setCategory(AVAudioSessionCategoryPlayAndRecord, with: .defaultToSpeaker) } 166 | catch { print("session config failed") } 167 | do { 168 | self.recorder = try AVAudioRecorder(url: self.directoryURL()!, settings: self.recorderSetting) 169 | self.recorder.delegate = self 170 | self.recorder.prepareToRecord() 171 | self.recorder.isMeteringEnabled = true 172 | } catch { 173 | print(error.localizedDescription) 174 | } 175 | do { try AVAudioSession.sharedInstance().setActive(true) } 176 | catch { print("session active failed") } 177 | } 178 | 179 | private func directoryURL() -> URL? { 180 | //定义并构建一个url来保存音频,音频文件名为recording-yyyy-MM-dd-HH-mm-ss.m4a 181 | //根据时间来设置存储文件名 182 | let format = DateFormatter() 183 | format.dateFormat="yyyy-MM-dd-HH-mm-ss" 184 | let currentFileName = "recording-\(format.string(from: Date())).m4a" 185 | print(currentFileName) 186 | 187 | let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0] 188 | let soundFileURL = documentsDirectory.appendingPathComponent(currentFileName) 189 | return soundFileURL 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /MCChatHUD/MCChatHUD/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NSMicrophoneUsageDescription 6 | Use your microphone to test 7 | CFBundleDevelopmentRegion 8 | $(DEVELOPMENT_LANGUAGE) 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UISupportedInterfaceOrientations 34 | 35 | UIInterfaceOrientationPortrait 36 | UIInterfaceOrientationLandscapeLeft 37 | UIInterfaceOrientationLandscapeRight 38 | 39 | UISupportedInterfaceOrientations~ipad 40 | 41 | UIInterfaceOrientationPortrait 42 | UIInterfaceOrientationPortraitUpsideDown 43 | UIInterfaceOrientationLandscapeLeft 44 | UIInterfaceOrientationLandscapeRight 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /MCChatHUD/MCChatHUD/Marco/HUDMarco.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HUDMarco.swift 3 | // MCChatHUD 4 | // 5 | // Created by duwei on 2018/1/30. 6 | // Copyright © 2018年 Dywane. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | //MARK: - UI Marco 13 | 14 | /// 界面宽度 15 | internal let HUDWidth: CGFloat = 170 16 | /// 界面高度 17 | internal let HUDHeight: CGFloat = 78 18 | /// 界面圆角 19 | internal let HUDCornerRadius: CGFloat = 38 20 | 21 | /// 屏幕高度 22 | internal let ScreenHeight = UIScreen.main.bounds.height 23 | /// 屏幕宽度 24 | internal let ScreenWidth = UIScreen.main.bounds.width 25 | 26 | /// 音量计高度 27 | internal let VolumeViewHeight = 40 28 | /// 音量计宽度 29 | internal let VolumeViewWidth = 60 30 | -------------------------------------------------------------------------------- /MCChatHUD/MCChatHUD/View/MCProgressView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MCProgressView.swift 3 | // MCChatHUD 4 | // 5 | // Created by duwei on 2018/1/30. 6 | // Copyright © 2018年 Dywane. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class MCProgressView: UIImageView { 12 | 13 | //MARK: - Private Properties 14 | /// 进度条 15 | private var progressLayer: CAShapeLayer! 16 | /// 进度动画 17 | private var animation: CABasicAnimation! 18 | /// 高斯模糊层 19 | private var blurView: UIVisualEffectView! 20 | 21 | //MARK: - Init 22 | override init(frame: CGRect) { 23 | super.init(frame: frame) 24 | layer.cornerRadius = HUDCornerRadius 25 | clipsToBounds = true 26 | setUpBlurView() 27 | configAnimate() 28 | } 29 | 30 | required init?(coder aDecoder: NSCoder) { 31 | fatalError("init(coder:) has not been implemented") 32 | } 33 | } 34 | 35 | //MARK: - Animation 36 | extension MCProgressView { 37 | 38 | /// 开始计时动画 39 | public func countingAnimate() { 40 | progressLayer.add(animation, forKey: nil) 41 | } 42 | 43 | /// 停止计时动画 44 | public func stopAnimate() { 45 | progressLayer.removeAllAnimations() 46 | } 47 | } 48 | 49 | // MARK: - Setup 50 | extension MCProgressView { 51 | 52 | private func configAnimate() { 53 | let maskPath = UIBezierPath(roundedRect: CGRect.init(x: 0, y: 0, width: frame.width, height: frame.height), cornerRadius: HUDCornerRadius) 54 | let maskLayer = CAShapeLayer() 55 | maskLayer.backgroundColor = UIColor.clear.cgColor 56 | maskLayer.path = maskPath.cgPath 57 | maskLayer.frame = bounds 58 | 59 | // 进度路径 60 | /* 61 | 路径的中心为HUD的中心,宽度为HUD的高度,从左往右绘制 62 | */ 63 | let progressPath = CGMutablePath() 64 | progressPath.move(to: CGPoint(x: 0, y: frame.height / 2)) 65 | progressPath.addLine(to: CGPoint(x: frame.width, y: frame.height / 2)) 66 | 67 | progressLayer = CAShapeLayer() 68 | progressLayer.frame = bounds 69 | progressLayer.fillColor = UIColor.clear.cgColor //图层背景颜色 70 | progressLayer.strokeColor = UIColor(red: 0.29, green: 0.29, blue: 0.29, alpha: 0.90).cgColor //图层绘制颜色 71 | progressLayer.lineCap = kCALineCapButt 72 | progressLayer.lineWidth = HUDHeight 73 | progressLayer.path = progressPath 74 | progressLayer.mask = maskLayer 75 | 76 | blurView.contentView.layer.addSublayer(progressLayer) 77 | 78 | animation = CABasicAnimation(keyPath: "strokeEnd") 79 | animation.duration = 60 //最大录音时长 80 | animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear) //匀速前进 81 | animation.fillMode = kCAFillModeForwards 82 | animation.fromValue = 0.0 83 | animation.toValue = 1.0 84 | animation.autoreverses = false 85 | animation.repeatCount = 1 86 | } 87 | 88 | private func setUpBlurView() { 89 | let rect = CGRect(x: 0, y: 0, width: 1, height: 1) 90 | UIGraphicsBeginImageContextWithOptions(rect.size, false, 1.0) 91 | let context: CGContext? = UIGraphicsGetCurrentContext() 92 | context?.setFillColor(UIColor.black.withAlphaComponent(0.8).cgColor) 93 | context?.fill(rect) 94 | let transparentImage = UIGraphicsGetImageFromCurrentImageContext() 95 | UIGraphicsEndImageContext() 96 | image = transparentImage 97 | 98 | blurView = UIVisualEffectView(effect: UIBlurEffect(style: .regular)) 99 | blurView.frame = bounds 100 | blurView.autoresizingMask = [.flexibleWidth, .flexibleHeight] 101 | blurView.layer.cornerRadius = layer.cornerRadius 102 | blurView.clipsToBounds = true 103 | addSubview(blurView) 104 | } 105 | 106 | } 107 | -------------------------------------------------------------------------------- /MCChatHUD/MCChatHUD/View/MCRecordHUD.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MCRecordHUD.swift 3 | // MCChatHUD 4 | // 5 | // Created by duwei on 2018/1/30. 6 | // Copyright © 2018年 Dywane. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | /// HUD类型 12 | /// 13 | /// - bar: 条状 14 | /// - stroke: 线状 15 | enum HUDType: Int { 16 | case bar = 0 17 | case line 18 | } 19 | 20 | class MCRecordHUD: UIView { 21 | 22 | //MARK: - Public Properties 23 | /// 提示Label 24 | public let titleLabel = UILabel() 25 | 26 | //MARK: - Private Properties 27 | private let progress = MCProgressView(frame: CGRect(x: 0, y: 0, width: HUDWidth, height: HUDHeight)) 28 | private var volume: MCVolumeView! 29 | 30 | //MARK: Methods 31 | public func startCounting() { 32 | progress.countingAnimate() 33 | titleLabel.text = "Slide up to cancel" 34 | } 35 | 36 | public func stopCounting() { 37 | progress.stopAnimate() 38 | } 39 | 40 | //MARK: - Init 41 | 42 | convenience init(type: HUDType) { 43 | self.init(frame: .zero) 44 | self.frame.size.width = HUDWidth 45 | self.frame.size.height = HUDHeight 46 | center = CGPoint(x: ScreenWidth/2, y: ScreenHeight/2 - 50) 47 | backgroundColor = UIColor.clear 48 | addSubview(progress) 49 | 50 | volume = MCVolumeView(frame: CGRect(x: 56, y: 0, width: VolumeViewWidth, height: VolumeViewHeight), type: type) 51 | addSubview(volume) 52 | 53 | setUpLabel() 54 | addSubview(titleLabel) 55 | setUpShadow() 56 | } 57 | 58 | override private init(frame: CGRect) { 59 | super.init(frame: frame) 60 | } 61 | 62 | required init?(coder aDecoder: NSCoder) { 63 | fatalError("init(coder:) has not been implemented") 64 | } 65 | 66 | } 67 | 68 | // MARK: - Setup 69 | extension MCRecordHUD { 70 | 71 | private func setUpLabel() { 72 | titleLabel.textColor = UIColor.white 73 | titleLabel.font = UIFont.systemFont(ofSize: 12, weight: .bold) 74 | titleLabel.textAlignment = .center 75 | titleLabel.backgroundColor = UIColor.clear 76 | titleLabel.frame = CGRect(x: 25, y: 46, width: 120, height: 14) 77 | } 78 | 79 | private func setUpShadow() { 80 | 81 | let progessViewBounds = progress.frame 82 | let shadowWidth = progessViewBounds.size.width * 0.85 83 | let shadowHeight = progessViewBounds.size.height * 0.75 84 | 85 | let shadowPath = UIBezierPath(roundedRect: CGRect(x: progessViewBounds.origin.x + (progessViewBounds.width - shadowWidth) * 0.5, 86 | y: progessViewBounds.origin.y + 20, 87 | width: shadowWidth, 88 | height: shadowHeight), 89 | cornerRadius: progress.layer.cornerRadius) 90 | 91 | layer.shadowColor = UIColor(red: 0.29, green: 0.29, blue: 0.29, alpha: 1).cgColor 92 | layer.shadowPath = shadowPath.cgPath 93 | layer.shadowOpacity = 0.5 94 | layer.shadowRadius = 8 95 | layer.shadowOffset = CGSize(width: 0, height: 10) 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /MCChatHUD/MCChatHUD/View/MCVolumeView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MCVolumeView.swift 3 | // MCChatHUD 4 | // 5 | // Created by duwei on 2018/1/30. 6 | // Copyright © 2018年 Dywane. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class MCVolumeView: UIView { 12 | 13 | //MARK: Private Properties 14 | /// 声音表数组 15 | private var soundMeters: [Float]! 16 | 17 | private var type: HUDType = .bar 18 | 19 | //MARK: Init 20 | convenience init(frame: CGRect, type: HUDType) { 21 | self.init(frame: frame) 22 | self.type = type 23 | } 24 | 25 | override init(frame: CGRect) { 26 | super.init(frame: frame) 27 | backgroundColor = UIColor.clear 28 | contentMode = .redraw //内容模式为重绘,因为需要多次重复绘制音量表 29 | NotificationCenter.default.addObserver(self, selector: #selector(updateView(notice:)), name: NSNotification.Name.init("updateMeters"), object: nil) 30 | } 31 | 32 | required init?(coder aDecoder: NSCoder) { 33 | fatalError("init(coder:) has not been implemented") 34 | } 35 | 36 | override func draw(_ rect: CGRect) { 37 | if soundMeters != nil && soundMeters.count > 0 { 38 | let context = UIGraphicsGetCurrentContext() 39 | context?.setLineCap(.round) 40 | context?.setLineJoin(.round) 41 | context?.setStrokeColor(UIColor.white.cgColor) 42 | 43 | let noVoice = -46.0 // 该值代表低于-46.0的声音都认为无声音 44 | let maxVolume = 55.0 // 该值代表最高声音为55.0 45 | 46 | switch type { 47 | case .bar: 48 | context?.setLineWidth(3) 49 | for (index,item) in soundMeters.enumerated() { 50 | let barHeight = maxVolume - (Double(item) - noVoice) //通过当前声音表计算应该显示的声音表高度 51 | context?.move(to: CGPoint(x: index * 6 + 3, y: 40)) 52 | context?.addLine(to: CGPoint(x: index * 6 + 3, y: Int(barHeight))) 53 | } 54 | case .line: 55 | context?.setLineWidth(1.5) 56 | for (index, item) in soundMeters.enumerated() { 57 | let position = maxVolume - (Double(item) - noVoice) //计算对应线段高度 58 | context?.addLine(to: CGPoint(x: Double(index * 6 + 3), y: position)) 59 | context?.move(to: CGPoint(x: Double(index * 6 + 3), y: position)) 60 | } 61 | } 62 | context?.strokePath() 63 | } 64 | } 65 | 66 | @objc private func updateView(notice: Notification) { 67 | soundMeters = notice.object as! [Float] 68 | setNeedsDisplay() 69 | } 70 | 71 | } 72 | -------------------------------------------------------------------------------- /MCChatHUD/MCChatHUDTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /MCChatHUD/MCChatHUDTests/MCChatHUDTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MCChatHUDTests.swift 3 | // MCChatHUDTests 4 | // 5 | // Created by duwei on 2018/1/30. 6 | // Copyright © 2018年 Dywane. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import MCChatHUD 11 | 12 | class MCChatHUDTests: XCTestCase { 13 | 14 | override func setUp() { 15 | super.setUp() 16 | // Put setup code here. This method is called before the invocation of each test method in the class. 17 | } 18 | 19 | override func tearDown() { 20 | // Put teardown code here. This method is called after the invocation of each test method in the class. 21 | super.tearDown() 22 | } 23 | 24 | func testExample() { 25 | // This is an example of a functional test case. 26 | // Use XCTAssert and related functions to verify your tests produce the correct results. 27 | } 28 | 29 | func testPerformanceExample() { 30 | // This is an example of a performance test case. 31 | self.measure { 32 | // Put the code you want to measure the time of here. 33 | } 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /MCChatHUD/MCChatHUDUITests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /MCChatHUD/MCChatHUDUITests/MCChatHUDUITests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MCChatHUDUITests.swift 3 | // MCChatHUDUITests 4 | // 5 | // Created by duwei on 2018/1/30. 6 | // Copyright © 2018年 Dywane. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | class MCChatHUDUITests: XCTestCase { 12 | 13 | override func setUp() { 14 | super.setUp() 15 | 16 | // Put setup code here. This method is called before the invocation of each test method in the class. 17 | 18 | // In UI tests it is usually best to stop immediately when a failure occurs. 19 | continueAfterFailure = false 20 | // UI tests must launch the application that they test. Doing this in setup will make sure it happens for each test method. 21 | XCUIApplication().launch() 22 | 23 | // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this. 24 | } 25 | 26 | override func tearDown() { 27 | // Put teardown code here. This method is called after the invocation of each test method in the class. 28 | super.tearDown() 29 | } 30 | 31 | func testExample() { 32 | // Use recording to get started writing UI tests. 33 | // Use XCTAssert and related functions to verify your tests produce the correct results. 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MCChatHUD 2 | 3 | [MatchaSKD](https://itunes.apple.com/cn/app/matcha-schedule/id1310248785?mt=8)中录音波形图实现Demo,并增加新的样式。 4 | 5 | [文章地址](https://dywane.github.io/在iOS中绘制录音音频波形图/) 6 | 7 | # 效果图 8 | ![条状波形图](http://upload-images.jianshu.io/upload_images/4853563-cc1e5ca0e113e99e.gif?imageMogr2/auto-orient/strip) 9 | ![线装波形图](http://upload-images.jianshu.io/upload_images/4853563-3cac5c7d7410f808.gif?imageMogr2/auto-orient/strip) 10 | 11 | 12 | # 实现方式 13 | ### 配置AvAudioSession 14 | 绘制波形图前首先需要配置好`AVAudioSession`,同时需要建立一个数组去保存音量数据。 15 | 16 | #### 相关属性 17 | - **recorderSetting**用于设定录音音质等相关数据。 18 | - **timer**以及**updateFequency**用于定时更新波形图。 19 | - **soundMeter**和**soundMeterCount**用于保存音量表数组。 20 | - **recordTime**用于记录录音时间,可以用于判断录音时间是否达到要求等进一波需求。 21 | 22 | 23 | ```swift 24 | /// 录音器 25 | private var recorder: AVAudioRecorder! 26 | /// 录音器设置 27 | private let recorderSetting = [AVSampleRateKey : NSNumber(value: Float(44100.0)),//声音采样率 28 | AVFormatIDKey : NSNumber(value: Int32(kAudioFormatMPEG4AAC)),//编码格式 29 | AVNumberOfChannelsKey : NSNumber(value: 1),//采集音轨 30 | AVEncoderAudioQualityKey : NSNumber(value: Int32(AVAudioQuality.medium.rawValue))]//声音质量 31 | /// 录音计时器 32 | private var timer: Timer? 33 | /// 波形更新间隔 34 | private let updateFequency = 0.05 35 | /// 声音数据数组 36 | private var soundMeters: [Float]! 37 | /// 声音数据数组容量 38 | private let soundMeterCount = 10 39 | /// 录音时间 40 | private var recordTime = 0.00 41 | 42 | ``` 43 | #### AvAudioSession相关配置 44 | - **configAVAudioSession**用于配置`AVAudioSession`,其中`AVAudioSessionCategoryRecord`是代表仅仅利用这个session进行录音操作,而需要播放操作的话是可以设置成`AVAudioSessionCategoryPlayAndRecord`或`AVAudioSessionCategoryPlayBlack`,两者区别一个是可以录音和播放,另一个是可以在后台播放(即静音后仍然可以播放语音)。 45 | - **configRecord**是用于配置整个`AVAudioRecoder`,包括权限获取、代理源设置、是否记录音量表等。 46 | - **directoryURL**是用于配置文件保存地址。 47 | 48 | ```swift 49 | private func configAVAudioSession() { 50 | let session = AVAudioSession.sharedInstance() 51 | do { try session.setCategory(AVAudioSessionCategoryPlayAndRecord, with: .defaultToSpeaker) } 52 | catch { print("session config failed") } 53 | } 54 | 55 | 56 | private func configRecord() { 57 | AVAudioSession.sharedInstance().requestRecordPermission { (allowed) in 58 | if !allowed { 59 | return 60 | } 61 | } 62 | let session = AVAudioSession.sharedInstance() 63 | do { try session.setCategory(AVAudioSessionCategoryPlayAndRecord, with: .defaultToSpeaker) } 64 | catch { print("session config failed") } 65 | do { 66 | self.recorder = try AVAudioRecorder(url: self.directoryURL()!, settings: self.recorderSetting) 67 | self.recorder.delegate = self 68 | self.recorder.prepareToRecord() 69 | self.recorder.isMeteringEnabled = true 70 | } catch { 71 | print(error.localizedDescription) 72 | } 73 | do { try AVAudioSession.sharedInstance().setActive(true) } 74 | catch { print("session active failed") } 75 | } 76 | 77 | 78 | private func directoryURL() -> URL? { 79 | // do something ... 80 | return soundFileURL 81 | } 82 | ``` 83 | 84 | ### 记录音频数据 85 | 在开始录音后,利用我们刚刚配置的定时器不断获取`averagePower`,并保存到数组之中。 86 | 87 | - **updateMeters**被定时器调用,不断将recorder中记录的音量数据保存到soundMeter数组中。 88 | - **addSoundMeter**用于完成添加数据的工作。 89 | 90 | ```swift 91 | private func updateMeters() { 92 | recorder.updateMeters() 93 | recordTime += updateFequency 94 | addSoundMeter(item: recorder.averagePower(forChannel: 0)) 95 | } 96 | 97 | 98 | private func addSoundMeter(item: Float) { 99 | if soundMeters.count < soundMeterCount { 100 | soundMeters.append(item) 101 | } else { 102 | for (index, _) in soundMeters.enumerated() { 103 | if index < soundMeterCount - 1 { 104 | soundMeters[index] = soundMeters[index + 1] 105 | } 106 | } 107 | // 插入新数据 108 | soundMeters[soundMeterCount - 1] = item 109 | NotificationCenter.default.post(name: NSNotification.Name.init("updateMeters"), object: soundMeters) 110 | } 111 | } 112 | ``` 113 | 114 | ### 开始绘制波形图 115 | 现在我们已经获取了我们需要的所有数据,可以开始绘制波形图了。这时候让我们转到`MCVolumeView.swift`文件中,在上一个步骤中,我们发送了一条叫做`updateMeters`的通知,目的就是为了通知`MCVolumeView`进行波形图的更新。 116 | 117 | ```swift 118 | override init(frame: CGRect) { 119 | super.init(frame: frame) 120 | backgroundColor = UIColor.clear 121 | contentMode = .redraw //内容模式为重绘,因为需要多次重复绘制音量表 122 | NotificationCenter.default.addObserver(self, selector: #selector(updateView(notice:)), name: NSNotification.Name.init("updateMeters"), object: nil) 123 | } 124 | 125 | @objc private func updateView(notice: Notification) { 126 | soundMeters = notice.object as! [Float] 127 | setNeedsDisplay() 128 | } 129 | ``` 130 | 131 | 当`setNeedsDisplay`被调用之后,就会调用`drawRect`方法,在这里我们可以进行绘制波形图的操作。 132 | 133 | - **noVoice**和**maxVolume**是用于确保声音的显示范围 134 | - 波形图的绘制使用CGContext进行绘制,当然也可以使用UIBezierPath进行绘制。 135 | 136 | ```swift 137 | override func draw(_ rect: CGRect) { 138 | if soundMeters != nil && soundMeters.count > 0 { 139 | let context = UIGraphicsGetCurrentContext() 140 | context?.setLineCap(.round) 141 | context?.setLineJoin(.round) 142 | context?.setStrokeColor(UIColor.white.cgColor) 143 | 144 | let noVoice = -46.0 // 该值代表低于-46.0的声音都认为无声音 145 | let maxVolume = 55.0 // 该值代表最高声音为55.0 146 | 147 | // draw the volume... 148 | 149 | context?.strokePath() 150 | } 151 | } 152 | ``` 153 | 154 | ### 柱状波形图的绘制 155 | 156 | - 根据`maxVolume`和`noVoice`计算出每一条柱状的高度,并移动context所在的点进行绘制 157 | - 另外需要注意的是`CGContext`中坐标点时反转的,所以在进行计算时需要将坐标轴进行反转来计算。 158 | 159 | ```swift 160 | case .bar: 161 | context?.setLineWidth(3) 162 | for (index,item) in soundMeters.enumerated() { 163 | let barHeight = maxVolume - (Double(item) - noVoice) //通过当前声音表计算应该显示的声音表高度 164 | context?.move(to: CGPoint(x: index * 6 + 3, y: 40)) 165 | context?.addLine(to: CGPoint(x: index * 6 + 3, y: Int(barHeight))) 166 | } 167 | ``` 168 | 169 | ### 线状波形图的绘制 170 | 171 | - 线状与条状一样使用同样的方法计算“高度”,但是在绘制条状波形图时,是先画线,再移动,而绘制条状波形图时是先移动再画线。 172 | 173 | ```swift 174 | case .line: 175 | context?.setLineWidth(1.5) 176 | for (index, item) in soundMeters.enumerated() { 177 | let position = maxVolume - (Double(item) - noVoice) //计算对应线段高度 178 | context?.addLine(to: CGPoint(x: Double(index * 6 + 3), y: position)) 179 | context?.move(to: CGPoint(x: Double(index * 6 + 3), y: position)) 180 | } 181 | ``` 182 | 183 | ### 进一步完善我们的波形图 184 | 在很多时候,录音不单止是需要显示波形图,还需要我们展示目前录音的时间和进度,所以我们可以在波形图上添加录音的进度条,所以我们转向`MCProgressView.swift`文件进行操作。 185 | 186 | - 使用`UIBezierPath`配合`CAShapeLayer`进行绘制。 187 | - **maskPath**是作为整个进度路径的蒙版,因为我们的录音HUD不是规则的方形,所以需要使用蒙版进度路径进行裁剪。 188 | - **progressPath**为进度路径,进度的绘制方法为从左到右依次绘制。 189 | - **animation**是进度路径的绘制动画。 190 | 191 | ```swift 192 | private func configAnimate() { 193 | let maskPath = UIBezierPath(roundedRect: CGRect.init(x: 0, y: 0, width: frame.width, height: frame.height), cornerRadius: HUDCornerRadius) 194 | let maskLayer = CAShapeLayer() 195 | maskLayer.backgroundColor = UIColor.clear.cgColor 196 | maskLayer.path = maskPath.cgPath 197 | maskLayer.frame = bounds 198 | 199 | // 进度路径 200 | /* 201 | 路径的中心为HUD的中心,宽度为HUD的高度,从左往右绘制 202 | */ 203 | let progressPath = CGMutablePath() 204 | progressPath.move(to: CGPoint(x: 0, y: frame.height / 2)) 205 | progressPath.addLine(to: CGPoint(x: frame.width, y: frame.height / 2)) 206 | 207 | progressLayer = CAShapeLayer() 208 | progressLayer.frame = bounds 209 | progressLayer.fillColor = UIColor.clear.cgColor //图层背景颜色 210 | progressLayer.strokeColor = UIColor(red: 0.29, green: 0.29, blue: 0.29, alpha: 0.90).cgColor //图层绘制颜色 211 | progressLayer.lineCap = kCALineCapButt 212 | progressLayer.lineWidth = HUDHeight 213 | progressLayer.path = progressPath 214 | progressLayer.mask = maskLayer 215 | 216 | animation = CABasicAnimation(keyPath: "strokeEnd") 217 | animation.duration = 60 //最大录音时长 218 | animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear) //匀速前进 219 | animation.fillMode = kCAFillModeForwards 220 | animation.fromValue = 0.0 221 | animation.toValue = 1.0 222 | animation.autoreverses = false 223 | animation.repeatCount = 1 224 | } 225 | 226 | ``` 227 | 228 | # 需求环境 229 | - iOS 10.0 230 | - Swift 4.0 231 | 232 | # Contribution 233 | You are welcome to contribute to the project by forking the repo, modifying the code and opening issues or pull requests. 234 | --------------------------------------------------------------------------------