├── .DS_Store ├── .gitignore ├── AudioMixDemo.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── xcshareddata │ └── xcschemes │ └── AudioMixDemo.xcscheme ├── AudioMixDemo ├── Application │ ├── AppDelegate.swift │ └── SceneDelegate.swift ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ ├── Contents.json │ ├── ic_Sound.imageset │ │ ├── Contents.json │ │ └── ic_Sound.png │ └── microphone.imageset │ │ ├── Contents.json │ │ └── microphone.png ├── AudioMixDemo-Bridging-Header.h ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── Constants.swift ├── Info.plist ├── Library │ ├── AudioWaveForm │ │ ├── SCWaveformView.h │ │ ├── SCWaveformView.m │ │ ├── SYWaveformPlayerView.h │ │ ├── SYWaveformPlayerView.m │ │ ├── pausebutton.png │ │ └── playbutton.png │ └── UIControl │ │ ├── Extensions │ │ ├── Array+Extensions.swift │ │ ├── Common+Extensions.swift │ │ ├── Date+Extension.swift │ │ ├── Locale+Extensions.swift │ │ ├── Optional+Extensions.swift │ │ ├── String+Extensions.swift │ │ ├── UIAlertController+Extensions.swift │ │ ├── UIApplication+Extensions.swift │ │ ├── UIColor+Extensions.swift │ │ ├── UIImage+Extensions.swift │ │ ├── UIImageView+Extensions.swift │ │ ├── UISearchBar+Extensions.swift │ │ ├── UISegmentedControl+Extensions.swift │ │ ├── UITextField+Extensions.swift │ │ ├── UITextView+Extensions.swift │ │ ├── UIView+Extensions.swift │ │ └── UIViewController+Extensions.swift │ │ ├── Pickers │ │ ├── Date │ │ │ └── DatePickerViewController.swift │ │ ├── PickerView │ │ │ └── PickerViewViewController.swift │ │ └── TextFields │ │ │ ├── OneTextFieldViewController.swift │ │ │ └── TwoTextFieldsViewController.swift │ │ ├── Viewers │ │ ├── Models │ │ │ └── AttributedTextBlock.swift │ │ └── TextViewController.swift │ │ └── Views │ │ ├── Button.swift │ │ ├── GradientSlider.swift │ │ ├── Label.swift │ │ ├── SegmentedControl.swift │ │ └── TextField.swift ├── RippleLayer.swift └── ViewController │ ├── AudioMergeViewController.swift │ ├── AudioRecord2ViewController.swift │ └── ViewController.swift ├── LICENSE ├── README.md └── ScreenShort └── appScreenShort.png /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piyushghoghari08/AKAudioOverlap/f811282228cc3e2a17c377a38085e4408f8a2cbf/.DS_Store -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## User settings 6 | xcuserdata/ 7 | 8 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) 9 | *.xcscmblueprint 10 | *.xccheckout 11 | 12 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) 13 | build/ 14 | DerivedData/ 15 | *.moved-aside 16 | *.pbxuser 17 | !default.pbxuser 18 | *.mode1v3 19 | !default.mode1v3 20 | *.mode2v3 21 | !default.mode2v3 22 | *.perspectivev3 23 | !default.perspectivev3 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | 28 | ## App packaging 29 | *.ipa 30 | *.dSYM.zip 31 | *.dSYM 32 | 33 | ## Playgrounds 34 | timeline.xctimeline 35 | playground.xcworkspace 36 | 37 | # Swift Package Manager 38 | # 39 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 40 | # Packages/ 41 | # Package.pins 42 | # Package.resolved 43 | # *.xcodeproj 44 | # 45 | # Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata 46 | # hence it is not needed unless you have added a package configuration file to your project 47 | # .swiftpm 48 | 49 | .build/ 50 | 51 | # CocoaPods 52 | # 53 | # We recommend against adding the Pods directory to your .gitignore. However 54 | # you should judge for yourself, the pros and cons are mentioned at: 55 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 56 | # 57 | # Pods/ 58 | # 59 | # Add this line if you want to avoid checking in source code from the Xcode workspace 60 | # *.xcworkspace 61 | 62 | # Carthage 63 | # 64 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 65 | # Carthage/Checkouts 66 | 67 | Carthage/Build/ 68 | 69 | # Accio dependency management 70 | Dependencies/ 71 | .accio/ 72 | 73 | # fastlane 74 | # 75 | # It is recommended to not store the screenshots in the git repo. 76 | # Instead, use fastlane to re-generate the screenshots whenever they are needed. 77 | # For more information about the recommended setup visit: 78 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 79 | 80 | fastlane/report.xml 81 | fastlane/Preview.html 82 | fastlane/screenshots/**/*.png 83 | fastlane/test_output 84 | 85 | # Code Injection 86 | # 87 | # After new code Injection tools there's a generated folder /iOSInjectionProject 88 | # https://github.com/johnno1962/injectionforxcode 89 | 90 | iOSInjectionProject/ 91 | -------------------------------------------------------------------------------- /AudioMixDemo.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 21134415245DD3C6007FCF01 /* README.md in Resources */ = {isa = PBXBuildFile; fileRef = 21134414245DD3C6007FCF01 /* README.md */; }; 11 | 218A2E3F245006DC001D07E3 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 218A2E3E245006DC001D07E3 /* AppDelegate.swift */; }; 12 | 218A2E41245006DC001D07E3 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 218A2E40245006DC001D07E3 /* SceneDelegate.swift */; }; 13 | 218A2E43245006DC001D07E3 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 218A2E42245006DC001D07E3 /* ViewController.swift */; }; 14 | 218A2E46245006DC001D07E3 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 218A2E44245006DC001D07E3 /* Main.storyboard */; }; 15 | 218A2E48245006DF001D07E3 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 218A2E47245006DF001D07E3 /* Assets.xcassets */; }; 16 | 218A2E4B245006DF001D07E3 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 218A2E49245006DF001D07E3 /* LaunchScreen.storyboard */; }; 17 | 218A2E55245014EC001D07E3 /* RippleLayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 218A2E54245014EC001D07E3 /* RippleLayer.swift */; }; 18 | 218A2E6124501C29001D07E3 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 218A2E6024501C29001D07E3 /* Constants.swift */; }; 19 | 218A2E6824501E45001D07E3 /* playbutton.png in Resources */ = {isa = PBXBuildFile; fileRef = 218A2E6324501E45001D07E3 /* playbutton.png */; }; 20 | 218A2E6924501E45001D07E3 /* SYWaveformPlayerView.m in Sources */ = {isa = PBXBuildFile; fileRef = 218A2E6424501E45001D07E3 /* SYWaveformPlayerView.m */; }; 21 | 218A2E6A24501E45001D07E3 /* SCWaveformView.m in Sources */ = {isa = PBXBuildFile; fileRef = 218A2E6624501E45001D07E3 /* SCWaveformView.m */; }; 22 | 218A2E6B24501E45001D07E3 /* pausebutton.png in Resources */ = {isa = PBXBuildFile; fileRef = 218A2E6724501E45001D07E3 /* pausebutton.png */; }; 23 | 218A2E9224502119001D07E3 /* AttributedTextBlock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 218A2E7024502119001D07E3 /* AttributedTextBlock.swift */; }; 24 | 218A2E9324502119001D07E3 /* TextViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 218A2E7124502119001D07E3 /* TextViewController.swift */; }; 25 | 218A2E9424502119001D07E3 /* TwoTextFieldsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 218A2E7424502119001D07E3 /* TwoTextFieldsViewController.swift */; }; 26 | 218A2E9524502119001D07E3 /* OneTextFieldViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 218A2E7524502119001D07E3 /* OneTextFieldViewController.swift */; }; 27 | 218A2E9624502119001D07E3 /* PickerViewViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 218A2E7724502119001D07E3 /* PickerViewViewController.swift */; }; 28 | 218A2E9724502119001D07E3 /* DatePickerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 218A2E7924502119001D07E3 /* DatePickerViewController.swift */; }; 29 | 218A2E9824502119001D07E3 /* Array+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 218A2E7B24502119001D07E3 /* Array+Extensions.swift */; }; 30 | 218A2E9924502119001D07E3 /* UISegmentedControl+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 218A2E7C24502119001D07E3 /* UISegmentedControl+Extensions.swift */; }; 31 | 218A2E9A24502119001D07E3 /* UIImageView+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 218A2E7D24502119001D07E3 /* UIImageView+Extensions.swift */; }; 32 | 218A2E9B24502119001D07E3 /* UIViewController+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 218A2E7E24502119001D07E3 /* UIViewController+Extensions.swift */; }; 33 | 218A2E9C24502119001D07E3 /* UIView+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 218A2E7F24502119001D07E3 /* UIView+Extensions.swift */; }; 34 | 218A2E9D24502119001D07E3 /* Date+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 218A2E8024502119001D07E3 /* Date+Extension.swift */; }; 35 | 218A2E9E24502119001D07E3 /* String+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 218A2E8124502119001D07E3 /* String+Extensions.swift */; }; 36 | 218A2E9F24502119001D07E3 /* UITextView+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 218A2E8224502119001D07E3 /* UITextView+Extensions.swift */; }; 37 | 218A2EA024502119001D07E3 /* UIColor+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 218A2E8324502119001D07E3 /* UIColor+Extensions.swift */; }; 38 | 218A2EA124502119001D07E3 /* UIImage+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 218A2E8424502119001D07E3 /* UIImage+Extensions.swift */; }; 39 | 218A2EA224502119001D07E3 /* UISearchBar+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 218A2E8524502119001D07E3 /* UISearchBar+Extensions.swift */; }; 40 | 218A2EA324502119001D07E3 /* UIApplication+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 218A2E8624502119001D07E3 /* UIApplication+Extensions.swift */; }; 41 | 218A2EA424502119001D07E3 /* UIAlertController+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 218A2E8724502119001D07E3 /* UIAlertController+Extensions.swift */; }; 42 | 218A2EA524502119001D07E3 /* Optional+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 218A2E8824502119001D07E3 /* Optional+Extensions.swift */; }; 43 | 218A2EA624502119001D07E3 /* Common+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 218A2E8924502119001D07E3 /* Common+Extensions.swift */; }; 44 | 218A2EA724502119001D07E3 /* Locale+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 218A2E8A24502119001D07E3 /* Locale+Extensions.swift */; }; 45 | 218A2EA824502119001D07E3 /* UITextField+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 218A2E8B24502119001D07E3 /* UITextField+Extensions.swift */; }; 46 | 218A2EA924502119001D07E3 /* Label.swift in Sources */ = {isa = PBXBuildFile; fileRef = 218A2E8D24502119001D07E3 /* Label.swift */; }; 47 | 218A2EAA24502119001D07E3 /* SegmentedControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 218A2E8E24502119001D07E3 /* SegmentedControl.swift */; }; 48 | 218A2EAB24502119001D07E3 /* GradientSlider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 218A2E8F24502119001D07E3 /* GradientSlider.swift */; }; 49 | 218A2EAC24502119001D07E3 /* TextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 218A2E9024502119001D07E3 /* TextField.swift */; }; 50 | 218A2EAD24502119001D07E3 /* Button.swift in Sources */ = {isa = PBXBuildFile; fileRef = 218A2E9124502119001D07E3 /* Button.swift */; }; 51 | 218A2EAF24502C35001D07E3 /* AudioRecord2ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 218A2EAE24502C35001D07E3 /* AudioRecord2ViewController.swift */; }; 52 | 218A2EB2245032A0001D07E3 /* AudioMergeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 218A2EB1245032A0001D07E3 /* AudioMergeViewController.swift */; }; 53 | /* End PBXBuildFile section */ 54 | 55 | /* Begin PBXFileReference section */ 56 | 21134414245DD3C6007FCF01 /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; 57 | 218A2E3B245006DC001D07E3 /* AKAudioOverlap.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = AKAudioOverlap.app; sourceTree = BUILT_PRODUCTS_DIR; }; 58 | 218A2E3E245006DC001D07E3 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 59 | 218A2E40245006DC001D07E3 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; 60 | 218A2E42245006DC001D07E3 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 61 | 218A2E45245006DC001D07E3 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 62 | 218A2E47245006DF001D07E3 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 63 | 218A2E4A245006DF001D07E3 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 64 | 218A2E4C245006DF001D07E3 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 65 | 218A2E54245014EC001D07E3 /* RippleLayer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RippleLayer.swift; sourceTree = ""; }; 66 | 218A2E5624501B99001D07E3 /* AudioMixDemo-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "AudioMixDemo-Bridging-Header.h"; sourceTree = ""; }; 67 | 218A2E6024501C29001D07E3 /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = ""; }; 68 | 218A2E6224501E45001D07E3 /* SYWaveformPlayerView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SYWaveformPlayerView.h; sourceTree = ""; }; 69 | 218A2E6324501E45001D07E3 /* playbutton.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = playbutton.png; sourceTree = ""; }; 70 | 218A2E6424501E45001D07E3 /* SYWaveformPlayerView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SYWaveformPlayerView.m; sourceTree = ""; }; 71 | 218A2E6524501E45001D07E3 /* SCWaveformView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SCWaveformView.h; sourceTree = ""; }; 72 | 218A2E6624501E45001D07E3 /* SCWaveformView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SCWaveformView.m; sourceTree = ""; }; 73 | 218A2E6724501E45001D07E3 /* pausebutton.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = pausebutton.png; sourceTree = ""; }; 74 | 218A2E7024502119001D07E3 /* AttributedTextBlock.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AttributedTextBlock.swift; sourceTree = ""; }; 75 | 218A2E7124502119001D07E3 /* TextViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TextViewController.swift; sourceTree = ""; }; 76 | 218A2E7424502119001D07E3 /* TwoTextFieldsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TwoTextFieldsViewController.swift; sourceTree = ""; }; 77 | 218A2E7524502119001D07E3 /* OneTextFieldViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OneTextFieldViewController.swift; sourceTree = ""; }; 78 | 218A2E7724502119001D07E3 /* PickerViewViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PickerViewViewController.swift; sourceTree = ""; }; 79 | 218A2E7924502119001D07E3 /* DatePickerViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DatePickerViewController.swift; sourceTree = ""; }; 80 | 218A2E7B24502119001D07E3 /* Array+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Array+Extensions.swift"; sourceTree = ""; }; 81 | 218A2E7C24502119001D07E3 /* UISegmentedControl+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UISegmentedControl+Extensions.swift"; sourceTree = ""; }; 82 | 218A2E7D24502119001D07E3 /* UIImageView+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIImageView+Extensions.swift"; sourceTree = ""; }; 83 | 218A2E7E24502119001D07E3 /* UIViewController+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIViewController+Extensions.swift"; sourceTree = ""; }; 84 | 218A2E7F24502119001D07E3 /* UIView+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIView+Extensions.swift"; sourceTree = ""; }; 85 | 218A2E8024502119001D07E3 /* Date+Extension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Date+Extension.swift"; sourceTree = ""; }; 86 | 218A2E8124502119001D07E3 /* String+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "String+Extensions.swift"; sourceTree = ""; }; 87 | 218A2E8224502119001D07E3 /* UITextView+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UITextView+Extensions.swift"; sourceTree = ""; }; 88 | 218A2E8324502119001D07E3 /* UIColor+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIColor+Extensions.swift"; sourceTree = ""; }; 89 | 218A2E8424502119001D07E3 /* UIImage+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIImage+Extensions.swift"; sourceTree = ""; }; 90 | 218A2E8524502119001D07E3 /* UISearchBar+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UISearchBar+Extensions.swift"; sourceTree = ""; }; 91 | 218A2E8624502119001D07E3 /* UIApplication+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIApplication+Extensions.swift"; sourceTree = ""; }; 92 | 218A2E8724502119001D07E3 /* UIAlertController+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIAlertController+Extensions.swift"; sourceTree = ""; }; 93 | 218A2E8824502119001D07E3 /* Optional+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Optional+Extensions.swift"; sourceTree = ""; }; 94 | 218A2E8924502119001D07E3 /* Common+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Common+Extensions.swift"; sourceTree = ""; }; 95 | 218A2E8A24502119001D07E3 /* Locale+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Locale+Extensions.swift"; sourceTree = ""; }; 96 | 218A2E8B24502119001D07E3 /* UITextField+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UITextField+Extensions.swift"; sourceTree = ""; }; 97 | 218A2E8D24502119001D07E3 /* Label.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Label.swift; sourceTree = ""; }; 98 | 218A2E8E24502119001D07E3 /* SegmentedControl.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SegmentedControl.swift; sourceTree = ""; }; 99 | 218A2E8F24502119001D07E3 /* GradientSlider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GradientSlider.swift; sourceTree = ""; }; 100 | 218A2E9024502119001D07E3 /* TextField.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TextField.swift; sourceTree = ""; }; 101 | 218A2E9124502119001D07E3 /* Button.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Button.swift; sourceTree = ""; }; 102 | 218A2EAE24502C35001D07E3 /* AudioRecord2ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioRecord2ViewController.swift; sourceTree = ""; }; 103 | 218A2EB1245032A0001D07E3 /* AudioMergeViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioMergeViewController.swift; sourceTree = ""; }; 104 | /* End PBXFileReference section */ 105 | 106 | /* Begin PBXFrameworksBuildPhase section */ 107 | 218A2E38245006DC001D07E3 /* Frameworks */ = { 108 | isa = PBXFrameworksBuildPhase; 109 | buildActionMask = 2147483647; 110 | files = ( 111 | ); 112 | runOnlyForDeploymentPostprocessing = 0; 113 | }; 114 | /* End PBXFrameworksBuildPhase section */ 115 | 116 | /* Begin PBXGroup section */ 117 | 218A2E32245006DC001D07E3 = { 118 | isa = PBXGroup; 119 | children = ( 120 | 21134414245DD3C6007FCF01 /* README.md */, 121 | 218A2E3D245006DC001D07E3 /* AudioMixDemo */, 122 | 218A2E3C245006DC001D07E3 /* Products */, 123 | ); 124 | sourceTree = ""; 125 | }; 126 | 218A2E3C245006DC001D07E3 /* Products */ = { 127 | isa = PBXGroup; 128 | children = ( 129 | 218A2E3B245006DC001D07E3 /* AKAudioOverlap.app */, 130 | ); 131 | name = Products; 132 | sourceTree = ""; 133 | }; 134 | 218A2E3D245006DC001D07E3 /* AudioMixDemo */ = { 135 | isa = PBXGroup; 136 | children = ( 137 | 218A2E6C245020FE001D07E3 /* Library */, 138 | 218A2E5324500928001D07E3 /* Application */, 139 | 218A2E52245008E6001D07E3 /* ViewController */, 140 | 218A2E44245006DC001D07E3 /* Main.storyboard */, 141 | 218A2E47245006DF001D07E3 /* Assets.xcassets */, 142 | 218A2E49245006DF001D07E3 /* LaunchScreen.storyboard */, 143 | 218A2E4C245006DF001D07E3 /* Info.plist */, 144 | 218A2E54245014EC001D07E3 /* RippleLayer.swift */, 145 | 218A2E5624501B99001D07E3 /* AudioMixDemo-Bridging-Header.h */, 146 | 218A2E6024501C29001D07E3 /* Constants.swift */, 147 | ); 148 | path = AudioMixDemo; 149 | sourceTree = ""; 150 | }; 151 | 218A2E52245008E6001D07E3 /* ViewController */ = { 152 | isa = PBXGroup; 153 | children = ( 154 | 218A2E42245006DC001D07E3 /* ViewController.swift */, 155 | 218A2EAE24502C35001D07E3 /* AudioRecord2ViewController.swift */, 156 | 218A2EB1245032A0001D07E3 /* AudioMergeViewController.swift */, 157 | ); 158 | path = ViewController; 159 | sourceTree = ""; 160 | }; 161 | 218A2E5324500928001D07E3 /* Application */ = { 162 | isa = PBXGroup; 163 | children = ( 164 | 218A2E3E245006DC001D07E3 /* AppDelegate.swift */, 165 | 218A2E40245006DC001D07E3 /* SceneDelegate.swift */, 166 | ); 167 | path = Application; 168 | sourceTree = ""; 169 | }; 170 | 218A2E5924501BB6001D07E3 /* AudioWaveForm */ = { 171 | isa = PBXGroup; 172 | children = ( 173 | 218A2E6724501E45001D07E3 /* pausebutton.png */, 174 | 218A2E6324501E45001D07E3 /* playbutton.png */, 175 | 218A2E6524501E45001D07E3 /* SCWaveformView.h */, 176 | 218A2E6624501E45001D07E3 /* SCWaveformView.m */, 177 | 218A2E6224501E45001D07E3 /* SYWaveformPlayerView.h */, 178 | 218A2E6424501E45001D07E3 /* SYWaveformPlayerView.m */, 179 | ); 180 | path = AudioWaveForm; 181 | sourceTree = ""; 182 | }; 183 | 218A2E6C245020FE001D07E3 /* Library */ = { 184 | isa = PBXGroup; 185 | children = ( 186 | 218A2E6D2450210D001D07E3 /* UIControl */, 187 | 218A2E5924501BB6001D07E3 /* AudioWaveForm */, 188 | ); 189 | path = Library; 190 | sourceTree = ""; 191 | }; 192 | 218A2E6D2450210D001D07E3 /* UIControl */ = { 193 | isa = PBXGroup; 194 | children = ( 195 | 218A2E7A24502119001D07E3 /* Extensions */, 196 | 218A2E7224502119001D07E3 /* Pickers */, 197 | 218A2E6E24502119001D07E3 /* Viewers */, 198 | 218A2E8C24502119001D07E3 /* Views */, 199 | ); 200 | path = UIControl; 201 | sourceTree = ""; 202 | }; 203 | 218A2E6E24502119001D07E3 /* Viewers */ = { 204 | isa = PBXGroup; 205 | children = ( 206 | 218A2E6F24502119001D07E3 /* Models */, 207 | 218A2E7124502119001D07E3 /* TextViewController.swift */, 208 | ); 209 | path = Viewers; 210 | sourceTree = ""; 211 | }; 212 | 218A2E6F24502119001D07E3 /* Models */ = { 213 | isa = PBXGroup; 214 | children = ( 215 | 218A2E7024502119001D07E3 /* AttributedTextBlock.swift */, 216 | ); 217 | path = Models; 218 | sourceTree = ""; 219 | }; 220 | 218A2E7224502119001D07E3 /* Pickers */ = { 221 | isa = PBXGroup; 222 | children = ( 223 | 218A2E7324502119001D07E3 /* TextFields */, 224 | 218A2E7624502119001D07E3 /* PickerView */, 225 | 218A2E7824502119001D07E3 /* Date */, 226 | ); 227 | path = Pickers; 228 | sourceTree = ""; 229 | }; 230 | 218A2E7324502119001D07E3 /* TextFields */ = { 231 | isa = PBXGroup; 232 | children = ( 233 | 218A2E7424502119001D07E3 /* TwoTextFieldsViewController.swift */, 234 | 218A2E7524502119001D07E3 /* OneTextFieldViewController.swift */, 235 | ); 236 | path = TextFields; 237 | sourceTree = ""; 238 | }; 239 | 218A2E7624502119001D07E3 /* PickerView */ = { 240 | isa = PBXGroup; 241 | children = ( 242 | 218A2E7724502119001D07E3 /* PickerViewViewController.swift */, 243 | ); 244 | path = PickerView; 245 | sourceTree = ""; 246 | }; 247 | 218A2E7824502119001D07E3 /* Date */ = { 248 | isa = PBXGroup; 249 | children = ( 250 | 218A2E7924502119001D07E3 /* DatePickerViewController.swift */, 251 | ); 252 | path = Date; 253 | sourceTree = ""; 254 | }; 255 | 218A2E7A24502119001D07E3 /* Extensions */ = { 256 | isa = PBXGroup; 257 | children = ( 258 | 218A2E7B24502119001D07E3 /* Array+Extensions.swift */, 259 | 218A2E7C24502119001D07E3 /* UISegmentedControl+Extensions.swift */, 260 | 218A2E7D24502119001D07E3 /* UIImageView+Extensions.swift */, 261 | 218A2E7E24502119001D07E3 /* UIViewController+Extensions.swift */, 262 | 218A2E7F24502119001D07E3 /* UIView+Extensions.swift */, 263 | 218A2E8024502119001D07E3 /* Date+Extension.swift */, 264 | 218A2E8124502119001D07E3 /* String+Extensions.swift */, 265 | 218A2E8224502119001D07E3 /* UITextView+Extensions.swift */, 266 | 218A2E8324502119001D07E3 /* UIColor+Extensions.swift */, 267 | 218A2E8424502119001D07E3 /* UIImage+Extensions.swift */, 268 | 218A2E8524502119001D07E3 /* UISearchBar+Extensions.swift */, 269 | 218A2E8624502119001D07E3 /* UIApplication+Extensions.swift */, 270 | 218A2E8724502119001D07E3 /* UIAlertController+Extensions.swift */, 271 | 218A2E8824502119001D07E3 /* Optional+Extensions.swift */, 272 | 218A2E8924502119001D07E3 /* Common+Extensions.swift */, 273 | 218A2E8A24502119001D07E3 /* Locale+Extensions.swift */, 274 | 218A2E8B24502119001D07E3 /* UITextField+Extensions.swift */, 275 | ); 276 | path = Extensions; 277 | sourceTree = ""; 278 | }; 279 | 218A2E8C24502119001D07E3 /* Views */ = { 280 | isa = PBXGroup; 281 | children = ( 282 | 218A2E8D24502119001D07E3 /* Label.swift */, 283 | 218A2E8E24502119001D07E3 /* SegmentedControl.swift */, 284 | 218A2E8F24502119001D07E3 /* GradientSlider.swift */, 285 | 218A2E9024502119001D07E3 /* TextField.swift */, 286 | 218A2E9124502119001D07E3 /* Button.swift */, 287 | ); 288 | path = Views; 289 | sourceTree = ""; 290 | }; 291 | /* End PBXGroup section */ 292 | 293 | /* Begin PBXNativeTarget section */ 294 | 218A2E3A245006DC001D07E3 /* AudioMixDemo */ = { 295 | isa = PBXNativeTarget; 296 | buildConfigurationList = 218A2E4F245006DF001D07E3 /* Build configuration list for PBXNativeTarget "AudioMixDemo" */; 297 | buildPhases = ( 298 | 218A2E37245006DC001D07E3 /* Sources */, 299 | 218A2E38245006DC001D07E3 /* Frameworks */, 300 | 218A2E39245006DC001D07E3 /* Resources */, 301 | ); 302 | buildRules = ( 303 | ); 304 | dependencies = ( 305 | ); 306 | name = AudioMixDemo; 307 | productName = AudioMixDemo; 308 | productReference = 218A2E3B245006DC001D07E3 /* AKAudioOverlap.app */; 309 | productType = "com.apple.product-type.application"; 310 | }; 311 | /* End PBXNativeTarget section */ 312 | 313 | /* Begin PBXProject section */ 314 | 218A2E33245006DC001D07E3 /* Project object */ = { 315 | isa = PBXProject; 316 | attributes = { 317 | LastSwiftUpdateCheck = 1140; 318 | LastUpgradeCheck = 1140; 319 | ORGANIZATIONNAME = "PIYUSH GHOGHARI"; 320 | TargetAttributes = { 321 | 218A2E3A245006DC001D07E3 = { 322 | CreatedOnToolsVersion = 11.4; 323 | LastSwiftMigration = 1140; 324 | }; 325 | }; 326 | }; 327 | buildConfigurationList = 218A2E36245006DC001D07E3 /* Build configuration list for PBXProject "AudioMixDemo" */; 328 | compatibilityVersion = "Xcode 9.3"; 329 | developmentRegion = en; 330 | hasScannedForEncodings = 0; 331 | knownRegions = ( 332 | en, 333 | Base, 334 | ); 335 | mainGroup = 218A2E32245006DC001D07E3; 336 | productRefGroup = 218A2E3C245006DC001D07E3 /* Products */; 337 | projectDirPath = ""; 338 | projectRoot = ""; 339 | targets = ( 340 | 218A2E3A245006DC001D07E3 /* AudioMixDemo */, 341 | ); 342 | }; 343 | /* End PBXProject section */ 344 | 345 | /* Begin PBXResourcesBuildPhase section */ 346 | 218A2E39245006DC001D07E3 /* Resources */ = { 347 | isa = PBXResourcesBuildPhase; 348 | buildActionMask = 2147483647; 349 | files = ( 350 | 21134415245DD3C6007FCF01 /* README.md in Resources */, 351 | 218A2E4B245006DF001D07E3 /* LaunchScreen.storyboard in Resources */, 352 | 218A2E48245006DF001D07E3 /* Assets.xcassets in Resources */, 353 | 218A2E46245006DC001D07E3 /* Main.storyboard in Resources */, 354 | 218A2E6B24501E45001D07E3 /* pausebutton.png in Resources */, 355 | 218A2E6824501E45001D07E3 /* playbutton.png in Resources */, 356 | ); 357 | runOnlyForDeploymentPostprocessing = 0; 358 | }; 359 | /* End PBXResourcesBuildPhase section */ 360 | 361 | /* Begin PBXSourcesBuildPhase section */ 362 | 218A2E37245006DC001D07E3 /* Sources */ = { 363 | isa = PBXSourcesBuildPhase; 364 | buildActionMask = 2147483647; 365 | files = ( 366 | 218A2EAD24502119001D07E3 /* Button.swift in Sources */, 367 | 218A2EA724502119001D07E3 /* Locale+Extensions.swift in Sources */, 368 | 218A2E9E24502119001D07E3 /* String+Extensions.swift in Sources */, 369 | 218A2E9F24502119001D07E3 /* UITextView+Extensions.swift in Sources */, 370 | 218A2EA824502119001D07E3 /* UITextField+Extensions.swift in Sources */, 371 | 218A2E6924501E45001D07E3 /* SYWaveformPlayerView.m in Sources */, 372 | 218A2EA224502119001D07E3 /* UISearchBar+Extensions.swift in Sources */, 373 | 218A2E9724502119001D07E3 /* DatePickerViewController.swift in Sources */, 374 | 218A2EAB24502119001D07E3 /* GradientSlider.swift in Sources */, 375 | 218A2E9924502119001D07E3 /* UISegmentedControl+Extensions.swift in Sources */, 376 | 218A2E9524502119001D07E3 /* OneTextFieldViewController.swift in Sources */, 377 | 218A2E9824502119001D07E3 /* Array+Extensions.swift in Sources */, 378 | 218A2E9B24502119001D07E3 /* UIViewController+Extensions.swift in Sources */, 379 | 218A2E6A24501E45001D07E3 /* SCWaveformView.m in Sources */, 380 | 218A2EB2245032A0001D07E3 /* AudioMergeViewController.swift in Sources */, 381 | 218A2EA324502119001D07E3 /* UIApplication+Extensions.swift in Sources */, 382 | 218A2E43245006DC001D07E3 /* ViewController.swift in Sources */, 383 | 218A2EAA24502119001D07E3 /* SegmentedControl.swift in Sources */, 384 | 218A2E55245014EC001D07E3 /* RippleLayer.swift in Sources */, 385 | 218A2EAF24502C35001D07E3 /* AudioRecord2ViewController.swift in Sources */, 386 | 218A2E9624502119001D07E3 /* PickerViewViewController.swift in Sources */, 387 | 218A2EA624502119001D07E3 /* Common+Extensions.swift in Sources */, 388 | 218A2EA924502119001D07E3 /* Label.swift in Sources */, 389 | 218A2E9224502119001D07E3 /* AttributedTextBlock.swift in Sources */, 390 | 218A2E9424502119001D07E3 /* TwoTextFieldsViewController.swift in Sources */, 391 | 218A2EA524502119001D07E3 /* Optional+Extensions.swift in Sources */, 392 | 218A2EAC24502119001D07E3 /* TextField.swift in Sources */, 393 | 218A2E9C24502119001D07E3 /* UIView+Extensions.swift in Sources */, 394 | 218A2E9324502119001D07E3 /* TextViewController.swift in Sources */, 395 | 218A2EA124502119001D07E3 /* UIImage+Extensions.swift in Sources */, 396 | 218A2E3F245006DC001D07E3 /* AppDelegate.swift in Sources */, 397 | 218A2EA424502119001D07E3 /* UIAlertController+Extensions.swift in Sources */, 398 | 218A2E6124501C29001D07E3 /* Constants.swift in Sources */, 399 | 218A2E9A24502119001D07E3 /* UIImageView+Extensions.swift in Sources */, 400 | 218A2EA024502119001D07E3 /* UIColor+Extensions.swift in Sources */, 401 | 218A2E9D24502119001D07E3 /* Date+Extension.swift in Sources */, 402 | 218A2E41245006DC001D07E3 /* SceneDelegate.swift in Sources */, 403 | ); 404 | runOnlyForDeploymentPostprocessing = 0; 405 | }; 406 | /* End PBXSourcesBuildPhase section */ 407 | 408 | /* Begin PBXVariantGroup section */ 409 | 218A2E44245006DC001D07E3 /* Main.storyboard */ = { 410 | isa = PBXVariantGroup; 411 | children = ( 412 | 218A2E45245006DC001D07E3 /* Base */, 413 | ); 414 | name = Main.storyboard; 415 | sourceTree = ""; 416 | }; 417 | 218A2E49245006DF001D07E3 /* LaunchScreen.storyboard */ = { 418 | isa = PBXVariantGroup; 419 | children = ( 420 | 218A2E4A245006DF001D07E3 /* Base */, 421 | ); 422 | name = LaunchScreen.storyboard; 423 | sourceTree = ""; 424 | }; 425 | /* End PBXVariantGroup section */ 426 | 427 | /* Begin XCBuildConfiguration section */ 428 | 218A2E4D245006DF001D07E3 /* Debug */ = { 429 | isa = XCBuildConfiguration; 430 | buildSettings = { 431 | ALWAYS_SEARCH_USER_PATHS = NO; 432 | CLANG_ANALYZER_NONNULL = YES; 433 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 434 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 435 | CLANG_CXX_LIBRARY = "libc++"; 436 | CLANG_ENABLE_MODULES = YES; 437 | CLANG_ENABLE_OBJC_ARC = YES; 438 | CLANG_ENABLE_OBJC_WEAK = YES; 439 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 440 | CLANG_WARN_BOOL_CONVERSION = YES; 441 | CLANG_WARN_COMMA = YES; 442 | CLANG_WARN_CONSTANT_CONVERSION = YES; 443 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 444 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 445 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 446 | CLANG_WARN_EMPTY_BODY = YES; 447 | CLANG_WARN_ENUM_CONVERSION = YES; 448 | CLANG_WARN_INFINITE_RECURSION = YES; 449 | CLANG_WARN_INT_CONVERSION = YES; 450 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 451 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 452 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 453 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 454 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 455 | CLANG_WARN_STRICT_PROTOTYPES = YES; 456 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 457 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 458 | CLANG_WARN_UNREACHABLE_CODE = YES; 459 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 460 | COPY_PHASE_STRIP = NO; 461 | DEBUG_INFORMATION_FORMAT = dwarf; 462 | ENABLE_STRICT_OBJC_MSGSEND = YES; 463 | ENABLE_TESTABILITY = YES; 464 | GCC_C_LANGUAGE_STANDARD = gnu11; 465 | GCC_DYNAMIC_NO_PIC = NO; 466 | GCC_NO_COMMON_BLOCKS = YES; 467 | GCC_OPTIMIZATION_LEVEL = 0; 468 | GCC_PREPROCESSOR_DEFINITIONS = ( 469 | "DEBUG=1", 470 | "$(inherited)", 471 | ); 472 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 473 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 474 | GCC_WARN_UNDECLARED_SELECTOR = YES; 475 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 476 | GCC_WARN_UNUSED_FUNCTION = YES; 477 | GCC_WARN_UNUSED_VARIABLE = YES; 478 | IPHONEOS_DEPLOYMENT_TARGET = 13.4; 479 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 480 | MTL_FAST_MATH = YES; 481 | ONLY_ACTIVE_ARCH = YES; 482 | SDKROOT = iphoneos; 483 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 484 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 485 | }; 486 | name = Debug; 487 | }; 488 | 218A2E4E245006DF001D07E3 /* Release */ = { 489 | isa = XCBuildConfiguration; 490 | buildSettings = { 491 | ALWAYS_SEARCH_USER_PATHS = NO; 492 | CLANG_ANALYZER_NONNULL = YES; 493 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 494 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 495 | CLANG_CXX_LIBRARY = "libc++"; 496 | CLANG_ENABLE_MODULES = YES; 497 | CLANG_ENABLE_OBJC_ARC = YES; 498 | CLANG_ENABLE_OBJC_WEAK = YES; 499 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 500 | CLANG_WARN_BOOL_CONVERSION = YES; 501 | CLANG_WARN_COMMA = YES; 502 | CLANG_WARN_CONSTANT_CONVERSION = YES; 503 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 504 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 505 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 506 | CLANG_WARN_EMPTY_BODY = YES; 507 | CLANG_WARN_ENUM_CONVERSION = YES; 508 | CLANG_WARN_INFINITE_RECURSION = YES; 509 | CLANG_WARN_INT_CONVERSION = YES; 510 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 511 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 512 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 513 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 514 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 515 | CLANG_WARN_STRICT_PROTOTYPES = YES; 516 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 517 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 518 | CLANG_WARN_UNREACHABLE_CODE = YES; 519 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 520 | COPY_PHASE_STRIP = NO; 521 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 522 | ENABLE_NS_ASSERTIONS = NO; 523 | ENABLE_STRICT_OBJC_MSGSEND = YES; 524 | GCC_C_LANGUAGE_STANDARD = gnu11; 525 | GCC_NO_COMMON_BLOCKS = YES; 526 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 527 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 528 | GCC_WARN_UNDECLARED_SELECTOR = YES; 529 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 530 | GCC_WARN_UNUSED_FUNCTION = YES; 531 | GCC_WARN_UNUSED_VARIABLE = YES; 532 | IPHONEOS_DEPLOYMENT_TARGET = 13.4; 533 | MTL_ENABLE_DEBUG_INFO = NO; 534 | MTL_FAST_MATH = YES; 535 | SDKROOT = iphoneos; 536 | SWIFT_COMPILATION_MODE = wholemodule; 537 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 538 | VALIDATE_PRODUCT = YES; 539 | }; 540 | name = Release; 541 | }; 542 | 218A2E50245006DF001D07E3 /* Debug */ = { 543 | isa = XCBuildConfiguration; 544 | buildSettings = { 545 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 546 | CLANG_ENABLE_MODULES = YES; 547 | CODE_SIGN_IDENTITY = "Apple Development"; 548 | CODE_SIGN_STYLE = Automatic; 549 | DEVELOPMENT_TEAM = D7234C6WKJ; 550 | INFOPLIST_FILE = AudioMixDemo/Info.plist; 551 | IPHONEOS_DEPLOYMENT_TARGET = 12.0; 552 | LD_RUNPATH_SEARCH_PATHS = ( 553 | "$(inherited)", 554 | "@executable_path/Frameworks", 555 | ); 556 | PRODUCT_BUNDLE_IDENTIFIER = com.iTouchSoluation.AudioMixDemo; 557 | PRODUCT_NAME = AKAudioOverlap; 558 | PROVISIONING_PROFILE_SPECIFIER = ""; 559 | SWIFT_OBJC_BRIDGING_HEADER = "AudioMixDemo/AudioMixDemo-Bridging-Header.h"; 560 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 561 | SWIFT_VERSION = 5.0; 562 | TARGETED_DEVICE_FAMILY = 1; 563 | }; 564 | name = Debug; 565 | }; 566 | 218A2E51245006DF001D07E3 /* Release */ = { 567 | isa = XCBuildConfiguration; 568 | buildSettings = { 569 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 570 | CLANG_ENABLE_MODULES = YES; 571 | CODE_SIGN_IDENTITY = "Apple Development"; 572 | CODE_SIGN_STYLE = Automatic; 573 | DEVELOPMENT_TEAM = D7234C6WKJ; 574 | INFOPLIST_FILE = AudioMixDemo/Info.plist; 575 | IPHONEOS_DEPLOYMENT_TARGET = 12.0; 576 | LD_RUNPATH_SEARCH_PATHS = ( 577 | "$(inherited)", 578 | "@executable_path/Frameworks", 579 | ); 580 | PRODUCT_BUNDLE_IDENTIFIER = com.iTouchSoluation.AudioMixDemo; 581 | PRODUCT_NAME = AKAudioOverlap; 582 | PROVISIONING_PROFILE_SPECIFIER = ""; 583 | SWIFT_OBJC_BRIDGING_HEADER = "AudioMixDemo/AudioMixDemo-Bridging-Header.h"; 584 | SWIFT_VERSION = 5.0; 585 | TARGETED_DEVICE_FAMILY = 1; 586 | }; 587 | name = Release; 588 | }; 589 | /* End XCBuildConfiguration section */ 590 | 591 | /* Begin XCConfigurationList section */ 592 | 218A2E36245006DC001D07E3 /* Build configuration list for PBXProject "AudioMixDemo" */ = { 593 | isa = XCConfigurationList; 594 | buildConfigurations = ( 595 | 218A2E4D245006DF001D07E3 /* Debug */, 596 | 218A2E4E245006DF001D07E3 /* Release */, 597 | ); 598 | defaultConfigurationIsVisible = 0; 599 | defaultConfigurationName = Release; 600 | }; 601 | 218A2E4F245006DF001D07E3 /* Build configuration list for PBXNativeTarget "AudioMixDemo" */ = { 602 | isa = XCConfigurationList; 603 | buildConfigurations = ( 604 | 218A2E50245006DF001D07E3 /* Debug */, 605 | 218A2E51245006DF001D07E3 /* Release */, 606 | ); 607 | defaultConfigurationIsVisible = 0; 608 | defaultConfigurationName = Release; 609 | }; 610 | /* End XCConfigurationList section */ 611 | }; 612 | rootObject = 218A2E33245006DC001D07E3 /* Project object */; 613 | } 614 | -------------------------------------------------------------------------------- /AudioMixDemo.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /AudioMixDemo.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /AudioMixDemo.xcodeproj/xcshareddata/xcschemes/AudioMixDemo.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 44 | 46 | 52 | 53 | 54 | 55 | 61 | 63 | 69 | 70 | 71 | 72 | 74 | 75 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /AudioMixDemo/Application/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // AudioMixDemo 4 | // 5 | // Created by PIYUSH GHOGHARI on 22/04/20. 6 | // Copyright © 2020 PIYUSH GHOGHARI. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | 15 | 16 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 17 | // Override point for customization after application launch. 18 | return true 19 | } 20 | 21 | // MARK: UISceneSession Lifecycle 22 | 23 | @available(iOS 13.0, *) 24 | func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { 25 | // Called when a new scene session is being created. 26 | // Use this method to select a configuration to create the new scene with. 27 | return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) 28 | } 29 | 30 | @available(iOS 13.0, *) 31 | func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { 32 | // Called when the user discards a scene session. 33 | // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. 34 | // Use this method to release any resources that were specific to the discarded scenes, as they will not return. 35 | } 36 | 37 | 38 | } 39 | 40 | -------------------------------------------------------------------------------- /AudioMixDemo/Application/SceneDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SceneDelegate.swift 3 | // AudioMixDemo 4 | // 5 | // Created by PIYUSH GHOGHARI on 22/04/20. 6 | // Copyright © 2020 PIYUSH GHOGHARI. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class SceneDelegate: UIResponder, UIWindowSceneDelegate { 12 | 13 | var window: UIWindow? 14 | 15 | 16 | @available(iOS 13.0, *) 17 | func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { 18 | // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`. 19 | // If using a storyboard, the `window` property will automatically be initialized and attached to the scene. 20 | // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead). 21 | guard let _ = (scene as? UIWindowScene) else { return } 22 | } 23 | 24 | @available(iOS 13.0, *) 25 | func sceneDidDisconnect(_ scene: UIScene) { 26 | // Called as the scene is being released by the system. 27 | // This occurs shortly after the scene enters the background, or when its session is discarded. 28 | // Release any resources associated with this scene that can be re-created the next time the scene connects. 29 | // The scene may re-connect later, as its session was not neccessarily discarded (see `application:didDiscardSceneSessions` instead). 30 | } 31 | 32 | @available(iOS 13.0, *) 33 | func sceneDidBecomeActive(_ scene: UIScene) { 34 | // Called when the scene has moved from an inactive state to an active state. 35 | // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. 36 | } 37 | 38 | @available(iOS 13.0, *) 39 | func sceneWillResignActive(_ scene: UIScene) { 40 | // Called when the scene will move from an active state to an inactive state. 41 | // This may occur due to temporary interruptions (ex. an incoming phone call). 42 | } 43 | 44 | @available(iOS 13.0, *) 45 | func sceneWillEnterForeground(_ scene: UIScene) { 46 | // Called as the scene transitions from the background to the foreground. 47 | // Use this method to undo the changes made on entering the background. 48 | } 49 | 50 | @available(iOS 13.0, *) 51 | func sceneDidEnterBackground(_ scene: UIScene) { 52 | // Called as the scene transitions from the foreground to the background. 53 | // Use this method to save data, release shared resources, and store enough scene-specific state information 54 | // to restore the scene back to its current state. 55 | } 56 | 57 | 58 | } 59 | 60 | -------------------------------------------------------------------------------- /AudioMixDemo/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "scale" : "2x", 6 | "size" : "20x20" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "scale" : "3x", 11 | "size" : "20x20" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "scale" : "2x", 16 | "size" : "29x29" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "scale" : "3x", 21 | "size" : "29x29" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "scale" : "2x", 26 | "size" : "40x40" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "scale" : "3x", 31 | "size" : "40x40" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "scale" : "2x", 36 | "size" : "60x60" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "scale" : "3x", 41 | "size" : "60x60" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "scale" : "1x", 46 | "size" : "20x20" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "scale" : "2x", 51 | "size" : "20x20" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "scale" : "1x", 56 | "size" : "29x29" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "scale" : "2x", 61 | "size" : "29x29" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "scale" : "1x", 66 | "size" : "40x40" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "scale" : "2x", 71 | "size" : "40x40" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "scale" : "1x", 76 | "size" : "76x76" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "scale" : "2x", 81 | "size" : "76x76" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "scale" : "2x", 86 | "size" : "83.5x83.5" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "scale" : "1x", 91 | "size" : "1024x1024" 92 | } 93 | ], 94 | "info" : { 95 | "author" : "xcode", 96 | "version" : 1 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /AudioMixDemo/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /AudioMixDemo/Assets.xcassets/ic_Sound.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "ic_Sound.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /AudioMixDemo/Assets.xcassets/ic_Sound.imageset/ic_Sound.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piyushghoghari08/AKAudioOverlap/f811282228cc3e2a17c377a38085e4408f8a2cbf/AudioMixDemo/Assets.xcassets/ic_Sound.imageset/ic_Sound.png -------------------------------------------------------------------------------- /AudioMixDemo/Assets.xcassets/microphone.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "microphone.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /AudioMixDemo/Assets.xcassets/microphone.imageset/microphone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piyushghoghari08/AKAudioOverlap/f811282228cc3e2a17c377a38085e4408f8a2cbf/AudioMixDemo/Assets.xcassets/microphone.imageset/microphone.png -------------------------------------------------------------------------------- /AudioMixDemo/AudioMixDemo-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | // 2 | // Use this file to import your target's public headers that you would like to expose to Swift. 3 | // 4 | 5 | #import "SYWaveformPlayerView.h" 6 | -------------------------------------------------------------------------------- /AudioMixDemo/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 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /AudioMixDemo/Constants.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Constants.swift 3 | // AudioMixDemo 4 | // 5 | // Created by PIYUSH GHOGHARI on 22/04/20. 6 | // Copyright © 2020 PIYUSH GHOGHARI. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | class Constants: NSObject { 11 | //Screen Size 12 | struct SCREEN_SIZES { 13 | static let HEIGHT = UIScreen.main.bounds.height 14 | static let WIDTH = UIScreen.main.bounds.width 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /AudioMixDemo/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 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | LSSupportsOpeningDocumentsInPlace 24 | 25 | NSMicrophoneUsageDescription 26 | Microphone will be used for Audio live streaming and recording. 27 | UIApplicationSceneManifest 28 | 29 | UIApplicationSupportsMultipleScenes 30 | 31 | UISceneConfigurations 32 | 33 | UIWindowSceneSessionRoleApplication 34 | 35 | 36 | UISceneConfigurationName 37 | Default Configuration 38 | UISceneDelegateClassName 39 | $(PRODUCT_MODULE_NAME).SceneDelegate 40 | UISceneStoryboardFile 41 | Main 42 | 43 | 44 | 45 | 46 | UIFileSharingEnabled 47 | 48 | UILaunchStoryboardName 49 | LaunchScreen 50 | UIMainStoryboardFile 51 | Main 52 | UIRequiredDeviceCapabilities 53 | 54 | armv7 55 | 56 | UISupportedInterfaceOrientations 57 | 58 | UIInterfaceOrientationPortrait 59 | UIInterfaceOrientationLandscapeLeft 60 | UIInterfaceOrientationLandscapeRight 61 | 62 | UISupportedInterfaceOrientations~ipad 63 | 64 | UIInterfaceOrientationPortrait 65 | UIInterfaceOrientationPortraitUpsideDown 66 | UIInterfaceOrientationLandscapeLeft 67 | UIInterfaceOrientationLandscapeRight 68 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /AudioMixDemo/Library/AudioWaveForm/SCWaveformView.h: -------------------------------------------------------------------------------- 1 | // 2 | // SCWaveformView.h 3 | // SCWaveformView 4 | // 5 | // Created by Simon CORSIN on 24/01/14. 6 | // Copyright (c) 2014 Simon CORSIN. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | #import 12 | 13 | @interface SCWaveformView : UIView 14 | 15 | @property (strong, readwrite, nonatomic) AVAsset *asset; 16 | @property (strong, readwrite, nonatomic) UIColor *normalColor; 17 | @property (strong, readwrite, nonatomic) UIColor *progressColor; 18 | @property (assign, readwrite, nonatomic) CGFloat progress; 19 | @property (assign, readwrite, nonatomic) BOOL antialiasingEnabled; 20 | 21 | @property (readwrite, nonatomic) UIImage *generatedNormalImage; 22 | @property (readwrite, nonatomic) UIImage *generatedProgressImage; 23 | 24 | // Ask the waveformview to generate the waveform right now 25 | // instead of doing it in the next draw operation 26 | - (void)generateWaveforms; 27 | 28 | // Render the waveform on a specified context 29 | + (void)renderWaveformInContext:(CGContextRef)context asset:(AVAsset *)asset withColor:(UIColor *)color andSize:(CGSize)size antialiasingEnabled:(BOOL)antialiasingEnabled; 30 | 31 | // Generate a waveform image for an asset 32 | + (UIImage*)generateWaveformImage:(AVAsset*)asset withColor:(UIColor*)color andSize:(CGSize)size antialiasingEnabled:(BOOL)antialiasingEnabled; 33 | 34 | @end 35 | -------------------------------------------------------------------------------- /AudioMixDemo/Library/AudioWaveForm/SCWaveformView.m: -------------------------------------------------------------------------------- 1 | // 2 | // SCWaveformView.m 3 | // SCWaveformView 4 | // 5 | // Created by Simon CORSIN on 24/01/14. 6 | // Copyright (c) 2014 Simon CORSIN. All rights reserved. 7 | // 8 | 9 | #import "SCWaveformView.h" 10 | 11 | #define absX(x) (x < 0 ? 0 - x : x) 12 | #define minMaxX(x, mn, mx) (x <= mn ? mn : (x >= mx ? mx : x)) 13 | #define noiseFloor (-50.0) 14 | #define decibel(amplitude) (20.0 * log10(absX(amplitude) / 32767.0)) 15 | 16 | @interface SCWaveformView() { 17 | UIImageView *_normalImageView; 18 | UIImageView *_progressImageView; 19 | UIView *_cropNormalView; 20 | UIView *_cropProgressView; 21 | BOOL _normalColorDirty; 22 | BOOL _progressColorDirty; 23 | } 24 | 25 | @end 26 | 27 | @implementation SCWaveformView 28 | 29 | - (instancetype)init { 30 | self = [super init]; 31 | 32 | if (self) { 33 | [self commonInit]; 34 | } 35 | 36 | return self; 37 | } 38 | 39 | - (instancetype)initWithCoder:(NSCoder *)aDecoder 40 | { 41 | self = [super initWithCoder:aDecoder]; 42 | 43 | if (self) { 44 | [self commonInit]; 45 | } 46 | 47 | return self; 48 | } 49 | 50 | - (void)commonInit 51 | { 52 | _normalImageView = [[UIImageView alloc] init]; 53 | _progressImageView = [[UIImageView alloc] init]; 54 | _cropNormalView = [[UIView alloc] init]; 55 | _cropProgressView = [[UIView alloc] init]; 56 | 57 | _cropNormalView.clipsToBounds = YES; 58 | _cropProgressView.clipsToBounds = YES; 59 | 60 | [_cropNormalView addSubview:_normalImageView]; 61 | [_cropProgressView addSubview:_progressImageView]; 62 | 63 | [self addSubview:_cropNormalView]; 64 | [self addSubview:_cropProgressView]; 65 | 66 | self.normalColor = [UIColor blueColor]; 67 | self.progressColor = [UIColor redColor]; 68 | 69 | _normalColorDirty = NO; 70 | _progressColorDirty = NO; 71 | } 72 | 73 | void SCRenderPixelWaveformInContext(CGContextRef context, float halfGraphHeight, double sample, float x) 74 | { 75 | float pixelHeight = halfGraphHeight * (1 - sample / noiseFloor); 76 | 77 | if (pixelHeight < 0) { 78 | pixelHeight = 0; 79 | } 80 | 81 | CGContextMoveToPoint(context, x, halfGraphHeight - pixelHeight); 82 | CGContextAddLineToPoint(context, x, halfGraphHeight + pixelHeight); 83 | CGContextStrokePath(context); 84 | 85 | } 86 | 87 | + (void)renderWaveformInContext:(CGContextRef)context asset:(AVAsset *)asset withColor:(UIColor *)color andSize:(CGSize)size antialiasingEnabled:(BOOL)antialiasingEnabled 88 | { 89 | if (asset == nil) { 90 | return; 91 | } 92 | 93 | CGFloat pixelRatio = [UIScreen mainScreen].scale; 94 | size.width *= pixelRatio; 95 | size.height *= pixelRatio; 96 | 97 | CGFloat widthInPixels = size.width; 98 | CGFloat heightInPixels = size.height; 99 | 100 | NSError *error = nil; 101 | AVAssetReader *reader = [[AVAssetReader alloc] initWithAsset:asset error:&error]; 102 | 103 | NSArray *audioTrackArray = [asset tracksWithMediaType:AVMediaTypeAudio]; 104 | 105 | if (audioTrackArray.count == 0) { 106 | return; 107 | } 108 | 109 | AVAssetTrack *songTrack = [audioTrackArray objectAtIndex:0]; 110 | 111 | NSDictionary *outputSettingsDict = [[NSDictionary alloc] initWithObjectsAndKeys: 112 | [NSNumber numberWithInt:kAudioFormatLinearPCM], AVFormatIDKey, 113 | [NSNumber numberWithInt:16], AVLinearPCMBitDepthKey, 114 | [NSNumber numberWithBool:NO], AVLinearPCMIsBigEndianKey, 115 | [NSNumber numberWithBool:NO], AVLinearPCMIsFloatKey, 116 | [NSNumber numberWithBool:NO], AVLinearPCMIsNonInterleaved, 117 | nil]; 118 | AVAssetReaderTrackOutput *output = [[AVAssetReaderTrackOutput alloc] initWithTrack:songTrack outputSettings:outputSettingsDict]; 119 | [reader addOutput:output]; 120 | 121 | UInt32 channelCount; 122 | NSArray *formatDesc = songTrack.formatDescriptions; 123 | for (unsigned int i = 0; i < [formatDesc count]; ++i) { 124 | CMAudioFormatDescriptionRef item = (__bridge CMAudioFormatDescriptionRef)[formatDesc objectAtIndex:i]; 125 | const AudioStreamBasicDescription* fmtDesc = CMAudioFormatDescriptionGetStreamBasicDescription(item); 126 | 127 | if (fmtDesc == nil) { 128 | return; 129 | } 130 | 131 | channelCount = fmtDesc->mChannelsPerFrame; 132 | } 133 | 134 | CGContextSetAllowsAntialiasing(context, antialiasingEnabled); 135 | CGContextSetLineWidth(context, 1.0); 136 | CGContextSetStrokeColorWithColor(context, color.CGColor); 137 | CGContextSetFillColorWithColor(context, color.CGColor); 138 | 139 | UInt32 bytesPerInputSample = 2 * channelCount; 140 | unsigned long int totalSamples = (unsigned long int)asset.duration.value; 141 | NSUInteger samplesPerPixel = totalSamples / (widthInPixels); 142 | samplesPerPixel = samplesPerPixel < 1 ? 1 : samplesPerPixel; 143 | [reader startReading]; 144 | 145 | float halfGraphHeight = (heightInPixels / 2); 146 | double bigSample = 0; 147 | NSUInteger bigSampleCount = 0; 148 | NSMutableData * data = [NSMutableData dataWithLength:32768]; 149 | 150 | CGFloat currentX = 0; 151 | while (reader.status == AVAssetReaderStatusReading) { 152 | CMSampleBufferRef sampleBufferRef = [output copyNextSampleBuffer]; 153 | 154 | if (sampleBufferRef) { 155 | CMBlockBufferRef blockBufferRef = CMSampleBufferGetDataBuffer(sampleBufferRef); 156 | size_t bufferLength = CMBlockBufferGetDataLength(blockBufferRef); 157 | 158 | if (data.length < bufferLength) { 159 | [data setLength:bufferLength]; 160 | } 161 | 162 | CMBlockBufferCopyDataBytes(blockBufferRef, 0, bufferLength, data.mutableBytes); 163 | 164 | SInt16 *samples = (SInt16 *)data.mutableBytes; 165 | int sampleCount = (int)(bufferLength / bytesPerInputSample); 166 | for (int i = 0; i < sampleCount; i++) { 167 | Float32 sample = (Float32) *samples++; 168 | sample = decibel(sample); 169 | sample = minMaxX(sample, noiseFloor, 0); 170 | 171 | for (int j = 1; j < channelCount; j++) 172 | samples++; 173 | 174 | bigSample += sample; 175 | bigSampleCount++; 176 | 177 | if (bigSampleCount == samplesPerPixel) { 178 | double averageSample = bigSample / (double)bigSampleCount; 179 | 180 | SCRenderPixelWaveformInContext(context, halfGraphHeight, averageSample, currentX); 181 | 182 | currentX ++; 183 | bigSample = 0; 184 | bigSampleCount = 0; 185 | } 186 | } 187 | CMSampleBufferInvalidate(sampleBufferRef); 188 | CFRelease(sampleBufferRef); 189 | } 190 | } 191 | 192 | // Rendering the last pixels 193 | bigSample = bigSampleCount > 0 ? bigSample / (double)bigSampleCount : noiseFloor; 194 | while (currentX < size.width) { 195 | SCRenderPixelWaveformInContext(context, halfGraphHeight, bigSample, currentX); 196 | currentX++; 197 | } 198 | 199 | } 200 | 201 | + (UIImage*)generateWaveformImage:(AVAsset *)asset withColor:(UIColor *)color andSize:(CGSize)size antialiasingEnabled:(BOOL)antialiasingEnabled 202 | { 203 | CGFloat ratio = [UIScreen mainScreen].scale; 204 | UIGraphicsBeginImageContextWithOptions(CGSizeMake(size.width * ratio, size.height * ratio), NO, 1); 205 | 206 | [SCWaveformView renderWaveformInContext:UIGraphicsGetCurrentContext() asset:asset withColor:color andSize:size antialiasingEnabled:antialiasingEnabled]; 207 | 208 | UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); 209 | 210 | UIGraphicsEndImageContext(); 211 | 212 | return image; 213 | } 214 | 215 | + (UIImage*)recolorizeImage:(UIImage*)image withColor:(UIColor*)color 216 | { 217 | CGRect imageRect = CGRectMake(0, 0, image.size.width, image.size.height); 218 | UIGraphicsBeginImageContextWithOptions(image.size, NO, image.scale); 219 | 220 | CGContextRef context = UIGraphicsGetCurrentContext(); 221 | CGContextTranslateCTM(context, 0.0, image.size.height); 222 | CGContextScaleCTM(context, 1.0, -1.0); 223 | CGContextDrawImage(context, imageRect, image.CGImage); 224 | [color set]; 225 | UIRectFillUsingBlendMode(imageRect, kCGBlendModeSourceAtop); 226 | 227 | UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext(); 228 | 229 | UIGraphicsEndImageContext(); 230 | 231 | return newImage; 232 | } 233 | 234 | - (void)generateWaveforms 235 | { 236 | CGRect rect = self.bounds; 237 | 238 | if (self.generatedNormalImage == nil && self.asset) { 239 | self.generatedNormalImage = [SCWaveformView generateWaveformImage:self.asset withColor:self.normalColor andSize:CGSizeMake(rect.size.width, rect.size.height) antialiasingEnabled:self.antialiasingEnabled]; 240 | _normalColorDirty = NO; 241 | } 242 | 243 | if (self.generatedNormalImage != nil) { 244 | if (_normalColorDirty) { 245 | self.generatedNormalImage = [SCWaveformView recolorizeImage:self.generatedNormalImage withColor:self.normalColor]; 246 | _normalColorDirty = NO; 247 | } 248 | 249 | if (_progressColorDirty || self.generatedProgressImage == nil) { 250 | self.generatedProgressImage = [SCWaveformView recolorizeImage:self.generatedNormalImage withColor:self.progressColor]; 251 | _progressColorDirty = NO; 252 | } 253 | } 254 | 255 | } 256 | 257 | - (void)drawRect:(CGRect)rect 258 | { 259 | [self generateWaveforms]; 260 | 261 | [super drawRect:rect]; 262 | } 263 | 264 | - (void)applyProgressToSubviews 265 | { 266 | CGRect bs = self.bounds; 267 | CGFloat progressWidth = bs.size.width * _progress; 268 | _cropProgressView.frame = CGRectMake(0, 0, progressWidth, bs.size.height); 269 | _cropNormalView.frame = CGRectMake(progressWidth, 0, bs.size.width - progressWidth, bs.size.height); 270 | _normalImageView.frame = CGRectMake(-progressWidth, 0, bs.size.width, bs.size.height); 271 | } 272 | 273 | - (void)layoutSubviews { 274 | [super layoutSubviews]; 275 | 276 | CGRect bs = self.bounds; 277 | _normalImageView.frame = bs; 278 | _progressImageView.frame = bs; 279 | 280 | // If the size is now bigger than the generated images 281 | if (bs.size.width > self.generatedNormalImage.size.width) { 282 | self.generatedNormalImage = nil; 283 | self.generatedProgressImage = nil; 284 | } 285 | 286 | [self applyProgressToSubviews]; 287 | } 288 | 289 | - (void)setNormalColor:(UIColor *)normalColor 290 | { 291 | _normalColor = normalColor; 292 | _normalColorDirty = YES; 293 | [self setNeedsDisplay]; 294 | } 295 | 296 | - (void)setProgressColor:(UIColor *)progressColor 297 | { 298 | _progressColor = progressColor; 299 | _progressColorDirty = YES; 300 | [self setNeedsDisplay]; 301 | } 302 | 303 | - (void)setAsset:(AVAsset *)asset 304 | { 305 | _asset = asset; 306 | self.generatedProgressImage = nil; 307 | self.generatedNormalImage = nil; 308 | [self setNeedsDisplay]; 309 | } 310 | 311 | - (void)setProgress:(CGFloat)progress 312 | { 313 | _progress = progress; 314 | [self applyProgressToSubviews]; 315 | } 316 | 317 | - (UIImage*)generatedNormalImage 318 | { 319 | return _normalImageView.image; 320 | } 321 | 322 | - (void)setGeneratedNormalImage:(UIImage *)generatedNormalImage 323 | { 324 | _normalImageView.image = generatedNormalImage; 325 | } 326 | 327 | - (UIImage*)generatedProgressImage 328 | { 329 | return _progressImageView.image; 330 | } 331 | 332 | - (void)setGeneratedProgressImage:(UIImage *)generatedProgressImage 333 | { 334 | _progressImageView.image = generatedProgressImage; 335 | } 336 | 337 | - (void)setAntialiasingEnabled:(BOOL)antialiasingEnabled 338 | { 339 | if (_antialiasingEnabled != antialiasingEnabled) { 340 | _antialiasingEnabled = antialiasingEnabled; 341 | self.generatedProgressImage = nil; 342 | self.generatedNormalImage = nil; 343 | [self setNeedsDisplay]; 344 | } 345 | } 346 | 347 | @end 348 | -------------------------------------------------------------------------------- /AudioMixDemo/Library/AudioWaveForm/SYWaveformPlayerView.h: -------------------------------------------------------------------------------- 1 | // 2 | // SYWaveformPlayerView.h 3 | // SCWaveformView 4 | // 5 | // Created by Spencer Yen on 12/26/14. 6 | // Copyright (c) 2014 Simon CORSIN. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | #import "SCWaveformView.h" 12 | 13 | @interface SYWaveformPlayerView : UIView 14 | 15 | - (id)initWithFrame:(CGRect)frame asset:(AVURLAsset *)asset color:(UIColor *)normalColor progressColor:(UIColor *)progressColor; 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /AudioMixDemo/Library/AudioWaveForm/SYWaveformPlayerView.m: -------------------------------------------------------------------------------- 1 | // 2 | // SYWaveformPlayerView.m 3 | // SCWaveformView 4 | // 5 | // Created by Spencer Yen on 12/26/14. 6 | // Copyright (c) 2014 Simon CORSIN. All rights reserved. 7 | // 8 | 9 | #import "SYWaveformPlayerView.h" 10 | 11 | @implementation SYWaveformPlayerView { 12 | AVAudioPlayer *player; 13 | SCWaveformView *waveformView; 14 | UIButton *playPauseButton; 15 | UIView *vwMainWaveForm; 16 | } 17 | 18 | - (id)initWithFrame:(CGRect)frame asset:(AVURLAsset *)asset color:(UIColor *)normalColor progressColor:(UIColor *)progressColor { 19 | 20 | if (self = [super initWithFrame:frame]) { 21 | 22 | player = [[AVAudioPlayer alloc] initWithContentsOfURL:asset.URL error:nil]; 23 | player.delegate = self; 24 | 25 | vwMainWaveForm = [[UIView alloc] initWithFrame:CGRectMake(0, 0, self.frame.size.width, 50)]; 26 | vwMainWaveForm.layer.cornerRadius = 10.0; 27 | [vwMainWaveForm setBackgroundColor:UIColor.clearColor]; 28 | vwMainWaveForm.clipsToBounds = YES; 29 | [self addSubview:vwMainWaveForm]; 30 | 31 | 32 | waveformView = [[SCWaveformView alloc] init]; 33 | waveformView.normalColor = normalColor; 34 | waveformView.progressColor = progressColor; 35 | waveformView.alpha = 0.8; 36 | waveformView.backgroundColor = [UIColor clearColor]; 37 | waveformView.asset = asset; 38 | [vwMainWaveForm addSubview:waveformView]; 39 | 40 | playPauseButton = [UIButton buttonWithType:UIButtonTypeCustom]; 41 | [playPauseButton setImage:[UIImage imageNamed:@"playbutton.png"] forState:UIControlStateNormal]; 42 | [playPauseButton addTarget:self 43 | action:@selector(playPauseTapped) 44 | forControlEvents:UIControlEventTouchUpInside]; 45 | [self addSubview:playPauseButton]; 46 | 47 | [NSTimer scheduledTimerWithTimeInterval:0.1 target: self 48 | selector: @selector(updateWaveform:) userInfo: nil repeats: YES]; 49 | 50 | } 51 | 52 | return self; 53 | } 54 | 55 | - (void)layoutSubviews { 56 | [super layoutSubviews]; 57 | 58 | playPauseButton.frame = CGRectMake(5, 5, 40, 40); 59 | playPauseButton.layer.cornerRadius = self.frame.size.height/4; 60 | 61 | // waveformView.frame = CGRectMake(self.frame.size.height/2 + 10, 0, self.frame.size.width - (self.frame.size.height/2 + 10), self.frame.size.height); 62 | waveformView.frame = CGRectMake(playPauseButton.frame.size.width + 5, 0, vwMainWaveForm.frame.size.width, vwMainWaveForm.frame.size.height); 63 | } 64 | 65 | - (void)playPauseTapped{ 66 | if(player.playing){ 67 | [playPauseButton setImage:[UIImage imageNamed:@"playbutton.png"] forState:UIControlStateNormal]; 68 | [player pause]; 69 | } else { 70 | [playPauseButton setImage:[UIImage imageNamed:@"pausebutton.png"] forState:UIControlStateNormal]; 71 | [player play]; 72 | } 73 | } 74 | 75 | - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{ 76 | [self touchesMoved:touches withEvent:event]; 77 | [player pause]; 78 | } 79 | 80 | - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event{ 81 | UITouch *touch = [[event allTouches]anyObject]; 82 | CGPoint location = [touch locationInView:touch.view]; 83 | if(location.x/self.frame.size.width > 0) { 84 | waveformView.progress = location.x/self.frame.size.width; 85 | } 86 | } 87 | 88 | - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { 89 | NSTimeInterval newTime = waveformView.progress * player.duration; 90 | player.currentTime = newTime; 91 | [playPauseButton setImage:[UIImage imageNamed:@"pausebutton.png"] forState:UIControlStateNormal]; 92 | [player play]; 93 | 94 | } 95 | 96 | - (void)updateWaveform:(id)sender { 97 | 98 | if(player.playing) { 99 | waveformView.progress = player.currentTime/player.duration; 100 | } 101 | } 102 | 103 | - (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player 104 | successfully:(BOOL)flag { 105 | [playPauseButton setImage:[UIImage imageNamed:@"playbutton.png"] forState:UIControlStateNormal]; 106 | 107 | } 108 | @end 109 | -------------------------------------------------------------------------------- /AudioMixDemo/Library/AudioWaveForm/pausebutton.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piyushghoghari08/AKAudioOverlap/f811282228cc3e2a17c377a38085e4408f8a2cbf/AudioMixDemo/Library/AudioWaveForm/pausebutton.png -------------------------------------------------------------------------------- /AudioMixDemo/Library/AudioWaveForm/playbutton.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piyushghoghari08/AKAudioOverlap/f811282228cc3e2a17c377a38085e4408f8a2cbf/AudioMixDemo/Library/AudioWaveForm/playbutton.png -------------------------------------------------------------------------------- /AudioMixDemo/Library/UIControl/Extensions/Array+Extensions.swift: -------------------------------------------------------------------------------- 1 | // swiftlint:disable all 2 | import Foundation 3 | 4 | extension Array { 5 | 6 | @discardableResult 7 | mutating func append(_ newArray: Array) -> CountableRange { 8 | let range = count..<(count + newArray.count) 9 | self += newArray 10 | return range 11 | } 12 | 13 | @discardableResult 14 | mutating func insert(_ newArray: Array, at index: Int) -> CountableRange { 15 | let mIndex = Swift.max(0, index) 16 | let start = Swift.min(count, mIndex) 17 | let end = start + newArray.count 18 | 19 | let left = self[0.. (_ element: T) { 26 | let anotherSelf = self 27 | 28 | removeAll(keepingCapacity: true) 29 | 30 | anotherSelf.each { (_: Int, current: Element) in 31 | if (current as! T) !== element { 32 | self.append(current) 33 | } 34 | } 35 | } 36 | 37 | func each(_ exe: (Int, Element) -> Void) { 38 | for (index, item) in enumerated() { 39 | exe(index, item) 40 | } 41 | } 42 | } 43 | 44 | extension Array where Element: Equatable { 45 | 46 | /// Remove Dublicates 47 | var unique: [Element] { 48 | // Thanks to https://github.com/sairamkotha for improving the method 49 | return self.reduce([]) { $0.contains($1) ? $0 : $0 + [$1] } 50 | } 51 | 52 | /// Check if array contains an array of elements. 53 | /// 54 | /// - Parameter elements: array of elements to check. 55 | /// - Returns: true if array contains all given items. 56 | public func contains(_ elements: [Element]) -> Bool { 57 | guard !elements.isEmpty else { // elements array is empty 58 | return false 59 | } 60 | var found = true 61 | for element in elements { 62 | if !contains(element) { 63 | found = false 64 | } 65 | } 66 | return found 67 | } 68 | 69 | /// All indexes of specified item. 70 | /// 71 | /// - Parameter item: item to check. 72 | /// - Returns: an array with all indexes of the given item. 73 | public func indexes(of item: Element) -> [Int] { 74 | var indexes: [Int] = [] 75 | for index in 0.. [[Element]] { 97 | var result = [[Element]]() 98 | var chunk = -1 99 | for (index, elem) in self.enumerated() { 100 | if index % size == 0 { 101 | result.append([Element]()) 102 | chunk += 1 103 | } 104 | result[chunk].append(elem) 105 | } 106 | return result 107 | } 108 | } 109 | 110 | public extension Array { 111 | 112 | /// Random item from array. 113 | var randomItem: Element? { 114 | if self.isEmpty { return nil } 115 | let index = Int(arc4random_uniform(UInt32(count))) 116 | return self[index] 117 | } 118 | 119 | /// Shuffled version of array. 120 | var shuffled: [Element] { 121 | var arr = self 122 | for _ in 0..<10 { 123 | arr.sort { (_, _) in arc4random() < arc4random() } 124 | } 125 | return arr 126 | } 127 | 128 | /// Shuffle array. 129 | mutating func shuffle() { 130 | // https://gist.github.com/ijoshsmith/5e3c7d8c2099a3fe8dc3 131 | for _ in 0..<10 { 132 | sort { (_, _) in arc4random() < arc4random() } 133 | } 134 | } 135 | 136 | /// Element at the given index if it exists. 137 | /// 138 | /// - Parameter index: index of element. 139 | /// - Returns: optional element (if exists). 140 | func item(at index: Int) -> Element? { 141 | guard index >= 0 && index < count else { return nil } 142 | return self[index] 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /AudioMixDemo/Library/UIControl/Extensions/Common+Extensions.swift: -------------------------------------------------------------------------------- 1 | // swiftlint:disable all 2 | import Foundation 3 | 4 | public func Log(_ object: T?, filename: String = #file, line: Int = #line, funcname: String = #function) { 5 | #if DEBUG 6 | guard let object = object else { return } 7 | print("***** \(Date()) \(filename.components(separatedBy: "/").last ?? "") (line: \(line)) :: \(funcname) :: \(object)") 8 | #endif 9 | } 10 | -------------------------------------------------------------------------------- /AudioMixDemo/Library/UIControl/Extensions/Date+Extension.swift: -------------------------------------------------------------------------------- 1 | // swiftlint:disable all 2 | import Foundation 3 | 4 | public extension Date { 5 | 6 | /// User’s current calendar. 7 | var calendar: Calendar { 8 | return Calendar.current 9 | } 10 | 11 | /// Era. 12 | var era: Int { 13 | return calendar.component(.era, from: self) 14 | } 15 | 16 | /// Year. 17 | var year: Int { 18 | get { 19 | return calendar.component(.year, from: self) 20 | } 21 | set { 22 | self = Date(calendar: calendar, timeZone: timeZone, era: era, year: newValue, month: month, day: day, hour: hour, minute: minute, second: second, nanosecond: nanosecond) 23 | } 24 | } 25 | 26 | /// Quarter. 27 | var quarter: Int { 28 | return calendar.component(.quarter, from: self) 29 | } 30 | 31 | /// Month. 32 | var month: Int { 33 | get { 34 | return calendar.component(.month, from: self) 35 | } 36 | set { 37 | self = Date(calendar: calendar, timeZone: timeZone, era: era, year: year, month: newValue, day: day, hour: hour, minute: minute, second: second, nanosecond: nanosecond) 38 | } 39 | } 40 | 41 | /// Week of year. 42 | var weekOfYear: Int { 43 | return calendar.component(.weekOfYear, from: self) 44 | } 45 | 46 | /// Week of month. 47 | var weekOfMonth: Int { 48 | return calendar.component(.weekOfMonth, from: self) 49 | } 50 | 51 | /// Weekday. 52 | var weekday: Int { 53 | return calendar.component(.weekday, from: self) 54 | } 55 | 56 | /// Day. 57 | var day: Int { 58 | get { 59 | return calendar.component(.day, from: self) 60 | } 61 | set { 62 | self = Date(calendar: calendar, timeZone: timeZone, era: era, year: year, month: month, day: newValue, hour: hour, minute: minute, second: second, nanosecond: nanosecond) 63 | } 64 | } 65 | 66 | /// Hour. 67 | var hour: Int { 68 | get { 69 | return calendar.component(.hour, from: self) 70 | } 71 | set { 72 | self = Date(calendar: calendar, timeZone: timeZone, era: era, year: year, month: month, day: day, hour: newValue, minute: minute, second: second, nanosecond: nanosecond) 73 | } 74 | } 75 | 76 | /// Minutes. 77 | var minute: Int { 78 | get { 79 | return calendar.component(.minute, from: self) 80 | } 81 | set { 82 | self = Date(calendar: calendar, timeZone: timeZone, era: era, year: year, month: month, day: day, hour: hour, minute: newValue, second: second, nanosecond: nanosecond) 83 | } 84 | } 85 | 86 | /// Seconds. 87 | var second: Int { 88 | get { 89 | return calendar.component(.second, from: self) 90 | } 91 | set { 92 | self = Date(calendar: calendar, timeZone: timeZone, era: era, year: year, month: month, day: day, hour: hour, minute: minute, second: newValue, nanosecond: nanosecond) 93 | } 94 | } 95 | 96 | /// Nanoseconds. 97 | var nanosecond: Int { 98 | return calendar.component(.nanosecond, from: self) 99 | } 100 | 101 | /// Check if date is in future. 102 | var isInFuture: Bool { 103 | return self > Date() 104 | } 105 | 106 | /// Check if date is in past. 107 | var isInPast: Bool { 108 | return self < Date() 109 | } 110 | 111 | /// Check if date is in today. 112 | var isInToday: Bool { 113 | return self.day == Date().day && self.month == Date().month && self.year == Date().year 114 | } 115 | 116 | /// ISO8601 string of format (yyyy-MM-dd'T'HH:mm:ss.SSS) from date. 117 | var iso8601String: String { 118 | // https://github.com/justinmakaila/NSDate-ISO-8601/blob/master/NSDateISO8601.swift 119 | let dateFormatter = DateFormatter() 120 | dateFormatter.locale = Locale(identifier: "en_US_POSIX") 121 | dateFormatter.timeZone = TimeZone(abbreviation: "GMT") 122 | dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSS" 123 | 124 | return dateFormatter.string(from: self).appending("Z") 125 | } 126 | 127 | /// Nearest five minutes to date. 128 | var nearestFiveMinutes: Date { 129 | var components = Calendar.current.dateComponents([.year, .month, .day, .hour, .minute], from: self) 130 | guard let min = components.minute else { 131 | return self 132 | } 133 | components.minute! = min % 5 < 3 ? min - min % 5 : min + 5 - (min % 5) 134 | components.second = 0 135 | if min > 57 { 136 | components.hour? += 1 137 | } 138 | return Calendar.current.date(from: components) ?? Date() 139 | } 140 | 141 | /// Nearest ten minutes to date. 142 | var nearestTenMinutes: Date { 143 | var components = Calendar.current.dateComponents([.year, .month, .day, .hour, .minute], from: self) 144 | guard let min = components.minute else { 145 | return self 146 | } 147 | components.minute! = min % 10 < 6 ? min - min % 10 : min + 10 - (min % 10) 148 | components.second = 0 149 | if min > 55 { 150 | components.hour? += 1 151 | } 152 | return Calendar.current.date(from: components) ?? Date() 153 | } 154 | 155 | /// Nearest quarter to date. 156 | var nearestHourQuarter: Date { 157 | var components = Calendar.current.dateComponents([.year, .month, .day, .hour, .minute], from: self) 158 | guard let min = components.minute else { 159 | return self 160 | } 161 | components.minute! = min % 15 < 8 ? min - min % 15 : min + 15 - (min % 15) 162 | components.second = 0 163 | if min > 52 { 164 | components.hour? += 1 165 | } 166 | return Calendar.current.date(from: components) ?? Date() 167 | } 168 | 169 | /// Nearest half hour to date. 170 | var nearestHalfHour: Date { 171 | var components = Calendar.current.dateComponents([.year, .month, .day, .hour, .minute], from: self) 172 | guard let min = components.minute else { 173 | return self 174 | } 175 | components.minute! = min % 30 < 15 ? min - min % 30 : min + 30 - (min % 30) 176 | components.second = 0 177 | if min > 30 { 178 | components.hour? += 1 179 | } 180 | return Calendar.current.date(from: components) ?? Date() 181 | } 182 | 183 | /// Time zone used by system. 184 | var timeZone: TimeZone { 185 | return self.calendar.timeZone 186 | } 187 | 188 | /// UNIX timestamp from date. 189 | var unixTimestamp: Double { 190 | return timeIntervalSince1970 191 | } 192 | 193 | } 194 | 195 | // MARK: - Methods 196 | public extension Date { 197 | 198 | /// Add calendar component to date. 199 | /// 200 | /// - Parameters: 201 | /// - component: component type. 202 | /// - value: multiples of compnenet to add. 203 | mutating func add(_ component: Calendar.Component, value: Int) { 204 | switch component { 205 | case .second: 206 | self = calendar.date(byAdding: .second, value: value, to: self) ?? self 207 | break 208 | 209 | case .minute: 210 | self = calendar.date(byAdding: .minute, value: value, to: self) ?? self 211 | break 212 | 213 | case .hour: 214 | self = calendar.date(byAdding: .hour, value: value, to: self) ?? self 215 | break 216 | 217 | case .day: 218 | self = calendar.date(byAdding: .day, value: value, to: self) ?? self 219 | break 220 | 221 | case .weekOfYear, .weekOfMonth: 222 | self = calendar.date(byAdding: .day, value: value * 7, to: self) ?? self 223 | break 224 | 225 | case .month: 226 | self = calendar.date(byAdding: .month, value: value, to: self) ?? self 227 | break 228 | 229 | case .year: 230 | self = calendar.date(byAdding: .year, value: value, to: self) ?? self 231 | break 232 | 233 | default: 234 | break 235 | } 236 | } 237 | 238 | /// Date by adding multiples of calendar component. 239 | /// 240 | /// - Parameters: 241 | /// - component: component type. 242 | /// - value: multiples of compnenets to add. 243 | /// - Returns: original date + multiples of compnenet added. 244 | func adding(_ component: Calendar.Component, value: Int) -> Date { 245 | switch component { 246 | case .second: 247 | return calendar.date(byAdding: .second, value: value, to: self) ?? self 248 | 249 | case .minute: 250 | return calendar.date(byAdding: .minute, value: value, to: self) ?? self 251 | 252 | case .hour: 253 | return calendar.date(byAdding: .hour, value: value, to: self) ?? self 254 | 255 | case .day: 256 | return calendar.date(byAdding: .day, value: value, to: self) ?? self 257 | 258 | case .weekOfYear, .weekOfMonth: 259 | return calendar.date(byAdding: .day, value: value * 7, to: self) ?? self 260 | 261 | case .month: 262 | return calendar.date(byAdding: .month, value: value, to: self) ?? self 263 | 264 | case .year: 265 | return calendar.date(byAdding: .year, value: value, to: self) ?? self 266 | 267 | default: 268 | return self 269 | } 270 | } 271 | 272 | /// Date by changing value of calendar component. 273 | /// 274 | /// - Parameters: 275 | /// - component: component type. 276 | /// - value: new value of compnenet to change. 277 | /// - Returns: original date + multiples of compnenets added. 278 | func changing(_ component: Calendar.Component, value: Int) -> Date { 279 | switch component { 280 | case .second: 281 | var date = self 282 | date.second = value 283 | return date 284 | 285 | case .minute: 286 | var date = self 287 | date.minute = value 288 | return date 289 | 290 | case .hour: 291 | var date = self 292 | date.hour = value 293 | return date 294 | 295 | case .day: 296 | var date = self 297 | date.day = value 298 | return date 299 | 300 | case .month: 301 | var date = self 302 | date.month = value 303 | return date 304 | 305 | case .year: 306 | var date = self 307 | date.year = value 308 | return date 309 | 310 | default: 311 | return self 312 | } 313 | } 314 | 315 | /// Data at the beginning of calendar component. 316 | /// 317 | /// - Parameter component: calendar component to get date at the beginning of. 318 | /// - Returns: date at the beginning of calendar component (if applicable). 319 | func beginning(of component: Calendar.Component) -> Date? { 320 | switch component { 321 | case .second: 322 | return calendar.date(from: calendar.dateComponents([.year, .month, .day, .hour, .minute, .second], from: self)) 323 | 324 | case .minute: 325 | return calendar.date(from: calendar.dateComponents([.year, .month, .day, .hour, .minute], from: self)) 326 | 327 | case .hour: 328 | return calendar.date(from: calendar.dateComponents([.year, .month, .day, .hour], from: self)) 329 | 330 | case .day: 331 | return self.calendar.startOfDay(for: self) 332 | 333 | case .weekOfYear, .weekOfMonth: 334 | return calendar.date(from: calendar.dateComponents([.yearForWeekOfYear, .weekOfYear], from: self)) 335 | 336 | case .month: 337 | return calendar.date(from: calendar.dateComponents([.year, .month], from: self)) 338 | 339 | case .year: 340 | return calendar.date(from: calendar.dateComponents([.year], from: self)) 341 | 342 | default: 343 | return nil 344 | } 345 | } 346 | 347 | /// Date at the end of calendar component. 348 | /// 349 | /// - Parameter component: calendar component to get date at the end of. 350 | /// - Returns: date at the end of calendar component (if applicable). 351 | func end(of component: Calendar.Component) -> Date? { 352 | switch component { 353 | case .second: 354 | var date = self.adding(.second, value: 1) 355 | guard let after = calendar.date(from: calendar.dateComponents([.year, .month, .day, .hour, .minute, .second], from: date)) else { 356 | return nil 357 | } 358 | date = after 359 | date.add(.second, value: -1) 360 | return date 361 | 362 | case .minute: 363 | var date = self.adding(.minute, value: 1) 364 | guard let after = calendar.date(from: calendar.dateComponents([.year, .month, .day, .hour, .minute], from: date)) else { 365 | return nil 366 | } 367 | date = after.adding(.second, value: -1) 368 | return date 369 | 370 | case .hour: 371 | var date = self.adding(.hour, value: 1) 372 | guard let after = calendar.date(from: calendar.dateComponents([.year, .month, .day, .hour], from: self)) else { 373 | return nil 374 | } 375 | date = after.adding(.second, value: -1) 376 | return date 377 | 378 | case .day: 379 | var date = self.adding(.day, value: 1) 380 | date = date.calendar.startOfDay(for: date) 381 | date.add(.second, value: -1) 382 | return date 383 | 384 | case .weekOfYear, .weekOfMonth: 385 | var date = self 386 | guard let beginningOfWeek = calendar.date(from: calendar.dateComponents([.yearForWeekOfYear, .weekOfYear], from: self)) else { 387 | return nil 388 | } 389 | date = beginningOfWeek.adding(.day, value: 7).adding(.second, value: -1) 390 | return date 391 | 392 | case .month: 393 | var date = self.adding(.month, value: 1) 394 | guard let after = calendar.date(from: calendar.dateComponents([.year, .month], from: self)) else { 395 | return nil 396 | } 397 | date = after.adding(.second, value: -1) 398 | return date 399 | 400 | case .year: 401 | var date = self.adding(.year, value: 1) 402 | guard let after = calendar.date(from: calendar.dateComponents([.year], from: self)) else { 403 | return nil 404 | } 405 | date = after.adding(.second, value: -1) 406 | return date 407 | 408 | default: 409 | return nil 410 | } 411 | } 412 | 413 | /// Date string from date. 414 | /// 415 | /// - Parameter style: DateFormatter style (default is .medium) 416 | /// - Returns: date string 417 | func dateString(ofStyle style: DateFormatter.Style = .medium) -> String { 418 | let dateFormatter = DateFormatter() 419 | dateFormatter.timeStyle = .none 420 | dateFormatter.dateStyle = style 421 | return dateFormatter.string(from: self) 422 | } 423 | 424 | /// Date and time string from date. 425 | /// 426 | /// - Parameter style: DateFormatter style (default is .medium) 427 | /// - Returns: date and time string 428 | func dateTimeString(ofStyle style: DateFormatter.Style = .medium) -> String { 429 | let dateFormatter = DateFormatter() 430 | dateFormatter.timeStyle = style 431 | dateFormatter.dateStyle = style 432 | return dateFormatter.string(from: self) 433 | } 434 | 435 | /// Check if date is in current given calendar component. 436 | /// 437 | /// - Parameter component: calendar componenet to check. 438 | /// - Returns: true if date is in current given calendar component. 439 | func isInCurrent(_ component: Calendar.Component) -> Bool { 440 | switch component { 441 | case .second: 442 | return second == Date().second && minute == Date().minute && hour == Date().hour && day == Date().day 443 | && month == Date().month && year == Date().year && era == Date().era 444 | 445 | case .minute: 446 | return minute == Date().minute && hour == Date().hour && day == Date().day && month == Date().month 447 | && year == Date().year && era == Date().era 448 | 449 | case .hour: 450 | return hour == Date().hour && day == Date().day && month == Date().month && year == Date().year 451 | && era == Date().era 452 | 453 | case .day: 454 | return day == Date().day && month == Date().month && year == Date().year && era == Date().era 455 | 456 | case .weekOfYear, .weekOfMonth: 457 | let beginningOfWeek = Date().beginning(of: .weekOfMonth)! 458 | let endOfWeek = Date().end(of: .weekOfMonth)! 459 | return self >= beginningOfWeek && self <= endOfWeek 460 | 461 | case .month: 462 | return month == Date().month && year == Date().year && era == Date().era 463 | 464 | case .year: 465 | return year == Date().year && era == Date().era 466 | 467 | case .era: 468 | return era == Date().era 469 | 470 | default: 471 | return false 472 | } 473 | } 474 | 475 | /// Time string from date 476 | func timeString(ofStyle style: DateFormatter.Style = .medium) -> String { 477 | let dateFormatter = DateFormatter() 478 | dateFormatter.timeStyle = style 479 | dateFormatter.dateStyle = .none 480 | return dateFormatter.string(from: self) 481 | } 482 | 483 | } 484 | 485 | // MARK: - Initializers 486 | public extension Date { 487 | 488 | /// Create a new date form calendar components. 489 | /// 490 | /// - Parameters: 491 | /// - calendar: Calendar (default is current). 492 | /// - timeZone: TimeZone (default is current). 493 | /// - era: Era (default is current era). 494 | /// - year: Year (default is current year). 495 | /// - month: Month (default is current month). 496 | /// - day: Day (default is today). 497 | /// - hour: Hour (default is current hour). 498 | /// - minute: Minute (default is current minute). 499 | /// - second: Second (default is current second). 500 | /// - nanosecond: Nanosecond (default is current nanosecond). 501 | init( 502 | calendar: Calendar? = Calendar.current, 503 | timeZone: TimeZone? = TimeZone.current, 504 | era: Int? = Date().era, 505 | year: Int? = Date().year, 506 | month: Int? = Date().month, 507 | day: Int? = Date().day, 508 | hour: Int? = Date().hour, 509 | minute: Int? = Date().minute, 510 | second: Int? = Date().second, 511 | nanosecond: Int? = Date().nanosecond) { 512 | 513 | var components = DateComponents() 514 | components.calendar = calendar 515 | components.timeZone = timeZone 516 | components.era = era 517 | components.year = year 518 | components.month = month 519 | components.day = day 520 | components.hour = hour 521 | components.minute = minute 522 | components.second = second 523 | components.nanosecond = nanosecond 524 | 525 | self = calendar?.date(from: components) ?? Date() 526 | } 527 | 528 | /// Create date object from ISO8601 string. 529 | /// 530 | /// - Parameter iso8601String: ISO8601 string of format (yyyy-MM-dd'T'HH:mm:ss.SSSZ). 531 | init(iso8601String: String) { 532 | // https://github.com/justinmakaila/NSDate-ISO-8601/blob/master/NSDateISO8601.swift 533 | let dateFormatter = DateFormatter() 534 | dateFormatter.locale = Locale(identifier: "en_US_POSIX") 535 | dateFormatter.timeZone = TimeZone.current 536 | dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ" 537 | self = dateFormatter.date(from: iso8601String) ?? Date() 538 | } 539 | 540 | /// Create new date object from UNIX timestamp. 541 | /// 542 | /// - Parameter unixTimestamp: UNIX timestamp. 543 | init(unixTimestamp: Double) { 544 | self.init(timeIntervalSince1970: unixTimestamp) 545 | } 546 | 547 | } 548 | 549 | public extension Date { 550 | /// SwiftRandom extension 551 | static func randomWithinDaysBeforeToday(_ days: Int) -> Date { 552 | let today = Date() 553 | let gregorian = Calendar(identifier: Calendar.Identifier.gregorian) 554 | 555 | let r1 = arc4random_uniform(UInt32(days)) 556 | let r2 = arc4random_uniform(UInt32(23)) 557 | let r3 = arc4random_uniform(UInt32(59)) 558 | let r4 = arc4random_uniform(UInt32(59)) 559 | 560 | var offsetComponents = DateComponents() 561 | offsetComponents.day = Int(r1) * -1 562 | offsetComponents.hour = Int(r2) 563 | offsetComponents.minute = Int(r3) 564 | offsetComponents.second = Int(r4) 565 | 566 | guard let rndDate1 = gregorian.date(byAdding: offsetComponents, to: today) else { 567 | print("randoming failed") 568 | return today 569 | } 570 | return rndDate1 571 | } 572 | 573 | /// SwiftRandom extension 574 | static func random() -> Date { 575 | let randomTime = TimeInterval(arc4random_uniform(UInt32.max)) 576 | return Date(timeIntervalSince1970: randomTime) 577 | } 578 | 579 | } 580 | -------------------------------------------------------------------------------- /AudioMixDemo/Library/UIControl/Extensions/Locale+Extensions.swift: -------------------------------------------------------------------------------- 1 | // swiftlint:disable all 2 | import Foundation 3 | 4 | extension Locale { 5 | 6 | static func locale(forCountry countryName: String) -> String? { 7 | return Locale.isoRegionCodes.filter { self.countryName(fromLocaleCode: $0) == countryName }.first 8 | } 9 | 10 | static func countryName(fromLocaleCode localeCode: String) -> String { 11 | return (Locale(identifier: "en_UK") as NSLocale).displayName(forKey: .countryCode, value: localeCode) ?? "" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /AudioMixDemo/Library/UIControl/Extensions/Optional+Extensions.swift: -------------------------------------------------------------------------------- 1 | // swiftlint:disable all 2 | import Foundation 3 | 4 | // https://github.com/apple/swift-evolution/blob/master/proposals/0121-remove-optional-comparison-operators.md 5 | // FIXME: comparison operators with optionals were removed from the Swift Standard Libary. 6 | // Consider refactoring the code to use the non-optional operators. 7 | public func < (lhs: T?, rhs: T?) -> Bool { 8 | switch (lhs, rhs) { 9 | case let (l?, r?): return l < r 10 | case (nil, _?): return true 11 | default: return false } 12 | } 13 | 14 | public func > (lhs: T?, rhs: T?) -> Bool { 15 | switch (lhs, rhs) { 16 | case let (l?, r?): 17 | return l > r 18 | default: 19 | return rhs < lhs 20 | } 21 | } 22 | 23 | public func == (lhs: T?, rhs: T?) -> Bool { 24 | switch (lhs, rhs) { 25 | case let (l?, r?): 26 | return l == r 27 | case (nil, nil): 28 | return true 29 | default: 30 | return false 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /AudioMixDemo/Library/UIControl/Extensions/String+Extensions.swift: -------------------------------------------------------------------------------- 1 | // swiftlint:disable all 2 | import UIKit 3 | 4 | extension String { 5 | 6 | subscript (i: Int) -> Character { 7 | return self[index(startIndex, offsetBy: i)] 8 | } 9 | 10 | subscript (i: Int) -> String { 11 | return String(self[i] as Character) 12 | } 13 | 14 | 15 | var containsAlphabets: Bool { 16 | //Checks if all the characters inside the string are alphabets 17 | let set = CharacterSet.letters 18 | return self.utf16.contains { 19 | guard let unicode = UnicodeScalar($0) else { return false } 20 | return set.contains(unicode) 21 | } 22 | } 23 | } 24 | 25 | // MARK: - NSAttributedString extensions 26 | public extension String { 27 | 28 | /// Regular string. 29 | var regular: NSAttributedString { 30 | return NSMutableAttributedString(string: self, attributes: [.font: UIFont.systemFont(ofSize: UIFont.systemFontSize)]) 31 | } 32 | 33 | /// Bold string. 34 | var bold: NSAttributedString { 35 | return NSMutableAttributedString(string: self, attributes: [.font: UIFont.boldSystemFont(ofSize: UIFont.systemFontSize)]) 36 | } 37 | 38 | /// Underlined string 39 | var underline: NSAttributedString { 40 | return NSAttributedString(string: self, attributes: [.underlineStyle: NSUnderlineStyle.single.rawValue]) 41 | } 42 | 43 | /// Strikethrough string. 44 | var strikethrough: NSAttributedString { 45 | return NSAttributedString(string: self, attributes: [.strikethroughStyle: NSNumber(value: NSUnderlineStyle.single.rawValue as Int)]) 46 | } 47 | 48 | /// Italic string. 49 | var italic: NSAttributedString { 50 | return NSMutableAttributedString(string: self, attributes: [.font: UIFont.italicSystemFont(ofSize: UIFont.systemFontSize)]) 51 | } 52 | 53 | /// Add color to string. 54 | /// 55 | /// - Parameter color: text color. 56 | /// - Returns: a NSAttributedString versions of string colored with given color. 57 | func colored(with color: UIColor) -> NSAttributedString { 58 | return NSMutableAttributedString(string: self, attributes: [.foregroundColor: color]) 59 | } 60 | } 61 | 62 | extension Array where Element: NSAttributedString { 63 | func joined(separator: NSAttributedString) -> NSAttributedString { 64 | var isFirst = true 65 | return self.reduce(NSMutableAttributedString()) { 66 | (r, e) in 67 | if isFirst { 68 | isFirst = false 69 | } else { 70 | r.append(separator) 71 | } 72 | r.append(e) 73 | return r 74 | } 75 | } 76 | 77 | func joined(separator: String) -> NSAttributedString { 78 | return joined(separator: NSAttributedString(string: separator)) 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /AudioMixDemo/Library/UIControl/Extensions/UIAlertController+Extensions.swift: -------------------------------------------------------------------------------- 1 | // swiftlint:disable all 2 | import UIKit 3 | import AudioToolbox 4 | 5 | // MARK: - Initializers 6 | extension UIAlertController { 7 | 8 | /// Create new alert view controller. 9 | /// 10 | /// - Parameters: 11 | /// - style: alert controller's style. 12 | /// - title: alert controller's title. 13 | /// - message: alert controller's message (default is nil). 14 | /// - defaultActionButtonTitle: default action button title (default is "OK") 15 | /// - tintColor: alert controller's tint color (default is nil) 16 | convenience init(style: UIAlertController.Style, source: UIView? = nil, title: String? = nil, message: String? = nil, tintColor: UIColor? = nil) { 17 | self.init(title: title, message: message, preferredStyle: style) 18 | 19 | // TODO: for iPad or other views 20 | let isPad: Bool = UIDevice.current.userInterfaceIdiom == .pad 21 | let root = UIApplication.shared.keyWindow?.rootViewController?.view 22 | 23 | //self.responds(to: #selector(getter: popoverPresentationController)) 24 | if let source = source { 25 | Log("----- source") 26 | popoverPresentationController?.sourceView = source 27 | popoverPresentationController?.sourceRect = source.bounds 28 | } else if isPad, let source = root, style == .actionSheet { 29 | Log("----- is pad") 30 | popoverPresentationController?.sourceView = source 31 | popoverPresentationController?.sourceRect = CGRect(x: source.bounds.midX, y: source.bounds.midY, width: 0, height: 0) 32 | //popoverPresentationController?.permittedArrowDirections = .down 33 | popoverPresentationController?.permittedArrowDirections = .init(rawValue: 0) 34 | } 35 | 36 | if let color = tintColor { 37 | self.view.tintColor = color 38 | } 39 | } 40 | } 41 | 42 | // MARK: - Methods 43 | extension UIAlertController { 44 | 45 | /// Present alert view controller in the current view controller. 46 | /// 47 | /// - Parameters: 48 | /// - animated: set true to animate presentation of alert controller (default is true). 49 | /// - vibrate: set true to vibrate the device while presenting the alert (default is false). 50 | /// - completion: an optional completion handler to be called after presenting alert controller (default is nil). 51 | public func show(animated: Bool = true, vibrate: Bool = false, style: UIBlurEffect.Style? = nil, completion: (() -> Void)? = nil) { 52 | 53 | /// TODO: change UIBlurEffectStyle 54 | if let style = style { 55 | for subview in view.allSubViewsOf(type: UIVisualEffectView.self) { 56 | subview.effect = UIBlurEffect(style: style) 57 | } 58 | } 59 | 60 | DispatchQueue.main.async { 61 | UIApplication.shared.keyWindow?.rootViewController?.present(self, animated: animated, completion: completion) 62 | if vibrate { 63 | AudioServicesPlayAlertSound(kSystemSoundID_Vibrate) 64 | } 65 | } 66 | } 67 | 68 | /// Add an action to Alert 69 | /// 70 | /// - Parameters: 71 | /// - title: action title 72 | /// - style: action style (default is UIAlertActionStyle.default) 73 | /// - isEnabled: isEnabled status for action (default is true) 74 | /// - handler: optional action handler to be called when button is tapped (default is nil) 75 | func addAction(image: UIImage? = nil, title: String, color: UIColor? = nil, style: UIAlertAction.Style = .default, isEnabled: Bool = true, handler: ((UIAlertAction) -> Void)? = nil) { 76 | //let isPad: Bool = UIDevice.current.userInterfaceIdiom == .pad 77 | //let action = UIAlertAction(title: title, style: isPad && style == .cancel ? .default : style, handler: handler) 78 | let action = UIAlertAction(title: title, style: style, handler: handler) 79 | action.isEnabled = isEnabled 80 | 81 | // button image 82 | if let image = image { 83 | action.setValue(image, forKey: "image") 84 | } 85 | 86 | // button title color 87 | if let color = color { 88 | action.setValue(color, forKey: "titleTextColor") 89 | } 90 | 91 | addAction(action) 92 | } 93 | 94 | /// Set alert's title, font and color 95 | /// 96 | /// - Parameters: 97 | /// - title: alert title 98 | /// - font: alert title font 99 | /// - color: alert title color 100 | func set(title: String?, font: UIFont, color: UIColor) { 101 | if title != nil { 102 | self.title = title 103 | } 104 | setTitle(font: font, color: color) 105 | } 106 | 107 | func setTitle(font: UIFont, color: UIColor) { 108 | guard let title = self.title else { return } 109 | let attributes: [NSAttributedString.Key: Any] = [.font: font, .foregroundColor: color] 110 | let attributedTitle = NSMutableAttributedString(string: title, attributes: attributes) 111 | setValue(attributedTitle, forKey: "attributedTitle") 112 | } 113 | 114 | /// Set alert's message, font and color 115 | /// 116 | /// - Parameters: 117 | /// - message: alert message 118 | /// - font: alert message font 119 | /// - color: alert message color 120 | func set(message: String?, font: UIFont, color: UIColor) { 121 | if message != nil { 122 | self.message = message 123 | } 124 | setMessage(font: font, color: color) 125 | } 126 | 127 | func setMessage(font: UIFont, color: UIColor) { 128 | guard let message = self.message else { return } 129 | let attributes: [NSAttributedString.Key: Any] = [.font: font, .foregroundColor: color] 130 | let attributedMessage = NSMutableAttributedString(string: message, attributes: attributes) 131 | setValue(attributedMessage, forKey: "attributedMessage") 132 | } 133 | 134 | /// Set alert's content viewController 135 | /// 136 | /// - Parameters: 137 | /// - vc: ViewController 138 | /// - height: height of content viewController 139 | func set(vc: UIViewController?, width: CGFloat? = nil, height: CGFloat? = nil) { 140 | guard let vc = vc else { return } 141 | setValue(vc, forKey: "contentViewController") 142 | if let height = height { 143 | vc.preferredContentSize.height = height 144 | preferredContentSize.height = height 145 | } 146 | } 147 | } 148 | extension UIAlertController { 149 | 150 | /// Use this method to display an **Alert** or an **ActionSheet** on any viewController. 151 | /// 152 | /// - Parameters: 153 | /// - controller: Object of controller on which you need to an display Alert/Actionsheet. 154 | /// - title: String Title which you want to display. 155 | /// - message: String Message which you want to display. 156 | /// - style: .alert || .actionshhet 157 | /// - cancelButton: String Title for Cancel Button type which you want to display. 158 | /// - distrutiveButton: String Title for Distrutive Button type which you want to display. 159 | /// - otherButtons: String Array of Other Button type which you want to display. 160 | /// - completion: You will get the call back here when user tap on the button from the alert. 161 | /// 162 | /// - Other Button Index will always be the first priority which will start from - **0...** 163 | /// - If Cancel And Destructive both the buttons will be there then index of Destructive button is **0**(2nd Last) and Cancel Button index is **1** (Last). 164 | /// - If Cancel, Destructive and Other Buttons will be there then index of Destructive button is **(2nd Last)** and Cancel Button index is **(Last)**. and Other Buttons index will start from **0** 165 | /// 166 | class func showAlert(controller: AnyObject , 167 | title: String? = nil, 168 | message: String? = nil, 169 | style: UIAlertController.Style = .alert , 170 | cancelButton: String? = nil , 171 | distrutiveButton: String? = nil , 172 | otherButtons: [String]? = nil, 173 | completion: ((Int, String) -> Void)?) { 174 | 175 | // Set Title to the Local Variable 176 | // let strTitle = "" 177 | 178 | // Set Message to the Local Variable 179 | // let strMessage = message! 180 | 181 | // Create an object of Alert Controller 182 | let alert = UIAlertController.init(title: title, message: message, preferredStyle: style) 183 | // Set Attributed title for Alert with custom Font and Color 184 | // let titleAttribute: [String : Any] = [NSFontAttributeName: UIFont.MuliBold(size: 16.0), NSForegroundColorAttributeName: UIColor.orangeDark] 185 | // let titleString = NSMutableAttributedString(string: strTitle, attributes: titleAttribute) 186 | // alert.setValue(titleString, forKey: "attributedTitle") // attributedMessage // attributedTitle 187 | 188 | // Set Attributed message for Alert with custom Font and Color 189 | // let messageAttribute: [String : Any] = [NSFontAttributeName: UIFont.MuliRegular(size: 14.0), NSForegroundColorAttributeName: UIColor.graySubTitle] 190 | // let messageString = NSMutableAttributedString(string: strMessage, attributes: messageAttribute) 191 | // alert.setValue(messageString, forKey: "attributedMessage") // attributedMessage // attributedTitle 192 | 193 | // Set Distrutive button if it is not nil 194 | if let strDistrutiveBtn = distrutiveButton { 195 | 196 | alert.addAction(UIAlertAction.init(title: strDistrutiveBtn, style: .destructive, handler: { (_) in 197 | 198 | completion?(otherButtons != nil ? otherButtons!.count : 0, strDistrutiveBtn) 199 | 200 | })) 201 | 202 | } 203 | 204 | // Set Cancel button if it is not nil 205 | if let strCancelBtn = cancelButton { 206 | 207 | alert.addAction(UIAlertAction.init(title: strCancelBtn, style: .cancel, handler: { (_) in 208 | 209 | // Pass action to the completion block 210 | if distrutiveButton != nil { 211 | // If Distrutive button was added to the alert then pass the index 2nd last 212 | completion?(otherButtons != nil ? otherButtons!.count + 1 : 1, strCancelBtn) 213 | } else { 214 | // Pass the last index to the completion block 215 | completion?(otherButtons != nil ? otherButtons!.count : 0, strCancelBtn) 216 | } 217 | 218 | })) 219 | 220 | } 221 | 222 | // Set Other Buttons if it is not nil 223 | if let arr = otherButtons { 224 | 225 | // Loop through all the array and add the individual action to the alert 226 | for (index, value) in arr.enumerated() { 227 | 228 | alert.addAction(UIAlertAction.init(title: value, style: .default, handler: { (_) in 229 | 230 | // Pass the index and the string value to the completion block which will use to perform further action 231 | completion?(index, value) 232 | 233 | })) 234 | 235 | } 236 | } 237 | 238 | // Change the Color of the button title 239 | // alert.view.tintColor = UIColor.orangeDark 240 | 241 | // Display an alert on on the controller 242 | controller.present(alert, animated: true, completion: nil) 243 | 244 | } 245 | 246 | /// Use this method to display an **Alert** with **Ok** Button on any viewController. 247 | /// 248 | /// - Parameters: 249 | /// - controller: Object of controller on which you need to display an Alert 250 | /// - message: String Message which you want to display. 251 | /// - completion: You will get the call back here when user tap on the button from the alert. Index will always be 0 252 | /// 253 | class func showAlertWithOkButton(controller: AnyObject , 254 | message: String? = nil , 255 | completion: ((Int, String) -> Void)?) { 256 | 257 | showAlert(controller: controller, message: message, style: .alert, cancelButton: nil, distrutiveButton: nil, otherButtons: ["OK"], completion: completion) 258 | 259 | } 260 | 261 | /// Use this method to display an **Alert** with **Cancel** Button on any viewController. 262 | /// 263 | /// - Parameters: 264 | /// - controller: Object of controller on which you need to display an Alert 265 | /// - message: String Message which you want to display. 266 | /// - completion: You will get the call back here when user tap on the button from the alert. Index will always be 0 267 | class func showAlertWithCancelButton(controller: AnyObject , 268 | message: String? = nil , 269 | completion: ((Int, String) -> Void)?) { 270 | 271 | showAlert(controller: controller, message: message, style: .alert, cancelButton: "Cancel", distrutiveButton: nil, otherButtons: nil, completion: completion) 272 | 273 | } 274 | 275 | /// Use this method to display an **Alert** for Delete confirmation on any viewController. 276 | /// 277 | /// - Parameters: 278 | /// - controller: Object of controller on which you need to display an Alert 279 | /// - message: String Message which you want to display. 280 | /// - completion: You will get the call back here when user tap on the button from the alert. 281 | /// 282 | /// - If Cancel And Destructive both the buttons will be there then index of Destructive button is **0**(2nd Last) and Cancel Button index is **1** (Last). 283 | class func showDeleteAlert(controller: AnyObject , 284 | message: String? = nil , 285 | completion: ((Int, String) -> Void)?) { 286 | 287 | showAlert(controller: controller, message: message, style: .alert, cancelButton: "Cancel", distrutiveButton: "Delete", otherButtons: nil, completion: completion) 288 | 289 | } 290 | 291 | class func showYesNoAlert(controller: AnyObject , 292 | message: String? = nil , 293 | completion: ((Int, String) -> Void)?) { 294 | 295 | showAlert(controller: controller, message: message, style: .alert, cancelButton: "No", distrutiveButton: nil, otherButtons: ["Yes"], completion: completion) 296 | 297 | } 298 | 299 | /// Use this method to display an **ActionSheet** for Image Picker confirmation on any viewController. 300 | /// 301 | /// - Parameters: 302 | /// - controller: Object of controller on which you need to display an Alert 303 | /// - message: String Message which you want to display. 304 | /// - completion: You will get the call back here when user tap on the button from the alert. 305 | /// 306 | /// - Index For "Use Gallery" button = 0 307 | /// - Index For "Use Camera" button = 1 308 | /// - Index For "Cancel" button = 2 309 | class func showActionsheetForImagePicker(controller: AnyObject , 310 | message: String? = nil , 311 | completion: ((Int, String) -> Void)?) { 312 | 313 | showAlert(controller: controller, message: message, style: .actionSheet, cancelButton: "Cancel", distrutiveButton: nil, otherButtons: ["Use Gallery", "Use Camera"], completion: completion) 314 | 315 | } 316 | 317 | } 318 | -------------------------------------------------------------------------------- /AudioMixDemo/Library/UIControl/Extensions/UIApplication+Extensions.swift: -------------------------------------------------------------------------------- 1 | // swiftlint:disable all 2 | import UIKit 3 | 4 | extension UIApplication { 5 | 6 | class func topViewController(_ viewController: UIViewController? = UIApplication.shared.keyWindow?.rootViewController) -> UIViewController? { 7 | if let nav = viewController as? UINavigationController { 8 | return topViewController(nav.visibleViewController) 9 | } 10 | if let tab = viewController as? UITabBarController { 11 | if let selected = tab.selectedViewController { 12 | return topViewController(selected) 13 | } 14 | } 15 | if let presented = viewController?.presentedViewController { 16 | return topViewController(presented) 17 | } 18 | 19 | return viewController 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /AudioMixDemo/Library/UIControl/Extensions/UIColor+Extensions.swift: -------------------------------------------------------------------------------- 1 | // swiftlint:disable all 2 | import Foundation 3 | import UIKit 4 | 5 | extension UIColor { 6 | 7 | /// SwifterSwift: https://github.com/SwifterSwift/SwifterSwift 8 | /// Hexadecimal value string (read-only). 9 | public var hexString: String { 10 | let components: [Int] = { 11 | let c = cgColor.components! 12 | let components = c.count == 4 ? c : [c[0], c[0], c[0], c[1]] 13 | return components.map { Int($0 * 255.0) } 14 | }() 15 | return String(format: "#%02X%02X%02X", components[0], components[1], components[2]) 16 | } 17 | 18 | /// SwifterSwift: https://github.com/SwifterSwift/SwifterSwift 19 | /// Short hexadecimal value string (read-only, if applicable). 20 | public var shortHexString: String? { 21 | let string = hexString.replacingOccurrences(of: "#", with: "") 22 | let chrs = Array(string) 23 | guard chrs[0] == chrs[1], chrs[2] == chrs[3], chrs[4] == chrs[5] else { return nil } 24 | return "#\(chrs[0])\(chrs[2])\(chrs[4])" 25 | } 26 | 27 | /// Color to Image 28 | func toImage(size: CGSize = CGSize(width: 1, height: 1)) -> UIImage { 29 | let rect: CGRect = CGRect(origin: .zero, size: size) 30 | UIGraphicsBeginImageContextWithOptions(rect.size, true, 0) 31 | self.setFill() 32 | UIRectFill(rect) 33 | let image = UIGraphicsGetImageFromCurrentImageContext() 34 | UIGraphicsEndImageContext() 35 | return image! // was image 36 | } 37 | 38 | /// SwifterSwift: https://github.com/SwifterSwift/SwifterSwift 39 | /// RGB components for a Color (between 0 and 255). 40 | /// 41 | /// UIColor.red.rgbComponents.red -> 255 42 | /// UIColor.green.rgbComponents.green -> 255 43 | /// UIColor.blue.rgbComponents.blue -> 255 44 | /// 45 | public var rgbComponents: (red: Int, green: Int, blue: Int) { 46 | var components: [CGFloat] { 47 | let c = cgColor.components! 48 | if c.count == 4 { 49 | return c 50 | } 51 | return [c[0], c[0], c[0], c[1]] 52 | } 53 | let r = components[0] 54 | let g = components[1] 55 | let b = components[2] 56 | return (red: Int(r * 255.0), green: Int(g * 255.0), blue: Int(b * 255.0)) 57 | } 58 | 59 | /// SwifterSwift: https://github.com/SwifterSwift/SwifterSwift 60 | /// RGB components for a Color represented as CGFloat numbers (between 0 and 1) 61 | /// 62 | /// UIColor.red.rgbComponents.red -> 1.0 63 | /// UIColor.green.rgbComponents.green -> 1.0 64 | /// UIColor.blue.rgbComponents.blue -> 1.0 65 | /// 66 | public var cgFloatComponents: (red: CGFloat, green: CGFloat, blue: CGFloat) { 67 | var components: [CGFloat] { 68 | let c = cgColor.components! 69 | if c.count == 4 { 70 | return c 71 | } 72 | return [c[0], c[0], c[0], c[1]] 73 | } 74 | let r = components[0] 75 | let g = components[1] 76 | let b = components[2] 77 | return (red: r, green: g, blue: b) 78 | } 79 | 80 | /// SwifterSwift: https://github.com/SwifterSwift/SwifterSwift 81 | /// Get components of hue, saturation, and brightness, and alpha (read-only). 82 | public var hsbaComponents: (hue: CGFloat, saturation: CGFloat, brightness: CGFloat, alpha: CGFloat) { 83 | var h: CGFloat = 0.0 84 | var s: CGFloat = 0.0 85 | var b: CGFloat = 0.0 86 | var a: CGFloat = 0.0 87 | 88 | self.getHue(&h, saturation: &s, brightness: &b, alpha: &a) 89 | return (hue: h, saturation: s, brightness: b, alpha: a) 90 | } 91 | 92 | /// Random color. 93 | public static var random: UIColor { 94 | let r = Int(arc4random_uniform(255)) 95 | let g = Int(arc4random_uniform(255)) 96 | let b = Int(arc4random_uniform(255)) 97 | return UIColor(red: r, green: g, blue: b) 98 | } 99 | } 100 | 101 | // MARK: - Initializers 102 | public extension UIColor { 103 | 104 | convenience init(hex: Int, alpha: CGFloat) { 105 | let r = CGFloat((hex & 0xFF0000) >> 16)/255 106 | let g = CGFloat((hex & 0xFF00) >> 8)/255 107 | let b = CGFloat(hex & 0xFF)/255 108 | self.init(red: r, green: g, blue: b, alpha: alpha) 109 | } 110 | 111 | convenience init(hex: Int) { 112 | self.init(hex: hex, alpha: 1.0) 113 | } 114 | 115 | /** 116 | Creates an UIColor from HEX String in "#363636" format 117 | 118 | - parameter hexString: HEX String in "#363636" format 119 | - returns: UIColor from HexString 120 | */ 121 | convenience init(hexString: String) { 122 | 123 | let hexString: String = (hexString as NSString).trimmingCharacters(in: .whitespacesAndNewlines) 124 | let scanner = Scanner(string: hexString as String) 125 | 126 | if hexString.hasPrefix("#") { 127 | scanner.scanLocation = 1 128 | } 129 | var color: UInt32 = 0 130 | scanner.scanHexInt32(&color) 131 | 132 | let mask = 0x000000FF 133 | let r = Int(color >> 16) & mask 134 | let g = Int(color >> 8) & mask 135 | let b = Int(color) & mask 136 | 137 | let red = CGFloat(r) / 255.0 138 | let green = CGFloat(g) / 255.0 139 | let blue = CGFloat(b) / 255.0 140 | self.init(red: red, green: green, blue: blue, alpha: 1) 141 | } 142 | 143 | /// Create UIColor from RGB values with optional transparency. 144 | /// 145 | /// - Parameters: 146 | /// - red: red component. 147 | /// - green: green component. 148 | /// - blue: blue component. 149 | /// - transparency: optional transparency value (default is 1) 150 | convenience init(red: Int, green: Int, blue: Int, transparency: CGFloat = 1) { 151 | assert(red >= 0 && red <= 255, "Invalid red component") 152 | assert(green >= 0 && green <= 255, "Invalid green component") 153 | assert(blue >= 0 && blue <= 255, "Invalid blue component") 154 | var trans: CGFloat { 155 | if transparency > 1 { 156 | return 1 157 | } else if transparency < 0 { 158 | return 0 159 | } else { 160 | return transparency 161 | } 162 | } 163 | self.init(red: CGFloat(red) / 255.0, green: CGFloat(green) / 255.0, blue: CGFloat(blue) / 255.0, alpha: trans) 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /AudioMixDemo/Library/UIControl/Extensions/UIImage+Extensions.swift: -------------------------------------------------------------------------------- 1 | // swiftlint:disable all 2 | import UIKit 3 | 4 | extension UIImage { 5 | 6 | /// Resizes an image to the specified size. 7 | /// 8 | /// - Parameters: 9 | /// - size: the size we desire to resize the image to. 10 | /// 11 | /// - Returns: the resized image. 12 | /// 13 | func imageWithSize(size: CGSize) -> UIImage? { 14 | 15 | UIGraphicsBeginImageContextWithOptions(size, false, UIScreen.main.scale) 16 | let rect = CGRect(x: 0.0, y: 0.0, width: size.width, height: size.height) 17 | draw(in: rect) 18 | 19 | let resultingImage = UIGraphicsGetImageFromCurrentImageContext() 20 | UIGraphicsEndImageContext() 21 | 22 | return resultingImage 23 | } 24 | 25 | /// Resizes an image to the specified size and adds an extra transparent margin at all sides of 26 | /// the image. 27 | /// 28 | /// - Parameters: 29 | /// - size: the size we desire to resize the image to. 30 | /// - extraMargin: the extra transparent margin to add to all sides of the image. 31 | /// 32 | /// - Returns: the resized image. The extra margin is added to the input image size. So that 33 | /// the final image's size will be equal to: 34 | /// `CGSize(width: size.width + extraMargin * 2, height: size.height + extraMargin * 2)` 35 | /// 36 | func imageWithSize(size: CGSize, extraMargin: CGFloat) -> UIImage? { 37 | 38 | let imageSize = CGSize(width: size.width + extraMargin * 2, height: size.height + extraMargin * 2) 39 | 40 | UIGraphicsBeginImageContextWithOptions(imageSize, false, UIScreen.main.scale) 41 | let drawingRect = CGRect(x: extraMargin, y: extraMargin, width: size.width, height: size.height) 42 | draw(in: drawingRect) 43 | 44 | let resultingImage = UIGraphicsGetImageFromCurrentImageContext() 45 | UIGraphicsEndImageContext() 46 | 47 | return resultingImage 48 | } 49 | 50 | /// Resizes an image to the specified size. 51 | /// 52 | /// - Parameters: 53 | /// - size: the size we desire to resize the image to. 54 | /// - roundedRadius: corner radius 55 | /// 56 | /// - Returns: the resized image with rounded corners. 57 | /// 58 | func imageWithSize(size: CGSize, roundedRadius radius: CGFloat) -> UIImage? { 59 | 60 | UIGraphicsBeginImageContextWithOptions(size, false, UIScreen.main.scale) 61 | if let currentContext = UIGraphicsGetCurrentContext() { 62 | let rect = CGRect(origin: .zero, size: size) 63 | currentContext.addPath(UIBezierPath(roundedRect: rect, 64 | byRoundingCorners: .allCorners, 65 | cornerRadii: CGSize(width: radius, height: radius)).cgPath) 66 | currentContext.clip() 67 | 68 | //Don't use CGContextDrawImage, coordinate system origin in UIKit and Core Graphics are vertical oppsite. 69 | draw(in: rect) 70 | currentContext.drawPath(using: .fillStroke) 71 | let roundedCornerImage = UIGraphicsGetImageFromCurrentImageContext() 72 | UIGraphicsEndImageContext() 73 | return roundedCornerImage 74 | } 75 | return nil 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /AudioMixDemo/Library/UIControl/Extensions/UIImageView+Extensions.swift: -------------------------------------------------------------------------------- 1 | // swiftlint:disable all 2 | import UIKit 3 | 4 | extension UIImageView { 5 | 6 | /// Sets the image property of the view based on initial text, a specified background color, custom text attributes, and a circular clipping 7 | /// 8 | /// - Parameters: 9 | /// - string: The string used to generate the initials. This should be a user's full name if available. 10 | /// - color: This optional paramter sets the background of the image. By default, a random color will be generated. 11 | /// - circular: This boolean will determine if the image view will be clipped to a circular shape. 12 | /// - textAttributes: This dictionary allows you to specify font, text color, shadow properties, etc. 13 | open func setImage(string: String?, color: UIColor? = nil, circular: Bool = false, textAttributes: [NSAttributedString.Key: Any]? = nil) { 14 | 15 | let image = imageSnap(text: string != nil ? string?.initials : "", color: color ?? UIColor.random, circular: circular, textAttributes: textAttributes) 16 | 17 | if let newImage = image { 18 | self.image = newImage 19 | } 20 | } 21 | 22 | private func imageSnap(text: String?, color: UIColor, circular: Bool, textAttributes: [NSAttributedString.Key: Any]?) -> UIImage? { 23 | 24 | let scale = Float(UIScreen.main.scale) 25 | var size = bounds.size 26 | if contentMode == .scaleToFill || contentMode == .scaleAspectFill || contentMode == .scaleAspectFit || contentMode == .redraw { 27 | size.width = CGFloat(floorf((Float(size.width) * scale) / scale)) 28 | size.height = CGFloat(floorf((Float(size.height) * scale) / scale)) 29 | } 30 | 31 | UIGraphicsBeginImageContextWithOptions(size, false, CGFloat(scale)) 32 | let context = UIGraphicsGetCurrentContext() 33 | if circular { 34 | let path = CGPath(ellipseIn: bounds, transform: nil) 35 | context?.addPath(path) 36 | context?.clip() 37 | } 38 | 39 | // Fill 40 | context?.setFillColor(color.cgColor) 41 | context?.fill(CGRect(x: 0, y: 0, width: size.width, height: size.height)) 42 | 43 | // Text 44 | if let text = text { 45 | let attributes: [NSAttributedString.Key: Any] = textAttributes ?? [.foregroundColor: UIColor.white, .font: UIFont.systemFont(ofSize: 15.0)] 46 | 47 | let textSize = text.size(withAttributes: attributes) 48 | let bounds = self.bounds 49 | let rect = CGRect(x: bounds.size.width/2 - textSize.width/2, y: bounds.size.height/2 - textSize.height/2, width: textSize.width, height: textSize.height) 50 | 51 | text.draw(in: rect, withAttributes: attributes) 52 | } 53 | 54 | let image = UIGraphicsGetImageFromCurrentImageContext() 55 | UIGraphicsEndImageContext() 56 | 57 | return image 58 | } 59 | } 60 | 61 | // MARK: String Helper 62 | extension String { 63 | 64 | public var initials: String { 65 | var finalString = String() 66 | var words = components(separatedBy: .whitespacesAndNewlines) 67 | 68 | if let firstCharacter = words.first?.first { 69 | finalString.append(String(firstCharacter)) 70 | words.removeFirst() 71 | } 72 | 73 | if let lastCharacter = words.last?.first { 74 | finalString.append(String(lastCharacter)) 75 | } 76 | 77 | return finalString.uppercased() 78 | 79 | //return self.components(separatedBy: .whitespacesAndNewlines).reduce("") { ($0.isEmpty ? "" : "\($0.uppercased().first!)") + ($1.isEmpty ? "" : "\($1.uppercased().first!)") } 80 | } 81 | } 82 | 83 | let kFontResizingProportion: CGFloat = 0.4 84 | let kColorMinComponent: Int = 30 85 | let kColorMaxComponent: Int = 214 86 | 87 | public typealias GradientColors = (top: UIColor, bottom: UIColor) 88 | 89 | typealias HSVOffset = (hue: CGFloat, saturation: CGFloat, brightness: CGFloat, alpha: CGFloat) 90 | let kGradientTopOffset: HSVOffset = (hue: -0.025, saturation: 0.05, brightness: 0, alpha: 0) 91 | let kGradientBotomOffset: HSVOffset = (hue: 0.025, saturation: -0.05, brightness: 0, alpha: 0) 92 | 93 | extension UIImageView { 94 | 95 | public func setImageForName(string: String, backgroundColor: UIColor? = nil, circular: Bool, textAttributes: [NSAttributedString.Key: AnyObject]?, gradient: Bool = false) { 96 | 97 | setImageForName(string: string, backgroundColor: backgroundColor, circular: circular, textAttributes: textAttributes, gradient: gradient, gradientColors: nil) 98 | } 99 | 100 | public func setImageForName(string: String, gradientColors: GradientColors? = nil, circular: Bool = true, textAttributes: [NSAttributedString.Key: AnyObject]? = nil) { 101 | 102 | setImageForName(string: string, backgroundColor: nil, circular: circular, textAttributes: textAttributes, gradient: true, gradientColors: gradientColors) 103 | } 104 | 105 | public func setImageForName(string: String, backgroundColor: UIColor? = nil, circular: Bool, textAttributes: [NSAttributedString.Key: AnyObject]? = nil, gradient: Bool = false, gradientColors: GradientColors? = nil) { 106 | 107 | let initials: String = initialsFromString(string: string) 108 | let color: UIColor = (backgroundColor != nil) ? backgroundColor! : randomColor(for: string) 109 | let gradientColors = gradientColors ?? topAndBottomColors(for: color) 110 | let attributes: [NSAttributedString.Key: AnyObject] = (textAttributes != nil) ? textAttributes! : [ 111 | NSAttributedString.Key.font: self.fontForFontName(name: nil), 112 | NSAttributedString.Key.foregroundColor: UIColor.white 113 | ] 114 | 115 | self.image = imageSnapshot(text: initials, backgroundColor: color, circular: circular, textAttributes: attributes, gradient: gradient, gradientColors: gradientColors) 116 | } 117 | 118 | private func fontForFontName(name: String?) -> UIFont { 119 | 120 | let fontSize = self.bounds.width * kFontResizingProportion 121 | if name != nil { 122 | return UIFont(name: name!, size: fontSize)! 123 | } else { 124 | return UIFont.systemFont(ofSize: fontSize) 125 | } 126 | 127 | } 128 | 129 | private func imageSnapshot(text imageText: String, backgroundColor: UIColor, circular: Bool, textAttributes: [NSAttributedString.Key: AnyObject], gradient: Bool, gradientColors: GradientColors) -> UIImage { 130 | 131 | let scale: CGFloat = UIScreen.main.scale 132 | 133 | var size: CGSize = self.bounds.size 134 | if (self.contentMode == .scaleToFill || 135 | self.contentMode == .scaleAspectFill || 136 | self.contentMode == .scaleAspectFit || 137 | self.contentMode == .redraw) { 138 | 139 | size.width = (size.width * scale) / scale 140 | size.height = (size.height * scale) / scale 141 | } 142 | 143 | UIGraphicsBeginImageContextWithOptions(size, false, scale) 144 | 145 | let context: CGContext = UIGraphicsGetCurrentContext()! 146 | 147 | if circular { 148 | // Clip context to a circle 149 | let path: CGPath = CGPath(ellipseIn: self.bounds, transform: nil) 150 | context.addPath(path) 151 | context.clip() 152 | } 153 | 154 | if gradient { 155 | // Draw a gradient from the top to the bottom 156 | let baseSpace = CGColorSpaceCreateDeviceRGB() 157 | let colors = [gradientColors.top.cgColor, gradientColors.bottom.cgColor] 158 | let gradient = CGGradient(colorsSpace: baseSpace, colors: colors as CFArray, locations: nil)! 159 | 160 | let startPoint = CGPoint(x: self.bounds.midX, y: self.bounds.minY) 161 | let endPoint = CGPoint(x: self.bounds.midX, y: self.bounds.maxY) 162 | 163 | context.drawLinearGradient(gradient, start: startPoint, end: endPoint, options: CGGradientDrawingOptions(rawValue: 0)) 164 | } else { 165 | // Fill background of context 166 | context.setFillColor(backgroundColor.cgColor) 167 | context.fill(CGRect(x: 0, y: 0, width: size.width, height: size.height)) 168 | } 169 | 170 | // Draw text in the context 171 | let textSize: CGSize = imageText.size(withAttributes: textAttributes) 172 | let bounds: CGRect = self.bounds 173 | 174 | imageText.draw(in: CGRect(x: bounds.midX - textSize.width / 2, 175 | y: bounds.midY - textSize.height / 2, 176 | width: textSize.width, 177 | height: textSize.height), 178 | withAttributes: textAttributes) 179 | 180 | let snapshot: UIImage = UIGraphicsGetImageFromCurrentImageContext()! 181 | UIGraphicsEndImageContext() 182 | 183 | return snapshot 184 | } 185 | } 186 | 187 | private func randomColorComponent() -> Int { 188 | let limit = kColorMaxComponent - kColorMinComponent 189 | return kColorMinComponent + Int(drand48() * Double(limit)) 190 | } 191 | 192 | private func randomColor(for string: String) -> UIColor { 193 | srand48(string.hashValue) 194 | 195 | let red = CGFloat(randomColorComponent()) / 255.0 196 | let green = CGFloat(randomColorComponent()) / 255.0 197 | let blue = CGFloat(randomColorComponent()) / 255.0 198 | 199 | return UIColor(red: red, green: green, blue: blue, alpha: 1.0) 200 | } 201 | 202 | private func initialsFromString(string: String) -> String { 203 | return string.components(separatedBy: .whitespacesAndNewlines).reduce("") { 204 | ($0.isEmpty ? "" : "\($0.uppercased().first!)") + ($1.isEmpty ? "" : "\($1.uppercased().first!)") 205 | } 206 | } 207 | 208 | private func clampColorComponent(_ value: CGFloat) -> CGFloat { 209 | return min(max(value, 0), 1) 210 | } 211 | 212 | private func correctColorComponents(of color: UIColor, withHSVOffset offset: HSVOffset) -> UIColor { 213 | 214 | var hue = CGFloat(0) 215 | var saturation = CGFloat(0) 216 | var brightness = CGFloat(0) 217 | var alpha = CGFloat(0) 218 | if color.getHue(&hue, saturation: &saturation, brightness: &brightness, alpha: &alpha) { 219 | hue = clampColorComponent(hue + offset.hue) 220 | saturation = clampColorComponent(saturation + offset.saturation) 221 | brightness = clampColorComponent(brightness + offset.brightness) 222 | alpha = clampColorComponent(alpha + offset.alpha) 223 | return UIColor(hue: hue, saturation: saturation, brightness: brightness, alpha: alpha) 224 | } 225 | 226 | return color 227 | } 228 | 229 | private func topAndBottomColors(for color: UIColor, withTopHSVOffset topHSVOffset: HSVOffset = kGradientTopOffset, withBottomHSVOffset bottomHSVOffset: HSVOffset = kGradientBotomOffset) -> GradientColors { 230 | let topColor = correctColorComponents(of: color, withHSVOffset: topHSVOffset) 231 | let bottomColor = correctColorComponents(of: color, withHSVOffset: bottomHSVOffset) 232 | return (top: topColor, bottom: bottomColor) 233 | } 234 | -------------------------------------------------------------------------------- /AudioMixDemo/Library/UIControl/Extensions/UISearchBar+Extensions.swift: -------------------------------------------------------------------------------- 1 | // swiftlint:disable all 2 | import UIKit 3 | 4 | extension UISearchBar { 5 | 6 | var textField: UITextField? { 7 | return value(forKey: "searchField") as? UITextField 8 | } 9 | 10 | func setSearchIcon(image: UIImage) { 11 | setImage(image, for: .search, state: .normal) 12 | } 13 | 14 | func setClearIcon(image: UIImage) { 15 | setImage(image, for: .clear, state: .normal) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /AudioMixDemo/Library/UIControl/Extensions/UISegmentedControl+Extensions.swift: -------------------------------------------------------------------------------- 1 | // swiftlint:disable all 2 | import UIKit 3 | 4 | public extension UISegmentedControl { 5 | 6 | /// Font of titles 7 | func title(font: UIFont) { 8 | let attributes: [NSAttributedString.Key: Any] = [.font: font] 9 | setTitleTextAttributes(attributes, for: UIControl.State()) 10 | //setNeedsDisplay() 11 | //layoutIfNeeded() 12 | } 13 | 14 | /// Segments titles. 15 | var segmentTitles: [String?] { 16 | get { 17 | var titles: [String?] = [] 18 | var i = 0 19 | while i < numberOfSegments { 20 | titles.append(titleForSegment(at: i)) 21 | i += 1 22 | } 23 | return titles 24 | } 25 | set { 26 | removeAllSegments() 27 | for (index, title) in newValue.enumerated() { 28 | insertSegment(withTitle: title, at: index, animated: false) 29 | } 30 | } 31 | } 32 | 33 | /// Segments images. 34 | var segmentImages: [UIImage?] { 35 | get { 36 | var images: [UIImage?] = [] 37 | var i = 0 38 | while i < numberOfSegments { 39 | images.append(imageForSegment(at: i)) 40 | i += 1 41 | } 42 | return images 43 | } 44 | set { 45 | removeAllSegments() 46 | for (index, image) in newValue.enumerated() { 47 | insertSegment(with: image, at: index, animated: false) 48 | } 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /AudioMixDemo/Library/UIControl/Extensions/UITextField+Extensions.swift: -------------------------------------------------------------------------------- 1 | // swiftlint:disable all 2 | import UIKit 3 | 4 | // MARK: - Properties 5 | 6 | extension UITextField { 7 | 8 | public typealias TextFieldConfig = (UITextField) -> Swift.Void 9 | 10 | public func config(textField configurate: TextFieldConfig?) { 11 | configurate?(self) 12 | } 13 | 14 | func left(image: UIImage?, color: UIColor = .black) { 15 | if let image = image { 16 | leftViewMode = UITextField.ViewMode.always 17 | let imageView = UIImageView(frame: CGRect(x: 0, y: 0, width: 20, height: 20)) 18 | imageView.contentMode = .scaleAspectFit 19 | imageView.image = image 20 | imageView.image = imageView.image?.withRenderingMode(.alwaysTemplate) 21 | imageView.tintColor = color 22 | leftView = imageView 23 | } else { 24 | leftViewMode = UITextField.ViewMode.never 25 | leftView = nil 26 | } 27 | } 28 | 29 | func right(image: UIImage?, color: UIColor = .black) { 30 | if let image = image { 31 | rightViewMode = UITextField.ViewMode.always 32 | let imageView = UIImageView(frame: CGRect(x: 0, y: 0, width: 20, height: 20)) 33 | imageView.contentMode = .scaleAspectFit 34 | imageView.image = image 35 | imageView.image = imageView.image?.withRenderingMode(.alwaysTemplate) 36 | imageView.tintColor = color 37 | rightView = imageView 38 | } else { 39 | rightViewMode = UITextField.ViewMode.never 40 | rightView = nil 41 | } 42 | } 43 | } 44 | 45 | // MARK: - Methods 46 | 47 | public extension UITextField { 48 | 49 | /// Set placeholder text color. 50 | /// 51 | /// - Parameter color: placeholder text color. 52 | func setPlaceHolderTextColor(_ color: UIColor) { 53 | self.attributedPlaceholder = NSAttributedString(string: self.placeholder != nil ? self.placeholder! : "", attributes: [NSAttributedString.Key.foregroundColor: color]) 54 | } 55 | 56 | /// Set placeholder text and its color 57 | func placeholder(text value: String, color: UIColor = .red) { 58 | self.attributedPlaceholder = NSAttributedString(string: value, attributes: [ NSAttributedString.Key.foregroundColor: color]) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /AudioMixDemo/Library/UIControl/Extensions/UITextView+Extensions.swift: -------------------------------------------------------------------------------- 1 | // swiftlint:disable all 2 | import UIKit 3 | 4 | // MARK: - Methods 5 | public extension UITextView { 6 | 7 | /// Scroll to the bottom of text view 8 | func scrollToBottom() { 9 | let range = NSRange(location: (text as NSString).length - 1, length: 1) 10 | scrollRangeToVisible(range) 11 | } 12 | 13 | /// Scroll to the top of text view 14 | func scrollToTop() { 15 | let range = NSRange(location: 0, length: 1) 16 | scrollRangeToVisible(range) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /AudioMixDemo/Library/UIControl/Extensions/UIView+Extensions.swift: -------------------------------------------------------------------------------- 1 | // swiftlint:disable all 2 | import UIKit 3 | import Foundation 4 | 5 | // MARK: - Designable Extension 6 | 7 | @IBDesignable 8 | extension UIView { 9 | 10 | @IBInspectable 11 | /// Should the corner be as circle 12 | public var circleCorner: Bool { 13 | get { 14 | return min(bounds.size.height, bounds.size.width) / 2 == cornerRadius 15 | } 16 | set { 17 | cornerRadius = newValue ? min(bounds.size.height, bounds.size.width) / 2 : cornerRadius 18 | } 19 | } 20 | 21 | @IBInspectable 22 | /// Corner radius of view; also inspectable from Storyboard. 23 | public var cornerRadius: CGFloat { 24 | get { 25 | return layer.cornerRadius 26 | } 27 | set { 28 | layer.cornerRadius = circleCorner ? min(bounds.size.height, bounds.size.width) / 2 : newValue 29 | //abs(CGFloat(Int(newValue * 100)) / 100) 30 | } 31 | } 32 | 33 | @IBInspectable 34 | /// Border color of view; also inspectable from Storyboard. 35 | public var borderColor: UIColor? { 36 | get { 37 | guard let color = layer.borderColor else { 38 | return nil 39 | } 40 | return UIColor(cgColor: color) 41 | } 42 | set { 43 | guard let color = newValue else { 44 | layer.borderColor = nil 45 | return 46 | } 47 | layer.borderColor = color.cgColor 48 | } 49 | } 50 | 51 | @IBInspectable 52 | /// Border width of view; also inspectable from Storyboard. 53 | public var borderWidth: CGFloat { 54 | get { 55 | return layer.borderWidth 56 | } 57 | set { 58 | layer.borderWidth = newValue 59 | } 60 | } 61 | 62 | @IBInspectable 63 | /// Shadow color of view; also inspectable from Storyboard. 64 | public var shadowColor: UIColor? { 65 | get { 66 | guard let color = layer.shadowColor else { 67 | return nil 68 | } 69 | return UIColor(cgColor: color) 70 | } 71 | set { 72 | layer.shadowColor = newValue?.cgColor 73 | } 74 | } 75 | 76 | @IBInspectable 77 | /// Shadow offset of view; also inspectable from Storyboard. 78 | public var shadowOffset: CGSize { 79 | get { 80 | return layer.shadowOffset 81 | } 82 | set { 83 | layer.shadowOffset = newValue 84 | } 85 | } 86 | 87 | @IBInspectable 88 | /// Shadow opacity of view; also inspectable from Storyboard. 89 | public var shadowOpacity: Double { 90 | get { 91 | return Double(layer.shadowOpacity) 92 | } 93 | set { 94 | layer.shadowOpacity = Float(newValue) 95 | } 96 | } 97 | 98 | @IBInspectable 99 | /// Shadow radius of view; also inspectable from Storyboard. 100 | public var shadowRadius: CGFloat { 101 | get { 102 | return layer.shadowRadius 103 | } 104 | set { 105 | layer.shadowRadius = newValue 106 | } 107 | } 108 | 109 | @IBInspectable 110 | /// Shadow path of view; also inspectable from Storyboard. 111 | public var shadowPath: CGPath? { 112 | get { 113 | return layer.shadowPath 114 | } 115 | set { 116 | layer.shadowPath = newValue 117 | } 118 | } 119 | 120 | @IBInspectable 121 | /// Should shadow rasterize of view; also inspectable from Storyboard. 122 | /// cache the rendered shadow so that it doesn't need to be redrawn 123 | public var shadowShouldRasterize: Bool { 124 | get { 125 | return layer.shouldRasterize 126 | } 127 | set { 128 | layer.shouldRasterize = newValue 129 | } 130 | } 131 | 132 | @IBInspectable 133 | /// Should shadow rasterize of view; also inspectable from Storyboard. 134 | /// cache the rendered shadow so that it doesn't need to be redrawn 135 | public var shadowRasterizationScale: CGFloat { 136 | get { 137 | return layer.rasterizationScale 138 | } 139 | set { 140 | layer.rasterizationScale = newValue 141 | } 142 | } 143 | 144 | @IBInspectable 145 | /// Corner radius of view; also inspectable from Storyboard. 146 | public var maskToBounds: Bool { 147 | get { 148 | return layer.masksToBounds 149 | } 150 | set { 151 | layer.masksToBounds = newValue 152 | } 153 | } 154 | } 155 | 156 | // MARK: - Properties 157 | 158 | public extension UIView { 159 | 160 | /// Size of view. 161 | var size: CGSize { 162 | get { 163 | return self.frame.size 164 | } 165 | set { 166 | self.width = newValue.width 167 | self.height = newValue.height 168 | } 169 | } 170 | 171 | /// Width of view. 172 | var width: CGFloat { 173 | get { 174 | return self.frame.size.width 175 | } 176 | set { 177 | self.frame.size.width = newValue 178 | } 179 | } 180 | 181 | /// Height of view. 182 | var height: CGFloat { 183 | get { 184 | return self.frame.size.height 185 | } 186 | set { 187 | self.frame.size.height = newValue 188 | } 189 | } 190 | } 191 | 192 | extension UIView { 193 | 194 | func superview(of type: T.Type) -> T? { 195 | return superview as? T ?? superview.flatMap { $0.superview(of: T.self) } 196 | } 197 | 198 | } 199 | 200 | // MARK: - Methods 201 | 202 | public extension UIView { 203 | 204 | typealias Configuration = (UIView) -> Swift.Void 205 | 206 | func config(configurate: Configuration?) { 207 | configurate?(self) 208 | } 209 | 210 | /// Set some or all corners radiuses of view. 211 | /// 212 | /// - Parameters: 213 | /// - corners: array of corners to change (example: [.bottomLeft, .topRight]). 214 | /// - radius: radius for selected corners. 215 | func roundCorners(_ corners: UIRectCorner, radius: CGFloat) { 216 | let maskPath = UIBezierPath(roundedRect: bounds, byRoundingCorners: corners, cornerRadii: CGSize(width: radius, height: radius)) 217 | let shape = CAShapeLayer() 218 | shape.path = maskPath.cgPath 219 | layer.mask = shape 220 | } 221 | } 222 | 223 | extension UIView { 224 | 225 | func searchVisualEffectsSubview() -> UIVisualEffectView? { 226 | if let visualEffectView = self as? UIVisualEffectView { 227 | return visualEffectView 228 | } else { 229 | for subview in subviews { 230 | if let found = subview.searchVisualEffectsSubview() { 231 | return found 232 | } 233 | } 234 | } 235 | return nil 236 | } 237 | 238 | /// This is the function to get subViews of a view of a particular type 239 | /// https://stackoverflow.com/a/45297466/5321670 240 | func subViews(type: T.Type) -> [T] { 241 | var all = [T]() 242 | for view in self.subviews { 243 | if let aView = view as? T { 244 | all.append(aView) 245 | } 246 | } 247 | return all 248 | } 249 | 250 | /// This is a function to get subViews of a particular type from view recursively. It would look recursively in all subviews and return back the subviews of the type T 251 | /// https://stackoverflow.com/a/45297466/5321670 252 | func allSubViewsOf(type: T.Type) -> [T] { 253 | var all = [T]() 254 | func getSubview(view: UIView) { 255 | if let aView = view as? T { 256 | all.append(aView) 257 | } 258 | guard view.subviews.count>0 else { return } 259 | view.subviews.forEach { getSubview(view: $0) } 260 | } 261 | getSubview(view: self) 262 | return all 263 | } 264 | } 265 | -------------------------------------------------------------------------------- /AudioMixDemo/Library/UIControl/Extensions/UIViewController+Extensions.swift: -------------------------------------------------------------------------------- 1 | // swiftlint:disable all 2 | import UIKit 3 | 4 | extension UIViewController { 5 | 6 | var alertController: UIAlertController? { 7 | guard let alert = UIApplication.topViewController() as? UIAlertController else { return nil } 8 | return alert 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /AudioMixDemo/Library/UIControl/Pickers/Date/DatePickerViewController.swift: -------------------------------------------------------------------------------- 1 | // swiftlint:disable all 2 | import UIKit 3 | 4 | extension UIAlertController { 5 | 6 | /// Add a date picker 7 | /// 8 | /// - Parameters: 9 | /// - mode: date picker mode 10 | /// - date: selected date of date picker 11 | /// - minimumDate: minimum date of date picker 12 | /// - maximumDate: maximum date of date picker 13 | /// - action: an action for datePicker value change 14 | 15 | func addDatePicker(mode: UIDatePicker.Mode, date: Date?, minimumDate: Date? = nil, maximumDate: Date? = nil, action: DatePickerViewController.Action?) { 16 | let datePicker = DatePickerViewController(mode: mode, date: date, minimumDate: minimumDate, maximumDate: maximumDate, action: action) 17 | set(vc: datePicker, height: 217) 18 | } 19 | } 20 | 21 | final class DatePickerViewController: UIViewController { 22 | 23 | public typealias Action = (Date) -> Void 24 | 25 | fileprivate var action: Action? 26 | 27 | fileprivate lazy var datePicker: UIDatePicker = { [unowned self] in 28 | $0.addTarget(self, action: #selector(DatePickerViewController.actionForDatePicker), for: .valueChanged) 29 | return $0 30 | }(UIDatePicker()) 31 | 32 | required init(mode: UIDatePicker.Mode, date: Date? = nil, minimumDate: Date? = nil, maximumDate: Date? = nil, action: Action?) { 33 | super.init(nibName: nil, bundle: nil) 34 | datePicker.datePickerMode = mode 35 | datePicker.date = date ?? Date() 36 | datePicker.minimumDate = minimumDate 37 | datePicker.maximumDate = maximumDate 38 | self.action = action 39 | } 40 | 41 | required init?(coder aDecoder: NSCoder) { 42 | fatalError("init(coder:) has not been implemented") 43 | } 44 | 45 | deinit { 46 | Log("has deinitialized") 47 | } 48 | 49 | override func loadView() { 50 | view = datePicker 51 | } 52 | 53 | @objc func actionForDatePicker() { 54 | action?(datePicker.date) 55 | } 56 | 57 | public func setDate(_ date: Date) { 58 | datePicker.setDate(date, animated: true) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /AudioMixDemo/Library/UIControl/Pickers/PickerView/PickerViewViewController.swift: -------------------------------------------------------------------------------- 1 | // swiftlint:disable all 2 | import UIKit 3 | 4 | extension UIAlertController { 5 | 6 | /// Add a picker view 7 | /// 8 | /// - Parameters: 9 | /// - values: values for picker view 10 | /// - initialSelection: initial selection of picker view 11 | /// - action: action for selected value of picker view 12 | func addPickerView(values: PickerViewViewController.Values, initialSelection: PickerViewViewController.Index? = nil, action: PickerViewViewController.Action?) { 13 | let pickerView = PickerViewViewController(values: values, initialSelection: initialSelection, action: action) 14 | set(vc: pickerView, height: 216) 15 | } 16 | } 17 | 18 | final class PickerViewViewController: UIViewController { 19 | 20 | public typealias Values = [[String]] 21 | public typealias Index = (column: Int, row: Int) 22 | public typealias Action = (_ vc: UIViewController, _ picker: UIPickerView, _ index: Index, _ values: Values) -> Void 23 | 24 | fileprivate var action: Action? 25 | fileprivate var values: Values = [[]] 26 | fileprivate var initialSelection: Index? 27 | 28 | fileprivate lazy var pickerView: UIPickerView = { 29 | return $0 30 | }(UIPickerView()) 31 | 32 | init(values: Values, initialSelection: Index? = nil, action: Action?) { 33 | super.init(nibName: nil, bundle: nil) 34 | self.values = values 35 | self.initialSelection = initialSelection 36 | self.action = action 37 | } 38 | 39 | required init?(coder aDecoder: NSCoder) { 40 | fatalError("init(coder:) has not been implemented") 41 | } 42 | 43 | deinit { 44 | Log("has deinitialized") 45 | } 46 | 47 | override func loadView() { 48 | view = pickerView 49 | } 50 | 51 | override func viewDidLoad() { 52 | super.viewDidLoad() 53 | pickerView.dataSource = self 54 | pickerView.delegate = self 55 | } 56 | 57 | override func viewWillAppear(_ animated: Bool) { 58 | super.viewWillAppear(true) 59 | if let initialSelection = initialSelection, values.count > initialSelection.column, values[initialSelection.column].count > initialSelection.row { 60 | pickerView.selectRow(initialSelection.row, inComponent: initialSelection.column, animated: true) 61 | } 62 | } 63 | } 64 | 65 | extension PickerViewViewController: UIPickerViewDataSource, UIPickerViewDelegate { 66 | 67 | // returns the number of 'columns' to display. 68 | public func numberOfComponents(in pickerView: UIPickerView) -> Int { 69 | return values.count 70 | } 71 | 72 | // returns the # of rows in each component.. 73 | public func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int { 74 | return values[component].count 75 | } 76 | /* 77 | // returns width of column and height of row for each component. 78 | public func pickerView(_ pickerView: UIPickerView, widthForComponent component: Int) -> CGFloat { 79 | 80 | } 81 | 82 | public func pickerView(_ pickerView: UIPickerView, rowHeightForComponent component: Int) -> CGFloat { 83 | 84 | } 85 | */ 86 | 87 | // these methods return either a plain NSString, a NSAttributedString, or a view (e.g UILabel) to display the row for the component. 88 | // for the view versions, we cache any hidden and thus unused views and pass them back for reuse. 89 | // If you return back a different object, the old one will be released. the view will be centered in the row rect 90 | public func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? { 91 | return values[component][row] 92 | } 93 | /* 94 | public func pickerView(_ pickerView: UIPickerView, attributedTitleForRow row: Int, forComponent component: Int) -> NSAttributedString? { 95 | // attributed title is favored if both methods are implemented 96 | } 97 | 98 | 99 | public func pickerView(_ pickerView: UIPickerView, viewForRow row: Int, forComponent component: Int, reusing view: UIView?) -> UIView { 100 | 101 | } 102 | */ 103 | public func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) { 104 | action?(self, pickerView, Index(column: component, row: row), values) 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /AudioMixDemo/Library/UIControl/Pickers/TextFields/OneTextFieldViewController.swift: -------------------------------------------------------------------------------- 1 | // swiftlint:disable all 2 | import UIKit 3 | 4 | extension UIAlertController { 5 | 6 | /// Add a textField 7 | /// 8 | /// - Parameters: 9 | /// - height: textField height 10 | /// - hInset: right and left margins to AlertController border 11 | /// - vInset: bottom margin to button 12 | /// - configuration: textField 13 | 14 | func addOneTextField(configuration: TextField.Config?) { 15 | let textField = OneTextFieldViewController(vInset: preferredStyle == .alert ? 12 : 0, configuration: configuration) 16 | let height: CGFloat = OneTextFieldViewController.ui.height + OneTextFieldViewController.ui.vInset 17 | set(vc: textField, height: height) 18 | } 19 | } 20 | 21 | final class OneTextFieldViewController: UIViewController { 22 | 23 | fileprivate lazy var textField: TextField = TextField() 24 | 25 | struct ui { 26 | static let height: CGFloat = 44 27 | static let hInset: CGFloat = 12 28 | static var vInset: CGFloat = 12 29 | } 30 | 31 | init(vInset: CGFloat = 12, configuration: TextField.Config?) { 32 | super.init(nibName: nil, bundle: nil) 33 | view.addSubview(textField) 34 | ui.vInset = vInset 35 | 36 | /// have to set textField frame width and height to apply cornerRadius 37 | textField.height = ui.height 38 | textField.width = view.width 39 | 40 | configuration?(textField) 41 | 42 | preferredContentSize.height = ui.height + ui.vInset 43 | } 44 | 45 | required init?(coder aDecoder: NSCoder) { 46 | fatalError("init(coder:) has not been implemented") 47 | } 48 | 49 | deinit { 50 | Log("has deinitialized") 51 | } 52 | 53 | override func viewDidLoad() { 54 | super.viewDidLoad() 55 | 56 | } 57 | 58 | override func viewDidLayoutSubviews() { 59 | super.viewDidLayoutSubviews() 60 | 61 | textField.width = view.width - ui.hInset * 2 62 | textField.height = ui.height 63 | textField.center.x = view.center.x 64 | textField.center.y = view.center.y - ui.vInset / 2 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /AudioMixDemo/Library/UIControl/Pickers/TextFields/TwoTextFieldsViewController.swift: -------------------------------------------------------------------------------- 1 | // swiftlint:disable all 2 | import UIKit 3 | 4 | extension UIAlertController { 5 | 6 | /// Add two textField 7 | /// 8 | /// - Parameters: 9 | /// - height: textField height 10 | /// - hInset: right and left margins to AlertController border 11 | /// - vInset: bottom margin to button 12 | /// - textFieldOne: first textField 13 | /// - textFieldTwo: second textField 14 | 15 | func addTwoTextFields(height: CGFloat = 58, hInset: CGFloat = 0, vInset: CGFloat = 0, textFieldOne: TextField.Config?, textFieldTwo: TextField.Config?) { 16 | let textField = TwoTextFieldsViewController(height: height, hInset: hInset, vInset: vInset, textFieldOne: textFieldOne, textFieldTwo: textFieldTwo) 17 | set(vc: textField, height: height * 2 + 2 * vInset) 18 | } 19 | } 20 | 21 | final class TwoTextFieldsViewController: UIViewController { 22 | 23 | fileprivate lazy var textFieldView: UIView = UIView() 24 | fileprivate lazy var textFieldOne: TextField = TextField() 25 | fileprivate lazy var textFieldTwo: TextField = TextField() 26 | 27 | fileprivate var height: CGFloat 28 | fileprivate var hInset: CGFloat 29 | fileprivate var vInset: CGFloat 30 | 31 | init(height: CGFloat, hInset: CGFloat, vInset: CGFloat, textFieldOne configurationOneFor: TextField.Config?, textFieldTwo configurationTwoFor: TextField.Config?) { 32 | self.height = height 33 | self.hInset = hInset 34 | self.vInset = vInset 35 | super.init(nibName: nil, bundle: nil) 36 | view.addSubview(textFieldView) 37 | 38 | textFieldView.addSubview(textFieldOne) 39 | textFieldView.addSubview(textFieldTwo) 40 | 41 | textFieldView.width = view.width 42 | textFieldView.height = height * 2 43 | textFieldView.maskToBounds = true 44 | textFieldView.borderWidth = 1 45 | textFieldView.borderColor = UIColor.lightGray 46 | textFieldView.cornerRadius = 8 47 | 48 | configurationOneFor?(textFieldOne) 49 | configurationTwoFor?(textFieldTwo) 50 | 51 | //preferredContentSize.height = height * 2 + vInset 52 | } 53 | 54 | required init?(coder aDecoder: NSCoder) { 55 | fatalError("init(coder:) has not been implemented") 56 | } 57 | 58 | deinit { 59 | Log("has deinitialized") 60 | } 61 | 62 | override func viewDidLoad() { 63 | super.viewDidLoad() 64 | 65 | } 66 | 67 | override func viewDidLayoutSubviews() { 68 | super.viewDidLayoutSubviews() 69 | 70 | textFieldView.width = view.width - hInset * 2 71 | textFieldView.height = height * 2 72 | textFieldView.center.x = view.center.x 73 | textFieldView.center.y = view.center.y 74 | 75 | textFieldOne.width = textFieldView.width 76 | textFieldOne.height = textFieldView.height / 2 77 | textFieldOne.center.x = textFieldView.width / 2 78 | textFieldOne.center.y = textFieldView.height / 4 79 | 80 | textFieldTwo.width = textFieldView.width 81 | textFieldTwo.height = textFieldView.height / 2 82 | textFieldTwo.center.x = textFieldView.width / 2 83 | textFieldTwo.center.y = textFieldView.height - textFieldView.height / 4 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /AudioMixDemo/Library/UIControl/Viewers/Models/AttributedTextBlock.swift: -------------------------------------------------------------------------------- 1 | // swiftlint:disable all 2 | import UIKit 3 | 4 | public enum AttributedTextBlock { 5 | 6 | case header1(String) 7 | case header2(String) 8 | case normal(String) 9 | case list(String) 10 | 11 | var text: NSMutableAttributedString { 12 | let attributedString: NSMutableAttributedString 13 | switch self { 14 | case .header1(let value): 15 | let attributes: [NSAttributedString.Key: Any] = [.font: UIFont.boldSystemFont(ofSize: 20), .foregroundColor: UIColor.black] 16 | attributedString = NSMutableAttributedString(string: value, attributes: attributes) 17 | case .header2(let value): 18 | let attributes: [NSAttributedString.Key: Any] = [.font: UIFont.boldSystemFont(ofSize: 18), .foregroundColor: UIColor.black] 19 | attributedString = NSMutableAttributedString(string: value, attributes: attributes) 20 | case .normal(let value): 21 | let attributes: [NSAttributedString.Key: Any] = [.font: UIFont.systemFont(ofSize: 15), .foregroundColor: UIColor.black] 22 | attributedString = NSMutableAttributedString(string: value, attributes: attributes) 23 | case .list(let value): 24 | let attributes: [NSAttributedString.Key: Any] = [.font: UIFont.systemFont(ofSize: 15), .foregroundColor: UIColor.black] 25 | attributedString = NSMutableAttributedString(string: "∙ " + value, attributes: attributes) 26 | } 27 | let paragraphStyle = NSMutableParagraphStyle() 28 | paragraphStyle.lineSpacing = 2 29 | paragraphStyle.lineHeightMultiple = 1 30 | paragraphStyle.paragraphSpacing = 10 31 | 32 | attributedString.addAttribute(.paragraphStyle, value: paragraphStyle, range: NSRange(location: 0, length: attributedString.length)) 33 | return attributedString 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /AudioMixDemo/Library/UIControl/Viewers/TextViewController.swift: -------------------------------------------------------------------------------- 1 | // swiftlint:disable all 2 | import UIKit 3 | 4 | extension UIAlertController { 5 | 6 | /// Add a Text Viewer 7 | /// 8 | /// - Parameters: 9 | /// - text: text kind 10 | 11 | func addTextViewer(text: TextViewerViewController.Kind) { 12 | let textViewer = TextViewerViewController(text: text) 13 | set(vc: textViewer) 14 | } 15 | } 16 | 17 | final class TextViewerViewController: UIViewController { 18 | 19 | enum Kind { 20 | 21 | case text(String?) 22 | case attributedText([AttributedTextBlock]) 23 | } 24 | 25 | fileprivate var text: [AttributedTextBlock] = [] 26 | 27 | fileprivate lazy var textView: UITextView = { 28 | $0.isEditable = false 29 | $0.isSelectable = true 30 | $0.backgroundColor = nil 31 | return $0 32 | }(UITextView()) 33 | 34 | struct UI { 35 | static let height: CGFloat = UIScreen.main.bounds.height * 0.8 36 | static let vInset: CGFloat = 16 37 | static let hInset: CGFloat = 16 38 | } 39 | 40 | init(text kind: Kind) { 41 | super.init(nibName: nil, bundle: nil) 42 | 43 | switch kind { 44 | case .text(let text): 45 | textView.text = text 46 | case .attributedText(let text): 47 | textView.attributedText = text.map { $0.text }.joined(separator: "\n") 48 | } 49 | textView.textContainerInset = UIEdgeInsets.init(top: UI.hInset, left: UI.vInset, bottom: UI.hInset, right: UI.vInset) 50 | //preferredContentSize.height = self.textView.contentSize.height 51 | } 52 | 53 | required init?(coder aDecoder: NSCoder) { 54 | fatalError("init(coder:) has not been implemented") 55 | } 56 | 57 | deinit { 58 | Log("has deinitialized") 59 | } 60 | 61 | override func loadView() { 62 | view = textView 63 | } 64 | 65 | override func viewDidLoad() { 66 | super.viewDidLoad() 67 | 68 | if UIDevice.current.userInterfaceIdiom == .pad { 69 | preferredContentSize.width = UIScreen.main.bounds.width * 0.618 70 | } 71 | } 72 | 73 | override func viewDidAppear(_ animated: Bool) { 74 | super.viewDidAppear(animated) 75 | textView.scrollToTop() 76 | } 77 | 78 | override func viewDidLayoutSubviews() { 79 | super.viewDidLayoutSubviews() 80 | preferredContentSize.height = textView.contentSize.height 81 | 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /AudioMixDemo/Library/UIControl/Views/Button.swift: -------------------------------------------------------------------------------- 1 | // swiftlint:disable all 2 | import UIKit 3 | 4 | open class Button: UIButton { 5 | 6 | public typealias Action = (Button) -> Swift.Void 7 | 8 | fileprivate var actionOnTouch: Action? 9 | 10 | init() { 11 | super.init(frame: .zero) 12 | } 13 | 14 | public override init(frame: CGRect) { 15 | super.init(frame: frame) 16 | } 17 | 18 | public required init?(coder aDecoder: NSCoder) { 19 | super.init(coder: aDecoder) 20 | } 21 | 22 | public func action(_ closure: @escaping Action) { 23 | if actionOnTouch == nil { 24 | addTarget(self, action: #selector(Button.actionOnTouchUpInside), for: .touchUpInside) 25 | } 26 | self.actionOnTouch = closure 27 | } 28 | 29 | @objc internal func actionOnTouchUpInside() { 30 | actionOnTouch?(self) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /AudioMixDemo/Library/UIControl/Views/GradientSlider.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GradientSlider.swift 3 | // GradientSlider 4 | // 5 | // Created by Jonathan Hull on 8/5/15. 6 | // Copyright © 2015 Jonathan Hull. All rights reserved. 7 | // 8 | 9 | // thx https://github.com/jonhull/GradientSlider 10 | 11 | // swiftlint:disable all 12 | import UIKit 13 | 14 | @IBDesignable class GradientSlider: UIControl { 15 | 16 | static var defaultThickness: CGFloat = 2.0 17 | static var defaultThumbSize: CGFloat = 28.0 18 | 19 | // MARK: Properties 20 | @IBInspectable var hasRainbow: Bool = false {didSet {updateTrackColors()}}//Uses saturation & lightness from minColor 21 | @IBInspectable var minColor: UIColor = UIColor.blue {didSet {updateTrackColors()}} 22 | @IBInspectable var maxColor: UIColor = UIColor.orange {didSet {updateTrackColors()}} 23 | 24 | @IBInspectable var value: CGFloat { 25 | get { return _value } 26 | set { set(value: newValue, animated: true) } 27 | } 28 | 29 | func set(value: CGFloat, animated: Bool = true) { 30 | _value = max(min(value, self.maximumValue), self.minimumValue) 31 | updateThumbPosition(animated: animated) 32 | } 33 | 34 | @IBInspectable var minimumValue: CGFloat = 0.0 // default 0.0. the current value may change if outside new min value 35 | @IBInspectable var maximumValue: CGFloat = 1.0 // default 1.0. the current value may change if outside new max value 36 | 37 | @IBInspectable var minimumValueImage: UIImage? = nil { // default is nil. image that appears to left of control (e.g. speaker off) 38 | didSet { 39 | if let img = minimumValueImage { 40 | let imgLayer = _minTrackImageLayer ?? { 41 | let l = CALayer() 42 | l.anchorPoint = CGPoint(x: 0.0, y: 0.5) 43 | self.layer.addSublayer(l) 44 | return l 45 | }() 46 | imgLayer.contents = img.cgImage 47 | imgLayer.bounds = CGRect(x: 0, y: 0, width: img.size.width, height: img.size.height) 48 | _minTrackImageLayer = imgLayer 49 | 50 | } else { 51 | _minTrackImageLayer?.removeFromSuperlayer() 52 | _minTrackImageLayer = nil 53 | } 54 | self.layer.needsLayout() 55 | } 56 | } 57 | @IBInspectable var maximumValueImage: UIImage? = nil { // default is nil. image that appears to right of control (e.g. speaker max) 58 | didSet { 59 | if let img = maximumValueImage { 60 | let imgLayer = _maxTrackImageLayer ?? { 61 | let l = CALayer() 62 | l.anchorPoint = CGPoint(x: 1.0, y: 0.5) 63 | self.layer.addSublayer(l) 64 | return l 65 | }() 66 | imgLayer.contents = img.cgImage 67 | imgLayer.bounds = CGRect(x: 0, y: 0, width: img.size.width, height: img.size.height) 68 | _maxTrackImageLayer = imgLayer 69 | 70 | } else { 71 | _maxTrackImageLayer?.removeFromSuperlayer() 72 | _maxTrackImageLayer = nil 73 | } 74 | self.layer.needsLayout() 75 | } 76 | } 77 | 78 | var continuous: Bool = true // if set, value change events are generated any time the value changes due to dragging. default = YES 79 | 80 | var actionBlock: (GradientSlider, CGFloat) -> Void = { slider, newValue in } 81 | 82 | @IBInspectable var thickness: CGFloat = defaultThickness { 83 | didSet { 84 | _trackLayer.cornerRadius = thickness / 2.0 85 | self.layer.setNeedsLayout() 86 | } 87 | } 88 | 89 | var trackBorderColor: UIColor? { 90 | set { 91 | _trackLayer.borderColor = newValue?.cgColor 92 | } 93 | get { 94 | if let color = _trackLayer.borderColor { 95 | return UIColor(cgColor: color) 96 | } 97 | return nil 98 | } 99 | } 100 | 101 | var trackBorderWidth: CGFloat { 102 | set { 103 | _trackLayer.borderWidth = newValue 104 | } 105 | get { 106 | return _trackLayer.borderWidth 107 | } 108 | } 109 | 110 | var thumbSize: CGFloat = defaultThumbSize { 111 | didSet { 112 | _thumbLayer.cornerRadius = thumbSize / 2.0 113 | _thumbLayer.bounds = CGRect(x: 0, y: 0, width: thumbSize, height: thumbSize) 114 | self.invalidateIntrinsicContentSize() 115 | } 116 | } 117 | 118 | @IBInspectable var thumbIcon: UIImage? = nil { 119 | didSet { 120 | _thumbIconLayer.contents = thumbIcon?.cgImage 121 | } 122 | } 123 | 124 | var thumbColor: UIColor { 125 | get { 126 | if let color = _thumbIconLayer.backgroundColor { 127 | return UIColor(cgColor: color) 128 | } 129 | return UIColor.white 130 | } 131 | set { 132 | _thumbIconLayer.backgroundColor = newValue.cgColor 133 | thumbIcon = nil 134 | } 135 | } 136 | 137 | // MARK: - Convienience Colors 138 | 139 | func setGradientForHueWithSaturation(saturation: CGFloat, brightness: CGFloat) { 140 | minColor = UIColor(hue: 0.0, saturation: saturation, brightness: brightness, alpha: 1.0) 141 | hasRainbow = true 142 | } 143 | 144 | func setGradientForSaturationWithHue(hue: CGFloat, brightness: CGFloat) { 145 | hasRainbow = false 146 | minColor = UIColor(hue: hue, saturation: 0.0, brightness: brightness, alpha: 1.0) 147 | maxColor = UIColor(hue: hue, saturation: 1.0, brightness: brightness, alpha: 1.0) 148 | } 149 | 150 | func setGradientForBrightnessWithHue(hue: CGFloat, saturation: CGFloat) { 151 | hasRainbow = false 152 | minColor = UIColor.black 153 | maxColor = UIColor(hue: hue, saturation: saturation, brightness: 1.0, alpha: 1.0) 154 | } 155 | 156 | func setGradientForRedWithGreen(green: CGFloat, blue: CGFloat) { 157 | hasRainbow = false 158 | minColor = UIColor(red: 0.0, green: green, blue: blue, alpha: 1.0) 159 | maxColor = UIColor(red: 1.0, green: green, blue: blue, alpha: 1.0) 160 | } 161 | 162 | func setGradientForGreenWithRed(red: CGFloat, blue: CGFloat) { 163 | hasRainbow = false 164 | minColor = UIColor(red: red, green: 0.0, blue: blue, alpha: 1.0) 165 | maxColor = UIColor(red: red, green: 1.0, blue: blue, alpha: 1.0) 166 | } 167 | 168 | func setGradientForBlueWithRed(red: CGFloat, green: CGFloat) { 169 | hasRainbow = false 170 | minColor = UIColor(red: red, green: green, blue: 0.0, alpha: 1.0) 171 | maxColor = UIColor(red: red, green: green, blue: 1.0, alpha: 1.0) 172 | } 173 | 174 | func setGradientForGrayscale() { 175 | hasRainbow = false 176 | minColor = UIColor.black 177 | maxColor = UIColor.white 178 | } 179 | 180 | // MARK: - Private Properties 181 | 182 | private var _value: CGFloat = 0.0 // default 0.0. this value will be pinned to min/max 183 | 184 | private var _thumbLayer: CALayer = { 185 | let thumb = CALayer() 186 | thumb.cornerRadius = defaultThumbSize/2.0 187 | thumb.bounds = CGRect(x: 0, y: 0, width: defaultThumbSize, height: defaultThumbSize) 188 | thumb.backgroundColor = UIColor.white.cgColor 189 | thumb.shadowColor = UIColor.black.cgColor 190 | thumb.shadowOffset = CGSize(width: 0.0, height: 2.5) 191 | thumb.shadowRadius = 2.0 192 | thumb.shadowOpacity = 0.25 193 | thumb.borderColor = UIColor.black.withAlphaComponent(0.15).cgColor 194 | thumb.borderWidth = 0.5 195 | return thumb 196 | }() 197 | 198 | private var _trackLayer: CAGradientLayer = { 199 | let track = CAGradientLayer() 200 | track.cornerRadius = defaultThickness / 2.0 201 | track.startPoint = CGPoint(x: 0.0, y: 0.5) 202 | track.endPoint = CGPoint(x: 1.0, y: 0.5) 203 | track.locations = [0.0, 1.0] 204 | track.colors = [UIColor.blue.cgColor, UIColor.orange.cgColor] 205 | track.borderColor = UIColor.black.cgColor 206 | return track 207 | }() 208 | 209 | private var _minTrackImageLayer: CALayer? 210 | private var _maxTrackImageLayer: CALayer? 211 | 212 | private var _thumbIconLayer: CALayer = { 213 | let size = defaultThumbSize - 4 214 | let iconLayer = CALayer() 215 | iconLayer.cornerRadius = size/2.0 216 | iconLayer.bounds = CGRect(x: 0, y: 0, width: size, height: size) 217 | iconLayer.backgroundColor = UIColor.white.cgColor 218 | return iconLayer 219 | }() 220 | 221 | // MARK: - Init 222 | 223 | override init(frame: CGRect) { 224 | super.init(frame: frame) 225 | commonSetup() 226 | } 227 | 228 | required init?(coder aDecoder: NSCoder) { 229 | super.init(coder: aDecoder) 230 | 231 | minColor = aDecoder.decodeObject(forKey: "minColor") as? UIColor ?? UIColor.lightGray 232 | maxColor = aDecoder.decodeObject(forKey: "maxColor") as? UIColor ?? UIColor.darkGray 233 | 234 | value = aDecoder.decodeObject(forKey: "value") as? CGFloat ?? 0.0 235 | minimumValue = aDecoder.decodeObject(forKey: "minimumValue") as? CGFloat ?? 0.0 236 | maximumValue = aDecoder.decodeObject(forKey: "maximumValue") as? CGFloat ?? 1.0 237 | 238 | minimumValueImage = aDecoder.decodeObject(forKey: "minimumValueImage") as? UIImage 239 | maximumValueImage = aDecoder.decodeObject(forKey: "maximumValueImage") as? UIImage 240 | 241 | thickness = aDecoder.decodeObject(forKey: "thickness") as? CGFloat ?? 2.0 242 | thumbIcon = aDecoder.decodeObject(forKey: "thumbIcon") as? UIImage 243 | 244 | commonSetup() 245 | } 246 | 247 | override func encode(with aCoder: NSCoder) { 248 | super.encode(with: aCoder) 249 | 250 | aCoder.encode(minColor, forKey: "minColor") 251 | aCoder.encode(maxColor, forKey: "maxColor") 252 | 253 | aCoder.encode(value, forKey: "value") 254 | aCoder.encode(minimumValue, forKey: "minimumValue") 255 | aCoder.encode(maximumValue, forKey: "maximumValue") 256 | 257 | aCoder.encode(minimumValueImage, forKey: "minimumValueImage") 258 | aCoder.encode(maximumValueImage, forKey: "maximumValueImage") 259 | 260 | aCoder.encode(thickness, forKey: "thickness") 261 | 262 | aCoder.encode(thumbIcon, forKey: "thumbIcon") 263 | 264 | } 265 | 266 | private func commonSetup() { 267 | self.layer.delegate = self 268 | self.layer.addSublayer(_trackLayer) 269 | self.layer.addSublayer(_thumbLayer) 270 | _thumbLayer.addSublayer(_thumbIconLayer) 271 | 272 | // instead of method - layoutSublayersOfLayer 273 | //needsDisplayOnBoundsChange = true 274 | } 275 | 276 | // MARK: - Layout 277 | 278 | override open var intrinsicContentSize: CGSize { 279 | return CGSize(width: UIView.noIntrinsicMetric, height: thumbSize) 280 | } 281 | 282 | override open var alignmentRectInsets: UIEdgeInsets { 283 | return UIEdgeInsets.init(top: 4.0, left: 2.0, bottom: 4.0, right: 2.0) 284 | } 285 | 286 | override open func layoutSublayers(of layer: CALayer) { 287 | super.layoutSublayers(of: layer) 288 | 289 | if layer != self.layer {return} 290 | 291 | var w = self.bounds.width 292 | let h = self.bounds.height 293 | var left: CGFloat = 2.0 294 | 295 | if let minImgLayer = _minTrackImageLayer { 296 | minImgLayer.position = CGPoint(x: 0.0, y: h/2.0) 297 | left = minImgLayer.bounds.width + 13.0 298 | } 299 | w -= left 300 | 301 | if let maxImgLayer = _maxTrackImageLayer { 302 | maxImgLayer.position = CGPoint(x: self.bounds.width, y: h/2.0) 303 | w -= (maxImgLayer.bounds.width + 13.0) 304 | } else { 305 | w -= 2.0 306 | } 307 | 308 | _trackLayer.bounds = CGRect(x: 0, y: 0, width: w, height: thickness) 309 | _trackLayer.position = CGPoint(x: w/2.0 + left, y: h/2.0) 310 | 311 | let halfSize = thumbSize/2.0 312 | var layerSize = thumbSize - 4.0 313 | if let icon = thumbIcon { 314 | layerSize = min(max(icon.size.height, icon.size.width), layerSize) 315 | _thumbIconLayer.cornerRadius = 0.0 316 | _thumbIconLayer.backgroundColor = UIColor.clear.cgColor 317 | } else { 318 | _thumbIconLayer.cornerRadius = layerSize/2.0 319 | } 320 | _thumbIconLayer.position = CGPoint(x: halfSize, y: halfSize) 321 | _thumbIconLayer.bounds = CGRect(x: 0, y: 0, width: layerSize, height: layerSize) 322 | 323 | updateThumbPosition(animated: false) 324 | } 325 | 326 | // MARK: - Touch Tracking 327 | 328 | override func beginTracking(_ touch: UITouch, with event: UIEvent?) -> Bool { 329 | let pt = touch.location(in: self) 330 | 331 | let center = _thumbLayer.position 332 | let diameter = max(thumbSize, 44.0) 333 | let r = CGRect(x: center.x - diameter/2.0, y: center.y - diameter/2.0, width: diameter, height: diameter) 334 | if r.contains(pt) { 335 | sendActions(for: .touchDown) 336 | return true 337 | } 338 | return false 339 | } 340 | 341 | override func continueTracking(_ touch: UITouch, with event: UIEvent?) -> Bool { 342 | let pt = touch.location(in: self) 343 | let newValue = valueForLocation(point: pt) 344 | set(value: newValue, animated: false) 345 | if(continuous) { 346 | sendActions(for: .valueChanged) 347 | actionBlock(self, newValue) 348 | } 349 | return true 350 | } 351 | 352 | override func endTracking(_ touch: UITouch?, with event: UIEvent?) { 353 | if let pt = touch?.location(in: self) { 354 | let newValue = valueForLocation(point: pt) 355 | set(value: newValue, animated: false) 356 | } 357 | actionBlock(self, _value) 358 | sendActions(for: [UIControl.Event.valueChanged, UIControl.Event.touchUpInside]) 359 | 360 | } 361 | 362 | // MARK: - Private Functions 363 | 364 | private func updateThumbPosition(animated: Bool) { 365 | let diff = maximumValue - minimumValue 366 | let perc = CGFloat((value - minimumValue) / diff) 367 | 368 | let halfHeight = self.bounds.height / 2.0 369 | let trackWidth = _trackLayer.bounds.width - thumbSize 370 | let left = _trackLayer.position.x - trackWidth/2.0 371 | 372 | if !animated { 373 | CATransaction.begin() //Move the thumb position without animations 374 | CATransaction.setValue(true, forKey: kCATransactionDisableActions) 375 | _thumbLayer.position = CGPoint(x: left + (trackWidth * perc), y: halfHeight) 376 | CATransaction.commit() 377 | } else { 378 | _thumbLayer.position = CGPoint(x: left + (trackWidth * perc), y: halfHeight) 379 | } 380 | } 381 | 382 | private func valueForLocation(point: CGPoint) -> CGFloat { 383 | 384 | var left = self.bounds.origin.x 385 | var w = self.bounds.width 386 | if let minImgLayer = _minTrackImageLayer { 387 | let amt = minImgLayer.bounds.width + 13.0 388 | w -= amt 389 | left += amt 390 | } else { 391 | w -= 2.0 392 | left += 2.0 393 | } 394 | 395 | if let maxImgLayer = _maxTrackImageLayer { 396 | w -= (maxImgLayer.bounds.width + 13.0) 397 | } else { 398 | w -= 2.0 399 | } 400 | 401 | let diff = CGFloat(self.maximumValue - self.minimumValue) 402 | 403 | let perc = max(min((point.x - left) / w, 1.0), 0.0) 404 | 405 | return (perc * diff) + CGFloat(self.minimumValue) 406 | } 407 | 408 | private func updateTrackColors() { 409 | if !hasRainbow { 410 | _trackLayer.colors = [minColor.cgColor, maxColor.cgColor] 411 | _trackLayer.locations = [0.0, 1.0] 412 | return 413 | } 414 | //Otherwise make a rainbow with the saturation & lightness of the min color 415 | var h: CGFloat = 0.0 416 | var s: CGFloat = 0.0 417 | var l: CGFloat = 0.0 418 | var a: CGFloat = 1.0 419 | 420 | minColor.getHue(&h, saturation: &s, brightness: &l, alpha: &a) 421 | 422 | let cnt = 40 423 | let step: CGFloat = 1.0 / CGFloat(cnt) 424 | let locations: [CGFloat] = (0...cnt).map { i in return (step * CGFloat(i)) } 425 | _trackLayer.colors = locations.map { return UIColor(hue: $0, saturation: s, brightness: l, alpha: a).cgColor } 426 | _trackLayer.locations = locations as [NSNumber] 427 | } 428 | } 429 | -------------------------------------------------------------------------------- /AudioMixDemo/Library/UIControl/Views/Label.swift: -------------------------------------------------------------------------------- 1 | // swiftlint:disable all 2 | import UIKit 3 | 4 | open class Label: UILabel { 5 | 6 | public typealias Action = (Label) -> Swift.Void 7 | 8 | fileprivate var actionOnTouch: Action? 9 | 10 | open var insets: UIEdgeInsets = .zero 11 | 12 | override open func drawText(in rect: CGRect) { 13 | super.drawText(in: rect.inset(by: insets)) 14 | } 15 | 16 | // Override -intrinsicContentSize: for Auto layout code 17 | override open var intrinsicContentSize: CGSize { 18 | var contentSize = super.intrinsicContentSize 19 | contentSize.height += insets.top + insets.bottom 20 | contentSize.width += insets.left + insets.right 21 | return contentSize 22 | } 23 | 24 | // Override -sizeThatFits: for Springs & Struts code 25 | override open func sizeThatFits(_ size: CGSize) -> CGSize { 26 | var contentSize = super.sizeThatFits(size) 27 | contentSize.height += insets.top + insets.bottom 28 | contentSize.width += insets.left + insets.right 29 | return contentSize 30 | } 31 | 32 | public func action(_ closure: @escaping Action) { 33 | Log("action did set") 34 | if actionOnTouch == nil { 35 | let gesture = UITapGestureRecognizer( 36 | target: self, 37 | action: #selector(Label.actionOnTouchUpInside)) 38 | gesture.numberOfTapsRequired = 1 39 | gesture.numberOfTouchesRequired = 1 40 | self.addGestureRecognizer(gesture) 41 | self.isUserInteractionEnabled = true 42 | } 43 | self.actionOnTouch = closure 44 | } 45 | 46 | @objc internal func actionOnTouchUpInside() { 47 | actionOnTouch?(self) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /AudioMixDemo/Library/UIControl/Views/SegmentedControl.swift: -------------------------------------------------------------------------------- 1 | // swiftlint:disable all 2 | import UIKit 3 | 4 | final class SegmentedControl: UISegmentedControl { 5 | 6 | public typealias Action = (Int) -> Swift.Void 7 | 8 | fileprivate var action: Action? 9 | 10 | func action(new: Action?) { 11 | if action == nil { 12 | addTarget(self, action: #selector(segmentedControlValueChanged(segment:)), for: .valueChanged) 13 | } 14 | action = new 15 | } 16 | 17 | @objc func segmentedControlValueChanged(segment: UISegmentedControl) { 18 | action?(segment.selectedSegmentIndex) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /AudioMixDemo/Library/UIControl/Views/TextField.swift: -------------------------------------------------------------------------------- 1 | // swiftlint:disable all 2 | import UIKit 3 | 4 | open class TextField: UITextField { 5 | 6 | public typealias Config = (TextField) -> Swift.Void 7 | 8 | public func configure(configurate: Config?) { 9 | configurate?(self) 10 | } 11 | 12 | public typealias Action = (UITextField) -> Void 13 | 14 | fileprivate var actionEditingChanged: Action? 15 | 16 | // Provides left padding for images 17 | 18 | override open func leftViewRect(forBounds bounds: CGRect) -> CGRect { 19 | var textRect = super.leftViewRect(forBounds: bounds) 20 | textRect.origin.x += leftViewPadding ?? 0 21 | return textRect 22 | } 23 | 24 | override open func textRect(forBounds bounds: CGRect) -> CGRect { 25 | return bounds.insetBy(dx: (leftTextPadding ?? 8) + (leftView?.width ?? 0) + (leftViewPadding ?? 0), dy: 0) 26 | } 27 | 28 | override open func editingRect(forBounds bounds: CGRect) -> CGRect { 29 | return bounds.insetBy(dx: (leftTextPadding ?? 8) + (leftView?.width ?? 0) + (leftViewPadding ?? 0), dy: 0) 30 | } 31 | 32 | public var leftViewPadding: CGFloat? 33 | public var leftTextPadding: CGFloat? 34 | 35 | public func action(closure: @escaping Action) { 36 | if actionEditingChanged == nil { 37 | addTarget(self, action: #selector(TextField.textFieldDidChange), for: .editingChanged) 38 | } 39 | actionEditingChanged = closure 40 | } 41 | 42 | @objc func textFieldDidChange(_ textField: UITextField) { 43 | actionEditingChanged?(self) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /AudioMixDemo/RippleLayer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RippleLayer.swift 3 | // Blaster 4 | // 5 | // Created by MyMAC on 05/12/18. 6 | // Copyright © 2018 MyMAC. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Foundation 11 | class RippleLayer: CAReplicatorLayer { 12 | fileprivate var rippleEffect: CALayer? 13 | private var animationGroup: CAAnimationGroup? 14 | var rippleRadius: CGFloat = 150.0 15 | var rippleRepeatCount: CGFloat = 10000.0 16 | var rippleWidth: CGFloat = 8 17 | var strScreenStatus = String() 18 | 19 | override init() { 20 | super.init() 21 | setupRippleEffect() 22 | 23 | repeatCount = Float(rippleRepeatCount) 24 | } 25 | 26 | required init?(coder aDecoder: NSCoder) { 27 | fatalError("init(coder:) has not been implemented") 28 | } 29 | 30 | override func layoutSublayers() { 31 | super.layoutSublayers() 32 | 33 | rippleEffect?.bounds = CGRect(x: 0, y: 0, width: rippleRadius*2, height: rippleRadius*2) 34 | rippleEffect?.cornerRadius = rippleRadius 35 | rippleEffect?.backgroundColor = UIColor(red: 239.0/255.0, green: 122.0/255.0, blue: 132.0/255.0, alpha: 1.0).cgColor 36 | instanceCount = 3 37 | instanceDelay = 0.4 38 | } 39 | 40 | func setupRippleEffect() { 41 | rippleEffect = CALayer() 42 | rippleEffect?.borderWidth = CGFloat(1) 43 | rippleEffect?.borderColor = UIColor(red: 239.0/255.0, green: 122.0/255.0, blue: 132.0/255.0, alpha: 1.0).cgColor 44 | rippleEffect?.opacity = 0 45 | addSublayer(rippleEffect!) 46 | } 47 | 48 | func startAnimation() { 49 | setupAnimationGroup() 50 | rippleEffect?.add(animationGroup!, forKey: "ripple") 51 | } 52 | 53 | func stopAnimation() { 54 | rippleEffect?.removeAnimation(forKey: "ripple") 55 | } 56 | 57 | func setupAnimationGroup() { 58 | let duration: CFTimeInterval = 3 59 | 60 | let group = CAAnimationGroup() 61 | group.duration = duration 62 | group.repeatCount = self.repeatCount 63 | group.timingFunction = CAMediaTimingFunction(name: .default) 64 | 65 | let scaleAnimation = CABasicAnimation(keyPath: "transform.scale.xy") 66 | scaleAnimation.fromValue = 0.0; 67 | scaleAnimation.toValue = 1.0; 68 | scaleAnimation.duration = duration 69 | 70 | let opacityAnimation = CAKeyframeAnimation(keyPath: "opacity") 71 | opacityAnimation.duration = duration 72 | let fromAlpha = 1.0 73 | opacityAnimation.values = [fromAlpha, (fromAlpha * 0.5), 0]; 74 | opacityAnimation.keyTimes = [0, 0.2, 1]; 75 | 76 | group.animations = [scaleAnimation, opacityAnimation] 77 | animationGroup = group; 78 | animationGroup!.delegate = self; 79 | } 80 | } 81 | extension RippleLayer: CAAnimationDelegate { 82 | func animationDidStop(_ anim: CAAnimation, finished flag: Bool) { 83 | if let count = rippleEffect?.animationKeys()?.count , count > 0 { 84 | rippleEffect?.removeAllAnimations() 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /AudioMixDemo/ViewController/AudioMergeViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AudioMergeViewController.swift 3 | // AudioMixDemo 4 | // 5 | // Created by PIYUSH GHOGHARI on 22/04/20. 6 | // Copyright © 2020 PIYUSH GHOGHARI. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import AVFoundation 11 | import AVKit 12 | class AudioMergeViewController: UIViewController { 13 | 14 | // MARK: - All IBOutlet's for this UIViewController 15 | 16 | @IBOutlet weak var player1View: UIView! 17 | @IBOutlet weak var player2View: UIView! 18 | @IBOutlet weak var finalPlayerView: UIView! 19 | @IBOutlet weak var filePathValue: UILabel! 20 | 21 | // Variables 22 | var audioRecord1URL: URL! 23 | var audioRecord2URL: URL! 24 | var tempURL: URL! 25 | 26 | override func viewDidLoad() { 27 | super.viewDidLoad() 28 | 29 | let asset1 : AVURLAsset = AVURLAsset.init(url: audioRecord1URL, options: nil) 30 | let playerView1 = SYWaveformPlayerView(frame: CGRect(x: 0, y: 5, width: Constants.SCREEN_SIZES.WIDTH - 50, height: 60), asset: asset1, color: UIColor.gray, progressColor: UIColor(red: 132.0/255.0, green: 112.0/255.0, blue: 255.0/255.0, alpha: 1.0)) 31 | self.player1View.addSubview(playerView1!) 32 | 33 | let asset2 : AVURLAsset = AVURLAsset.init(url: audioRecord2URL, options: nil) 34 | let playerView2 = SYWaveformPlayerView(frame: CGRect(x: 0, y: 5, width: Constants.SCREEN_SIZES.WIDTH - 50, height: 60), asset: asset2, color: UIColor.gray, progressColor: UIColor(red: 132.0/255.0, green: 112.0/255.0, blue: 255.0/255.0, alpha: 1.0)) 35 | self.player2View.addSubview(playerView2!) 36 | // Do any additional setup after loading the view. 37 | } 38 | 39 | 40 | @IBAction func overLAyAudioButttonClick(_ sender: UIButton) { 41 | self.view.endEditing(true) 42 | self.mixAudio() 43 | } 44 | 45 | 46 | func mixAudio() { 47 | let composition = AVMutableComposition() 48 | let tracks:[URL] = [audioRecord1URL, audioRecord2URL] 49 | 50 | for trackName in tracks { 51 | let audioAsset = AVURLAsset(url: trackName, options: nil) 52 | let audioTrack: AVMutableCompositionTrack? = composition.addMutableTrack(withMediaType: AVMediaType.audio, preferredTrackID: kCMPersistentTrackID_Invalid) 53 | try? audioTrack?.insertTimeRange(CMTimeRangeMake(start: CMTime.zero, duration: audioAsset.duration), of: audioAsset.tracks(withMediaType: AVMediaType.audio)[0], at: CMTime.zero) 54 | } 55 | 56 | let assetExport = AVAssetExportSession(asset: composition, presetName: AVAssetExportPresetAppleM4A) 57 | 58 | let date :NSDate = NSDate() 59 | let dateFormatter = DateFormatter() 60 | dateFormatter.dateFormat = "yyyy-MM-dd'_'HH_mm_ss" 61 | 62 | let filename = "AudioMix\(dateFormatter.string(from: date as Date)).m4a" 63 | let filePath = getDocumentsDirectory().appendingPathComponent(filename) 64 | 65 | assetExport?.outputFileType = AVFileType.m4a 66 | assetExport?.outputURL = filePath 67 | assetExport?.shouldOptimizeForNetworkUse = true 68 | 69 | assetExport?.exportAsynchronously(completionHandler: {() -> Void in 70 | print("Completed Sucessfully") 71 | print("filePath: ->\(filePath)") 72 | 73 | 74 | }) 75 | 76 | self.filePathValue.text = "FileManager/ApplicationName/audio/Mix/\(filename)" 77 | 78 | let alert = UIAlertController(title: "AudioMixDemo", message: "Completed Sucessfully", preferredStyle: UIAlertController.Style.alert) 79 | 80 | 81 | 82 | alert.addAction(image: nil, title: "OK", color: .black, style: .default) { action in 83 | // completion handler 84 | let asset2 : AVURLAsset = AVURLAsset.init(url: self.getDocumentsDirectory().appendingPathComponent(filename), options: nil) 85 | let playerView2 = SYWaveformPlayerView(frame: CGRect(x: 0, y: 5, width: Constants.SCREEN_SIZES.WIDTH - 50, height: 60), asset: asset2, color: UIColor.gray, progressColor: UIColor(red: 132.0/255.0, green: 112.0/255.0, blue: 255.0/255.0, alpha: 1.0)) 86 | self.finalPlayerView.addSubview(playerView2!) 87 | } 88 | 89 | // show the alert 90 | self.present(alert, animated: true, completion: nil) 91 | } 92 | 93 | 94 | func getDocumentsDirectory() -> URL{ 95 | let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask) 96 | let paths = documentsDirectory[0].appendingPathComponent("audio/Mix") 97 | do{ 98 | try FileManager.default.createDirectory(atPath: paths.path, withIntermediateDirectories: true, attributes: nil) 99 | }catch let error as NSError{ 100 | NSLog("Unable to create directory \(error.debugDescription)") 101 | } 102 | return paths 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /AudioMixDemo/ViewController/AudioRecord2ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AudioRecord2ViewController.swift 3 | // AudioMixDemo 4 | // 5 | // Created by PIYUSH GHOGHARI on 22/04/20. 6 | // Copyright © 2020 PIYUSH GHOGHARI. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class AudioRecord2ViewController: UIViewController { 12 | // MARK: - All IBoutlet's for this UIViewController 13 | @IBOutlet weak var progressView: UIView! 14 | @IBOutlet weak var vwAudioPlayerMain: UIView! 15 | @IBOutlet weak var recordButtonLabel: UILabel! 16 | 17 | var rippleLayer = RippleLayer() 18 | 19 | //Internal audio recording variables 20 | var isRecordingStart = false 21 | var isRecording = false 22 | var isAudioRecording = false 23 | var audioRecorder: AVAudioRecorder! 24 | var audioPlayer : AVAudioPlayer! 25 | var audioData = Data() 26 | var recordAudioURL : URL! 27 | var objSYWaveformPlayer: SYWaveformPlayerView! 28 | 29 | var audioRecord1URL: URL! 30 | var tempAudioURl: URL! 31 | override func viewDidLoad() { 32 | super.viewDidLoad() 33 | self.navigationController?.title = "Record Audio 2" 34 | // Do any additional setup after loading the view. 35 | } 36 | 37 | 38 | @IBAction func audioRecordingButtonClick(_ sender: UIControl) { 39 | self.view.endEditing(true) 40 | if isAudioRecording == false { 41 | recordButtonLabel.text = "Stop Recording" 42 | isAudioRecording = true 43 | self.startProgress() 44 | self.setUpRecording() 45 | } else { 46 | isAudioRecording = false 47 | self.finishAudioRecording(success: true) 48 | self.rippleLayer.stopAnimation() 49 | 50 | } 51 | } 52 | 53 | @IBAction func nextButtonClick(_ sender: UIControl) { 54 | self.view.endEditing(true) 55 | if isAudioRecording == true { 56 | isAudioRecording = false 57 | self.finishAudioRecording(success: true) 58 | self.rippleLayer.stopAnimation() 59 | } 60 | 61 | 62 | 63 | 64 | let objudioMergeVC = self.storyboard?.instantiateViewController(withIdentifier: "AudioMergeViewController")as! AudioMergeViewController 65 | objudioMergeVC.audioRecord1URL = audioRecord1URL 66 | objudioMergeVC.audioRecord2URL = tempAudioURl 67 | self.navigationController?.pushViewController(objudioMergeVC, animated: true) 68 | 69 | } 70 | 71 | func startProgress() { 72 | self.rippleLayer.position = CGPoint(x: self.progressView.layer.bounds.midX, y: self.progressView.layer.bounds.midY); 73 | self.rippleLayer.startAnimation() 74 | self.progressView.layer.addSublayer(self.rippleLayer) 75 | 76 | } 77 | 78 | } 79 | 80 | extension AudioRecord2ViewController: AVAudioRecorderDelegate, AVAudioPlayerDelegate { 81 | //---------------------------- 82 | // Recording Code - Start 83 | //---------------------------- 84 | 85 | //MARK: - Internal Audio Recording Code 86 | func setUpRecording(){ 87 | self.isRecordingStart = true 88 | self.setup_recorder() 89 | self.audioRecorder.record() 90 | 91 | self.isRecording = true 92 | } 93 | 94 | //record File Path 95 | func getDocumentsDirectory() -> URL{ 96 | let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask) 97 | let paths = documentsDirectory[0].appendingPathComponent("audio") 98 | do{ 99 | try FileManager.default.createDirectory(atPath: paths.path, withIntermediateDirectories: true, attributes: nil) 100 | }catch let error as NSError{ 101 | NSLog("Unable to create directory \(error.debugDescription)") 102 | } 103 | return paths 104 | } 105 | 106 | func getFileUrl() -> URL{ 107 | if recordAudioURL == nil { 108 | let date :NSDate = NSDate() 109 | let dateFormatter = DateFormatter() 110 | dateFormatter.dateFormat = "yyyy-MM-dd'_'HH_mm_ss" 111 | 112 | let filename = "\(dateFormatter.string(from: date as Date)).m4a" 113 | let filePath = getDocumentsDirectory().appendingPathComponent(filename) 114 | recordAudioURL = filePath 115 | 116 | 117 | } 118 | return recordAudioURL 119 | } 120 | 121 | func setup_recorder(){ 122 | let session = AVAudioSession.sharedInstance() 123 | do{ 124 | try session.setCategory(.playAndRecord, options: .defaultToSpeaker) 125 | try session.setActive(true) 126 | let settings = [ 127 | AVFormatIDKey: Int(kAudioFormatMPEG4AAC), 128 | AVSampleRateKey: 44100, 129 | AVNumberOfChannelsKey: 2, 130 | AVEncoderAudioQualityKey:AVAudioQuality.high.rawValue 131 | ] 132 | audioRecorder = try AVAudioRecorder(url: getFileUrl(), settings: settings) 133 | audioRecorder.delegate = self 134 | audioRecorder.isMeteringEnabled = true 135 | audioRecorder.prepareToRecord() 136 | } 137 | catch let error { 138 | display_alert(msg_title: "Error", msg_desc: error.localizedDescription, action_title: "OK") 139 | } 140 | } 141 | 142 | //Record Timer Function. 143 | @objc func updateAudioMeter(timer: Timer){ 144 | if(self.isRecordingStart == true){ 145 | if audioRecorder.isRecording{ 146 | let hr = Int((audioRecorder.currentTime / 60) / 60) 147 | let min = Int(audioRecorder.currentTime / 60) 148 | let sec = Int(audioRecorder.currentTime.truncatingRemainder(dividingBy: 60)) 149 | let totalTimeString = String(format: "%02d:%02d:%02d", hr, min, sec) 150 | print("Recording Time : ->",totalTimeString) 151 | audioRecorder.updateMeters() 152 | } 153 | } 154 | } 155 | 156 | func finishAudioRecording(success: Bool){ 157 | if success{ 158 | audioRecorder.stop() 159 | audioRecorder = nil 160 | let saveAudioURL = getFileUrl() 161 | audioData = try! Data(contentsOf: saveAudioURL) 162 | print("data \(audioData)") 163 | 164 | print("audioURL2: -> \(recordAudioURL)") 165 | let asset : AVURLAsset = AVURLAsset.init(url: recordAudioURL, options: nil) 166 | let playerView = SYWaveformPlayerView(frame: CGRect(x: 0, y: 5, width: Constants.SCREEN_SIZES.WIDTH - 50, height: 60), asset: asset, color: UIColor.gray, progressColor: UIColor(red: 132.0/255.0, green: 112.0/255.0, blue: 255.0/255.0, alpha: 1.0)) 167 | self.vwAudioPlayerMain.addSubview(playerView!) 168 | 169 | tempAudioURl = recordAudioURL 170 | recordAudioURL = nil 171 | }else{ 172 | display_alert(msg_title: "Error", msg_desc: "Recording failed.", action_title: "OK") 173 | } 174 | } 175 | 176 | func audioRecorderDidFinishRecording(_ recorder: AVAudioRecorder, successfully flag: Bool){ 177 | if !flag{ 178 | finishAudioRecording(success: false) 179 | } 180 | } 181 | 182 | func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool){} 183 | 184 | func display_alert(msg_title : String , msg_desc : String ,action_title : String){ 185 | let ac = UIAlertController(title: msg_title, message: msg_desc, preferredStyle: .alert) 186 | // ac.addAction(UIAlertAction(title: action_title, style: .default){ 187 | // (result : UIAlertAction) -> Void in 188 | // }) 189 | ac.addAction(UIAlertAction(title: action_title, style: .default, handler: nil)) 190 | present(ac, animated: true) 191 | } 192 | 193 | //---------------------------- 194 | // Recording Code - End 195 | //---------------------------- 196 | } 197 | -------------------------------------------------------------------------------- /AudioMixDemo/ViewController/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // AudioMixDemo 4 | // 5 | // Created by PIYUSH GHOGHARI on 22/04/20. 6 | // Copyright © 2020 PIYUSH GHOGHARI. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import AVKit 11 | import AVFoundation 12 | 13 | class ViewController: UIViewController { 14 | 15 | // MARK: - All IBoutlet's for this UIViewController 16 | @IBOutlet weak var progressView: UIView! 17 | @IBOutlet weak var vwAudioPlayerMain: UIView! 18 | @IBOutlet weak var recordButtonLabel: UILabel! 19 | 20 | var rippleLayer = RippleLayer() 21 | 22 | //Internal audio recording variables 23 | var isRecordingStart = false 24 | var isRecording = false 25 | var isAudioRecording = false 26 | var audioRecorder: AVAudioRecorder! 27 | var audioPlayer : AVAudioPlayer! 28 | var audioData = Data() 29 | var recordAudioURL : URL! 30 | var objSYWaveformPlayer: SYWaveformPlayerView! 31 | var tempAudioURl: URL! 32 | 33 | override func viewDidLoad() { 34 | super.viewDidLoad() 35 | // Do any additional setup after loading the view. 36 | } 37 | 38 | @IBAction func audioRecordingButtonClick(_ sender: UIControl) { 39 | self.view.endEditing(true) 40 | if isAudioRecording == false { 41 | recordButtonLabel.text = "Stop Recording" 42 | isAudioRecording = true 43 | self.startProgress() 44 | self.setUpRecording() 45 | } else { 46 | isAudioRecording = false 47 | self.finishAudioRecording(success: true) 48 | self.rippleLayer.stopAnimation() 49 | 50 | } 51 | } 52 | 53 | @IBAction func nextButtonClick(_ sender: UIControl) { 54 | self.view.endEditing(true) 55 | if isAudioRecording == true { 56 | isAudioRecording = false 57 | self.finishAudioRecording(success: true) 58 | self.rippleLayer.stopAnimation() 59 | } 60 | 61 | let objAudio2VC = self.storyboard?.instantiateViewController(withIdentifier: "AudioRecord2ViewController")as! AudioRecord2ViewController 62 | objAudio2VC.audioRecord1URL = tempAudioURl 63 | self.navigationController?.pushViewController(objAudio2VC, animated: true) 64 | } 65 | 66 | func startProgress() { 67 | self.rippleLayer.position = CGPoint(x: self.progressView.layer.bounds.midX, y: self.progressView.layer.bounds.midY); 68 | self.rippleLayer.startAnimation() 69 | self.progressView.layer.addSublayer(self.rippleLayer) 70 | 71 | } 72 | } 73 | 74 | 75 | extension ViewController: AVAudioRecorderDelegate, AVAudioPlayerDelegate { 76 | //---------------------------- 77 | // Recording Code - Start 78 | //---------------------------- 79 | 80 | //MARK: - Internal Audio Recording Code 81 | func setUpRecording(){ 82 | self.isRecordingStart = true 83 | self.setup_recorder() 84 | self.audioRecorder.record() 85 | 86 | self.isRecording = true 87 | } 88 | 89 | //record File Path 90 | func getDocumentsDirectory() -> URL{ 91 | let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask) 92 | let paths = documentsDirectory[0].appendingPathComponent("audio") 93 | do{ 94 | try FileManager.default.createDirectory(atPath: paths.path, withIntermediateDirectories: true, attributes: nil) 95 | }catch let error as NSError{ 96 | NSLog("Unable to create directory \(error.debugDescription)") 97 | } 98 | return paths 99 | } 100 | 101 | func getFileUrl() -> URL{ 102 | if recordAudioURL == nil { 103 | let date :NSDate = NSDate() 104 | let dateFormatter = DateFormatter() 105 | dateFormatter.dateFormat = "yyyy-MM-dd'_'HH_mm_ss" 106 | 107 | let filename = "\(dateFormatter.string(from: date as Date)).m4a" 108 | let filePath = getDocumentsDirectory().appendingPathComponent(filename) 109 | recordAudioURL = filePath 110 | 111 | 112 | } 113 | return recordAudioURL 114 | } 115 | 116 | func setup_recorder(){ 117 | let session = AVAudioSession.sharedInstance() 118 | do{ 119 | try session.setCategory(.playAndRecord, options: .defaultToSpeaker) 120 | try session.setActive(true) 121 | let settings = [ 122 | AVFormatIDKey: Int(kAudioFormatMPEG4AAC), 123 | AVSampleRateKey: 44100, 124 | AVNumberOfChannelsKey: 2, 125 | AVEncoderAudioQualityKey:AVAudioQuality.high.rawValue 126 | ] 127 | audioRecorder = try AVAudioRecorder(url: getFileUrl(), settings: settings) 128 | audioRecorder.delegate = self 129 | audioRecorder.isMeteringEnabled = true 130 | audioRecorder.prepareToRecord() 131 | } 132 | catch let error { 133 | display_alert(msg_title: "Error", msg_desc: error.localizedDescription, action_title: "OK") 134 | } 135 | } 136 | 137 | //Record Timer Function. 138 | @objc func updateAudioMeter(timer: Timer){ 139 | if(self.isRecordingStart == true){ 140 | if audioRecorder.isRecording{ 141 | let hr = Int((audioRecorder.currentTime / 60) / 60) 142 | let min = Int(audioRecorder.currentTime / 60) 143 | let sec = Int(audioRecorder.currentTime.truncatingRemainder(dividingBy: 60)) 144 | let totalTimeString = String(format: "%02d:%02d:%02d", hr, min, sec) 145 | print("Recording Time : ->",totalTimeString) 146 | audioRecorder.updateMeters() 147 | } 148 | } 149 | } 150 | 151 | func finishAudioRecording(success: Bool){ 152 | if success{ 153 | audioRecorder.stop() 154 | audioRecorder = nil 155 | let saveAudioURL = getFileUrl() 156 | audioData = try! Data(contentsOf: saveAudioURL) 157 | print("data \(audioData)") 158 | 159 | print("audioURL1: -> \(recordAudioURL)") 160 | 161 | let asset : AVURLAsset = AVURLAsset.init(url: recordAudioURL, options: nil) 162 | let playerView = SYWaveformPlayerView(frame: CGRect(x: 0, y: 5, width: Constants.SCREEN_SIZES.WIDTH - 50, height: 60), asset: asset, color: UIColor.gray, progressColor: UIColor(red: 132.0/255.0, green: 112.0/255.0, blue: 255.0/255.0, alpha: 1.0)) 163 | self.vwAudioPlayerMain.addSubview(playerView!) 164 | tempAudioURl = recordAudioURL 165 | recordAudioURL = nil 166 | }else{ 167 | display_alert(msg_title: "Error", msg_desc: "Recording failed.", action_title: "OK") 168 | } 169 | } 170 | 171 | func audioRecorderDidFinishRecording(_ recorder: AVAudioRecorder, successfully flag: Bool){ 172 | if !flag{ 173 | finishAudioRecording(success: false) 174 | } 175 | } 176 | 177 | func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool){} 178 | 179 | func display_alert(msg_title : String , msg_desc : String ,action_title : String){ 180 | let ac = UIAlertController(title: msg_title, message: msg_desc, preferredStyle: .alert) 181 | // ac.addAction(UIAlertAction(title: action_title, style: .default){ 182 | // (result : UIAlertAction) -> Void in 183 | // }) 184 | ac.addAction(UIAlertAction(title: action_title, style: .default, handler: nil)) 185 | present(ac, animated: true) 186 | } 187 | 188 | //---------------------------- 189 | // Recording Code - End 190 | //---------------------------- 191 | } 192 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Piyush Ghoghari 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AKAudioOverlap 2 | 3 | 4 | 5 | ## Description 6 | 7 | AKAudioOverlap is audio recording, convert audio to wavefoem, overlap two audio and export/ save into local device. 8 | 9 | ## Building the Project 10 | 11 | 1. Clone the repo: 12 | 13 | > git clone https://github.com/piyushghoghari08/AKAudioOverlap.git 14 | 15 | 2. Open the project from "AudioMixDemo.xcodeproj" file with latest Xcode version 11.3.1, support langauge swift 5.1 and build 16 | 17 | 3. Select target and device on top and click on play button (project is required minimum iOS version 13.0 and compatible with iPhone and iPad) 18 | 19 | 20 |

Features

21 | 22 |

> Audio Recording.

23 |

> Convert audio recoding file to waveform.

24 |

> Overlap audio.

25 |

> Save into local storage.

26 | 27 |

Dependencies

28 | SYWaveformPlayer
29 | -------------------------------------------------------------------------------- /ScreenShort/appScreenShort.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piyushghoghari08/AKAudioOverlap/f811282228cc3e2a17c377a38085e4408f8a2cbf/ScreenShort/appScreenShort.png --------------------------------------------------------------------------------