├── .gitignore ├── iperf-swiftui.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ ├── WorkspaceSettings.xcsettings │ │ └── swiftpm │ │ └── Package.resolved └── xcshareddata │ └── xcschemes │ └── iperf3-swiftui.xcscheme ├── iperf-swiftui ├── AppDelegate.swift ├── Assets.xcassets │ ├── AccentColor.colorset │ │ └── Contents.json │ ├── AppIcon.appiconset │ │ ├── Contents.json │ │ ├── Icon-App-167x167.png │ │ ├── Icon-App-20x20@2x.png │ │ ├── Icon-App-20x20@3x.png │ │ ├── Icon-App-29x29@1x.png │ │ ├── Icon-App-29x29@2x.png │ │ ├── Icon-App-29x29@3x.png │ │ ├── Icon-App-40x40@1x.png │ │ ├── Icon-App-40x40@2x.png │ │ ├── Icon-App-40x40@3x.png │ │ ├── Icon-App-60x60@2x.png │ │ ├── Icon-App-60x60@3x.png │ │ ├── Icon-App-76x76@1x.png │ │ ├── Icon-App-76x76@2x.png │ │ └── iTunesArtwork@2x.png │ └── Contents.json ├── Base.lproj │ └── LaunchScreen.storyboard ├── ContentView.swift ├── Info.plist ├── IperfPresetsController.swift ├── IperfRunnerController.swift ├── IperfRunnerTestsController.swift ├── Preview Content │ └── Preview Assets.xcassets │ │ └── Contents.json ├── SceneDelegate.swift ├── SingleTestView.swift ├── components │ ├── AddressPortStack.swift │ ├── OptionsPicker.swift │ ├── OptionsPickerIcon.swift │ ├── RateOption.swift │ ├── SelectServerRow.swift │ ├── ServerEntry.swift │ ├── ServersList.swift │ ├── StartButton.swift │ ├── TextFieldWithLabel.swift │ └── TextFieldWithOption.swift ├── extensions │ ├── IperfIntervalResultArray.swift │ ├── IperfRunnerState+HasDescription.swift │ ├── IperfSwift+HasDescription.swift │ ├── IperfSwift+IperfConfigurationInput.swift │ └── IperfThroughput+pretty.swift ├── iperf-swiftui.entitlements ├── servers.json └── views │ ├── InlineSettingsView.swift │ ├── IperfTestView.swift │ ├── MoreSettingsView.swift │ ├── ResultsView.swift │ ├── SelectPortView.swift │ └── SelectServerView.swift ├── iperf-swiftuiTests ├── Info.plist ├── iperf-swiftuiRunnerTests.swift └── iperf-swiftuiTests.swift ├── license.md ├── public ├── client-main.png ├── params-tcp.png ├── params-udp.png └── server-main.png └── readme.md /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | # Xcode 3 | # 4 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 5 | 6 | ## User settings 7 | xcuserdata/ 8 | 9 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) 10 | *.xcscmblueprint 11 | *.xccheckout 12 | 13 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) 14 | build/ 15 | DerivedData/ 16 | *.moved-aside 17 | *.pbxuser 18 | !default.pbxuser 19 | *.mode1v3 20 | !default.mode1v3 21 | *.mode2v3 22 | !default.mode2v3 23 | *.perspectivev3 24 | !default.perspectivev3 25 | 26 | ## Obj-C/Swift specific 27 | *.hmap 28 | 29 | ## App packaging 30 | *.ipa 31 | *.dSYM.zip 32 | *.dSYM 33 | 34 | ## Playgrounds 35 | timeline.xctimeline 36 | playground.xcworkspace 37 | 38 | # Swift Package Manager 39 | # 40 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 41 | # Packages/ 42 | # Package.pins 43 | # Package.resolved 44 | # *.xcodeproj 45 | # 46 | # Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata 47 | # hence it is not needed unless you have added a package configuration file to your project 48 | # .swiftpm 49 | 50 | .build/ 51 | 52 | # CocoaPods 53 | # 54 | # We recommend against adding the Pods directory to your .gitignore. However 55 | # you should judge for yourself, the pros and cons are mentioned at: 56 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 57 | # 58 | # Pods/ 59 | # 60 | # Add this line if you want to avoid checking in source code from the Xcode workspace 61 | # *.xcworkspace 62 | 63 | # Carthage 64 | # 65 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 66 | # Carthage/Checkouts 67 | 68 | Carthage/Build/ 69 | 70 | # Accio dependency management 71 | Dependencies/ 72 | .accio/ 73 | 74 | # fastlane 75 | # 76 | # It is recommended to not store the screenshots in the git repo. 77 | # Instead, use fastlane to re-generate the screenshots whenever they are needed. 78 | # For more information about the recommended setup visit: 79 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 80 | 81 | fastlane/report.xml 82 | fastlane/Preview.html 83 | fastlane/screenshots/**/*.png 84 | fastlane/test_output 85 | 86 | # Code Injection 87 | # 88 | # After new code Injection tools there's a generated folder /iOSInjectionProject 89 | # https://github.com/johnno1962/injectionforxcode 90 | 91 | iOSInjectionProject/ 92 | -------------------------------------------------------------------------------- /iperf-swiftui.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 52; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | CB702682255828EF00BCC15F /* IperfSwift+HasDescription.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB702681255828EF00BCC15F /* IperfSwift+HasDescription.swift */; }; 11 | CB702688255829BE00BCC15F /* IperfSwift+IperfConfigurationInput.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB702687255829BE00BCC15F /* IperfSwift+IperfConfigurationInput.swift */; }; 12 | CB70269425582C9600BCC15F /* RateOption.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB70269325582C9600BCC15F /* RateOption.swift */; }; 13 | CB7026BE2559740F00BCC15F /* servers.json in Resources */ = {isa = PBXBuildFile; fileRef = CB7026BD2559740E00BCC15F /* servers.json */; }; 14 | CB7026C225597A3F00BCC15F /* ServerEntry.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB7026C125597A3F00BCC15F /* ServerEntry.swift */; }; 15 | CB7026C825597FB700BCC15F /* SelectServerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB7026C725597FB700BCC15F /* SelectServerView.swift */; }; 16 | CB7026CC2559879000BCC15F /* SelectServerRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB7026CB2559879000BCC15F /* SelectServerRow.swift */; }; 17 | CB7026D42559884800BCC15F /* SelectPortView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB7026D32559884800BCC15F /* SelectPortView.swift */; }; 18 | CB7026D825598ED900BCC15F /* AddressPortStack.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB7026D725598ED900BCC15F /* AddressPortStack.swift */; }; 19 | CB7026DC25598F3300BCC15F /* IperfRunnerController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB7026DB25598F3300BCC15F /* IperfRunnerController.swift */; }; 20 | CB7026E02559958400BCC15F /* IperfPresetsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB7026DF2559958400BCC15F /* IperfPresetsController.swift */; }; 21 | CB7026E42559B44E00BCC15F /* ServersList.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB7026E32559B44E00BCC15F /* ServersList.swift */; }; 22 | CB7026E8255A8A3400BCC15F /* IperfRunnerTestsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB7026E7255A8A3400BCC15F /* IperfRunnerTestsController.swift */; }; 23 | CB7026F0255A9E8E00BCC15F /* IperfTestView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB7026EF255A9E8E00BCC15F /* IperfTestView.swift */; }; 24 | CB7026F4255AC66500BCC15F /* IperfIntervalResultArray.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB7026F3255AC66500BCC15F /* IperfIntervalResultArray.swift */; }; 25 | CB7026F8255AC68900BCC15F /* IperfRunnerState+HasDescription.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB7026F7255AC68900BCC15F /* IperfRunnerState+HasDescription.swift */; }; 26 | CB7026FC255AF30A00BCC15F /* SingleTestView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB7026FB255AF30A00BCC15F /* SingleTestView.swift */; }; 27 | CB702700255AF3B600BCC15F /* InlineSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB7026FF255AF3B600BCC15F /* InlineSettingsView.swift */; }; 28 | CB702704255AF85A00BCC15F /* IperfThroughput+pretty.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB702703255AF85A00BCC15F /* IperfThroughput+pretty.swift */; }; 29 | CB9624292556C86300E096F6 /* TextFieldWithOption.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB9624282556C86300E096F6 /* TextFieldWithOption.swift */; }; 30 | CBBC806D25489E9C00D19BFF /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBBC806C25489E9C00D19BFF /* AppDelegate.swift */; }; 31 | CBBC806F25489E9C00D19BFF /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBBC806E25489E9C00D19BFF /* SceneDelegate.swift */; }; 32 | CBBC807125489E9C00D19BFF /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBBC807025489E9C00D19BFF /* ContentView.swift */; }; 33 | CBBC807325489E9E00D19BFF /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = CBBC807225489E9E00D19BFF /* Assets.xcassets */; }; 34 | CBBC807625489E9E00D19BFF /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = CBBC807525489E9E00D19BFF /* Preview Assets.xcassets */; }; 35 | CBBC807925489E9E00D19BFF /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = CBBC807725489E9E00D19BFF /* LaunchScreen.storyboard */; }; 36 | CBC26D3328E18F2C00483F44 /* IperfSwift in Frameworks */ = {isa = PBXBuildFile; productRef = CBC26D3228E18F2C00483F44 /* IperfSwift */; }; 37 | CBCA40A92549FECB009DA63C /* OptionsPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBCA40A82549FECB009DA63C /* OptionsPicker.swift */; }; 38 | CBCA40AD2549FEF7009DA63C /* StartButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBCA40AC2549FEF7009DA63C /* StartButton.swift */; }; 39 | CBCA40B1254A238E009DA63C /* TextFieldWithLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBCA40B0254A238E009DA63C /* TextFieldWithLabel.swift */; }; 40 | CBCA40B4254A2668009DA63C /* ResultsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBCA40B3254A2668009DA63C /* ResultsView.swift */; }; 41 | CBCA40B7254A267F009DA63C /* MoreSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBCA40B6254A267F009DA63C /* MoreSettingsView.swift */; }; 42 | CBDB6A002556FF3A0010443A /* OptionsPickerIcon.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBDB69FF2556FF3A0010443A /* OptionsPickerIcon.swift */; }; 43 | CBDB6A0A255753480010443A /* iperf-swiftuiTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBDB6A09255753480010443A /* iperf-swiftuiTests.swift */; }; 44 | CBDB6A1625575F910010443A /* iperf-swiftuiRunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBDB6A1525575F910010443A /* iperf-swiftuiRunnerTests.swift */; }; 45 | /* End PBXBuildFile section */ 46 | 47 | /* Begin PBXContainerItemProxy section */ 48 | CBDB6A0C255753480010443A /* PBXContainerItemProxy */ = { 49 | isa = PBXContainerItemProxy; 50 | containerPortal = CBBC806125489E9C00D19BFF /* Project object */; 51 | proxyType = 1; 52 | remoteGlobalIDString = CBBC806825489E9C00D19BFF; 53 | remoteInfo = "iperf3-swift"; 54 | }; 55 | /* End PBXContainerItemProxy section */ 56 | 57 | /* Begin PBXFileReference section */ 58 | CB702681255828EF00BCC15F /* IperfSwift+HasDescription.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "IperfSwift+HasDescription.swift"; sourceTree = ""; }; 59 | CB702687255829BE00BCC15F /* IperfSwift+IperfConfigurationInput.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "IperfSwift+IperfConfigurationInput.swift"; sourceTree = ""; }; 60 | CB70269325582C9600BCC15F /* RateOption.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RateOption.swift; sourceTree = ""; }; 61 | CB7026BD2559740E00BCC15F /* servers.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = servers.json; sourceTree = ""; }; 62 | CB7026C125597A3F00BCC15F /* ServerEntry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerEntry.swift; sourceTree = ""; }; 63 | CB7026C725597FB700BCC15F /* SelectServerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectServerView.swift; sourceTree = ""; }; 64 | CB7026CB2559879000BCC15F /* SelectServerRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = SelectServerRow.swift; path = "iperf-swiftui/components/SelectServerRow.swift"; sourceTree = SOURCE_ROOT; }; 65 | CB7026D32559884800BCC15F /* SelectPortView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectPortView.swift; sourceTree = ""; }; 66 | CB7026D725598ED900BCC15F /* AddressPortStack.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddressPortStack.swift; sourceTree = ""; }; 67 | CB7026DB25598F3300BCC15F /* IperfRunnerController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IperfRunnerController.swift; sourceTree = ""; }; 68 | CB7026DF2559958400BCC15F /* IperfPresetsController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IperfPresetsController.swift; sourceTree = ""; }; 69 | CB7026E32559B44E00BCC15F /* ServersList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServersList.swift; sourceTree = ""; }; 70 | CB7026E7255A8A3400BCC15F /* IperfRunnerTestsController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IperfRunnerTestsController.swift; sourceTree = ""; }; 71 | CB7026EF255A9E8E00BCC15F /* IperfTestView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IperfTestView.swift; sourceTree = ""; }; 72 | CB7026F3255AC66500BCC15F /* IperfIntervalResultArray.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IperfIntervalResultArray.swift; sourceTree = ""; }; 73 | CB7026F7255AC68900BCC15F /* IperfRunnerState+HasDescription.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "IperfRunnerState+HasDescription.swift"; sourceTree = ""; }; 74 | CB7026FB255AF30A00BCC15F /* SingleTestView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SingleTestView.swift; sourceTree = ""; }; 75 | CB7026FF255AF3B600BCC15F /* InlineSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InlineSettingsView.swift; sourceTree = ""; }; 76 | CB702703255AF85A00BCC15F /* IperfThroughput+pretty.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "IperfThroughput+pretty.swift"; sourceTree = ""; }; 77 | CB9624282556C86300E096F6 /* TextFieldWithOption.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextFieldWithOption.swift; sourceTree = ""; }; 78 | CBBC806925489E9C00D19BFF /* iperf-swiftui.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "iperf-swiftui.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 79 | CBBC806C25489E9C00D19BFF /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 80 | CBBC806E25489E9C00D19BFF /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; 81 | CBBC807025489E9C00D19BFF /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; 82 | CBBC807225489E9E00D19BFF /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 83 | CBBC807525489E9E00D19BFF /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; 84 | CBBC807825489E9E00D19BFF /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 85 | CBBC807A25489E9E00D19BFF /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 86 | CBBC80C12548A40E00D19BFF /* iperf-swiftui.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "iperf-swiftui.entitlements"; sourceTree = ""; }; 87 | CBCA40A82549FECB009DA63C /* OptionsPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OptionsPicker.swift; sourceTree = ""; }; 88 | CBCA40AC2549FEF7009DA63C /* StartButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StartButton.swift; sourceTree = ""; }; 89 | CBCA40B0254A238E009DA63C /* TextFieldWithLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextFieldWithLabel.swift; sourceTree = ""; }; 90 | CBCA40B3254A2668009DA63C /* ResultsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResultsView.swift; sourceTree = ""; }; 91 | CBCA40B6254A267F009DA63C /* MoreSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MoreSettingsView.swift; sourceTree = ""; }; 92 | CBDB69FF2556FF3A0010443A /* OptionsPickerIcon.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OptionsPickerIcon.swift; sourceTree = ""; }; 93 | CBDB6A07255753480010443A /* iperf-swiftuiTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "iperf-swiftuiTests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; 94 | CBDB6A09255753480010443A /* iperf-swiftuiTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "iperf-swiftuiTests.swift"; sourceTree = ""; }; 95 | CBDB6A0B255753480010443A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 96 | CBDB6A1525575F910010443A /* iperf-swiftuiRunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "iperf-swiftuiRunnerTests.swift"; sourceTree = ""; }; 97 | /* End PBXFileReference section */ 98 | 99 | /* Begin PBXFrameworksBuildPhase section */ 100 | CBBC806625489E9C00D19BFF /* Frameworks */ = { 101 | isa = PBXFrameworksBuildPhase; 102 | buildActionMask = 2147483647; 103 | files = ( 104 | CBC26D3328E18F2C00483F44 /* IperfSwift in Frameworks */, 105 | ); 106 | runOnlyForDeploymentPostprocessing = 0; 107 | }; 108 | CBDB6A04255753480010443A /* Frameworks */ = { 109 | isa = PBXFrameworksBuildPhase; 110 | buildActionMask = 2147483647; 111 | files = ( 112 | ); 113 | runOnlyForDeploymentPostprocessing = 0; 114 | }; 115 | /* End PBXFrameworksBuildPhase section */ 116 | 117 | /* Begin PBXGroup section */ 118 | CB1F32D825544F2F0009DC0E /* components */ = { 119 | isa = PBXGroup; 120 | children = ( 121 | CBCA40A82549FECB009DA63C /* OptionsPicker.swift */, 122 | CBCA40AC2549FEF7009DA63C /* StartButton.swift */, 123 | CB7026D725598ED900BCC15F /* AddressPortStack.swift */, 124 | CB7026C125597A3F00BCC15F /* ServerEntry.swift */, 125 | CB70269325582C9600BCC15F /* RateOption.swift */, 126 | CB9624282556C86300E096F6 /* TextFieldWithOption.swift */, 127 | CB7026CB2559879000BCC15F /* SelectServerRow.swift */, 128 | CB7026E32559B44E00BCC15F /* ServersList.swift */, 129 | CBCA40B0254A238E009DA63C /* TextFieldWithLabel.swift */, 130 | CBDB69FF2556FF3A0010443A /* OptionsPickerIcon.swift */, 131 | ); 132 | path = components; 133 | sourceTree = ""; 134 | }; 135 | CB702680255828E300BCC15F /* extensions */ = { 136 | isa = PBXGroup; 137 | children = ( 138 | CB702681255828EF00BCC15F /* IperfSwift+HasDescription.swift */, 139 | CB702703255AF85A00BCC15F /* IperfThroughput+pretty.swift */, 140 | CB7026F7255AC68900BCC15F /* IperfRunnerState+HasDescription.swift */, 141 | CB7026F3255AC66500BCC15F /* IperfIntervalResultArray.swift */, 142 | CB702687255829BE00BCC15F /* IperfSwift+IperfConfigurationInput.swift */, 143 | ); 144 | path = extensions; 145 | sourceTree = ""; 146 | }; 147 | CBBC806025489E9C00D19BFF = { 148 | isa = PBXGroup; 149 | children = ( 150 | CBBC806B25489E9C00D19BFF /* iperf-swiftui */, 151 | CBDB6A08255753480010443A /* iperf-swiftuiTests */, 152 | CBBC806A25489E9C00D19BFF /* Products */, 153 | ); 154 | sourceTree = ""; 155 | }; 156 | CBBC806A25489E9C00D19BFF /* Products */ = { 157 | isa = PBXGroup; 158 | children = ( 159 | CBBC806925489E9C00D19BFF /* iperf-swiftui.app */, 160 | CBDB6A07255753480010443A /* iperf-swiftuiTests.xctest */, 161 | ); 162 | name = Products; 163 | sourceTree = ""; 164 | }; 165 | CBBC806B25489E9C00D19BFF /* iperf-swiftui */ = { 166 | isa = PBXGroup; 167 | children = ( 168 | CB702680255828E300BCC15F /* extensions */, 169 | CB1F32D825544F2F0009DC0E /* components */, 170 | CBCA40A72549FEBC009DA63C /* views */, 171 | CBBC80C12548A40E00D19BFF /* iperf-swiftui.entitlements */, 172 | CBBC806C25489E9C00D19BFF /* AppDelegate.swift */, 173 | CBBC806E25489E9C00D19BFF /* SceneDelegate.swift */, 174 | CBBC807025489E9C00D19BFF /* ContentView.swift */, 175 | CB7026DB25598F3300BCC15F /* IperfRunnerController.swift */, 176 | CB7026FB255AF30A00BCC15F /* SingleTestView.swift */, 177 | CB7026DF2559958400BCC15F /* IperfPresetsController.swift */, 178 | CB7026E7255A8A3400BCC15F /* IperfRunnerTestsController.swift */, 179 | CBBC807225489E9E00D19BFF /* Assets.xcassets */, 180 | CBBC807725489E9E00D19BFF /* LaunchScreen.storyboard */, 181 | CB7026BD2559740E00BCC15F /* servers.json */, 182 | CBBC807A25489E9E00D19BFF /* Info.plist */, 183 | CBBC807425489E9E00D19BFF /* Preview Content */, 184 | ); 185 | path = "iperf-swiftui"; 186 | sourceTree = ""; 187 | }; 188 | CBBC807425489E9E00D19BFF /* Preview Content */ = { 189 | isa = PBXGroup; 190 | children = ( 191 | CBBC807525489E9E00D19BFF /* Preview Assets.xcassets */, 192 | ); 193 | path = "Preview Content"; 194 | sourceTree = ""; 195 | }; 196 | CBCA40A72549FEBC009DA63C /* views */ = { 197 | isa = PBXGroup; 198 | children = ( 199 | CBCA40B3254A2668009DA63C /* ResultsView.swift */, 200 | CB7026EF255A9E8E00BCC15F /* IperfTestView.swift */, 201 | CBCA40B6254A267F009DA63C /* MoreSettingsView.swift */, 202 | CB7026C725597FB700BCC15F /* SelectServerView.swift */, 203 | CB7026FF255AF3B600BCC15F /* InlineSettingsView.swift */, 204 | CB7026D32559884800BCC15F /* SelectPortView.swift */, 205 | ); 206 | path = views; 207 | sourceTree = ""; 208 | }; 209 | CBDB6A08255753480010443A /* iperf-swiftuiTests */ = { 210 | isa = PBXGroup; 211 | children = ( 212 | CBDB6A09255753480010443A /* iperf-swiftuiTests.swift */, 213 | CBDB6A1525575F910010443A /* iperf-swiftuiRunnerTests.swift */, 214 | CBDB6A0B255753480010443A /* Info.plist */, 215 | ); 216 | path = "iperf-swiftuiTests"; 217 | sourceTree = ""; 218 | }; 219 | /* End PBXGroup section */ 220 | 221 | /* Begin PBXNativeTarget section */ 222 | CBBC806825489E9C00D19BFF /* iperf-swiftui */ = { 223 | isa = PBXNativeTarget; 224 | buildConfigurationList = CBBC807D25489E9E00D19BFF /* Build configuration list for PBXNativeTarget "iperf-swiftui" */; 225 | buildPhases = ( 226 | CBBC806525489E9C00D19BFF /* Sources */, 227 | CBBC806625489E9C00D19BFF /* Frameworks */, 228 | CBBC806725489E9C00D19BFF /* Resources */, 229 | ); 230 | buildRules = ( 231 | ); 232 | dependencies = ( 233 | ); 234 | name = "iperf-swiftui"; 235 | packageProductDependencies = ( 236 | CBC26D3228E18F2C00483F44 /* IperfSwift */, 237 | ); 238 | productName = "iperf3-swift"; 239 | productReference = CBBC806925489E9C00D19BFF /* iperf-swiftui.app */; 240 | productType = "com.apple.product-type.application"; 241 | }; 242 | CBDB6A06255753480010443A /* iperf-swiftuiTests */ = { 243 | isa = PBXNativeTarget; 244 | buildConfigurationList = CBDB6A0E255753480010443A /* Build configuration list for PBXNativeTarget "iperf-swiftuiTests" */; 245 | buildPhases = ( 246 | CBDB6A03255753480010443A /* Sources */, 247 | CBDB6A04255753480010443A /* Frameworks */, 248 | CBDB6A05255753480010443A /* Resources */, 249 | ); 250 | buildRules = ( 251 | ); 252 | dependencies = ( 253 | CBDB6A0D255753480010443A /* PBXTargetDependency */, 254 | ); 255 | name = "iperf-swiftuiTests"; 256 | productName = "iperf3-swiftTests"; 257 | productReference = CBDB6A07255753480010443A /* iperf-swiftuiTests.xctest */; 258 | productType = "com.apple.product-type.bundle.unit-test"; 259 | }; 260 | /* End PBXNativeTarget section */ 261 | 262 | /* Begin PBXProject section */ 263 | CBBC806125489E9C00D19BFF /* Project object */ = { 264 | isa = PBXProject; 265 | attributes = { 266 | LastSwiftUpdateCheck = 1210; 267 | LastUpgradeCheck = 1210; 268 | TargetAttributes = { 269 | CBBC806825489E9C00D19BFF = { 270 | CreatedOnToolsVersion = 12.1; 271 | }; 272 | CBDB6A06255753480010443A = { 273 | CreatedOnToolsVersion = 12.1; 274 | TestTargetID = CBBC806825489E9C00D19BFF; 275 | }; 276 | }; 277 | }; 278 | buildConfigurationList = CBBC806425489E9C00D19BFF /* Build configuration list for PBXProject "iperf-swiftui" */; 279 | compatibilityVersion = "Xcode 9.3"; 280 | developmentRegion = en; 281 | hasScannedForEncodings = 0; 282 | knownRegions = ( 283 | en, 284 | Base, 285 | ); 286 | mainGroup = CBBC806025489E9C00D19BFF; 287 | packageReferences = ( 288 | CBC26D3128E18F2C00483F44 /* XCRemoteSwiftPackageReference "iperf-swift" */, 289 | ); 290 | productRefGroup = CBBC806A25489E9C00D19BFF /* Products */; 291 | projectDirPath = ""; 292 | projectRoot = ""; 293 | targets = ( 294 | CBBC806825489E9C00D19BFF /* iperf-swiftui */, 295 | CBDB6A06255753480010443A /* iperf-swiftuiTests */, 296 | ); 297 | }; 298 | /* End PBXProject section */ 299 | 300 | /* Begin PBXResourcesBuildPhase section */ 301 | CBBC806725489E9C00D19BFF /* Resources */ = { 302 | isa = PBXResourcesBuildPhase; 303 | buildActionMask = 2147483647; 304 | files = ( 305 | CBBC807925489E9E00D19BFF /* LaunchScreen.storyboard in Resources */, 306 | CBBC807625489E9E00D19BFF /* Preview Assets.xcassets in Resources */, 307 | CB7026BE2559740F00BCC15F /* servers.json in Resources */, 308 | CBBC807325489E9E00D19BFF /* Assets.xcassets in Resources */, 309 | ); 310 | runOnlyForDeploymentPostprocessing = 0; 311 | }; 312 | CBDB6A05255753480010443A /* Resources */ = { 313 | isa = PBXResourcesBuildPhase; 314 | buildActionMask = 2147483647; 315 | files = ( 316 | ); 317 | runOnlyForDeploymentPostprocessing = 0; 318 | }; 319 | /* End PBXResourcesBuildPhase section */ 320 | 321 | /* Begin PBXSourcesBuildPhase section */ 322 | CBBC806525489E9C00D19BFF /* Sources */ = { 323 | isa = PBXSourcesBuildPhase; 324 | buildActionMask = 2147483647; 325 | files = ( 326 | CB702700255AF3B600BCC15F /* InlineSettingsView.swift in Sources */, 327 | CB70269425582C9600BCC15F /* RateOption.swift in Sources */, 328 | CBCA40B7254A267F009DA63C /* MoreSettingsView.swift in Sources */, 329 | CB7026CC2559879000BCC15F /* SelectServerRow.swift in Sources */, 330 | CB7026D42559884800BCC15F /* SelectPortView.swift in Sources */, 331 | CBDB6A002556FF3A0010443A /* OptionsPickerIcon.swift in Sources */, 332 | CB7026C225597A3F00BCC15F /* ServerEntry.swift in Sources */, 333 | CB7026F8255AC68900BCC15F /* IperfRunnerState+HasDescription.swift in Sources */, 334 | CBCA40AD2549FEF7009DA63C /* StartButton.swift in Sources */, 335 | CB7026FC255AF30A00BCC15F /* SingleTestView.swift in Sources */, 336 | CB7026DC25598F3300BCC15F /* IperfRunnerController.swift in Sources */, 337 | CB7026C825597FB700BCC15F /* SelectServerView.swift in Sources */, 338 | CBCA40A92549FECB009DA63C /* OptionsPicker.swift in Sources */, 339 | CB7026E8255A8A3400BCC15F /* IperfRunnerTestsController.swift in Sources */, 340 | CBBC806D25489E9C00D19BFF /* AppDelegate.swift in Sources */, 341 | CB7026D825598ED900BCC15F /* AddressPortStack.swift in Sources */, 342 | CB7026E42559B44E00BCC15F /* ServersList.swift in Sources */, 343 | CBCA40B1254A238E009DA63C /* TextFieldWithLabel.swift in Sources */, 344 | CBBC806F25489E9C00D19BFF /* SceneDelegate.swift in Sources */, 345 | CB702688255829BE00BCC15F /* IperfSwift+IperfConfigurationInput.swift in Sources */, 346 | CB9624292556C86300E096F6 /* TextFieldWithOption.swift in Sources */, 347 | CB7026F4255AC66500BCC15F /* IperfIntervalResultArray.swift in Sources */, 348 | CBCA40B4254A2668009DA63C /* ResultsView.swift in Sources */, 349 | CB702682255828EF00BCC15F /* IperfSwift+HasDescription.swift in Sources */, 350 | CBBC807125489E9C00D19BFF /* ContentView.swift in Sources */, 351 | CB7026E02559958400BCC15F /* IperfPresetsController.swift in Sources */, 352 | CB702704255AF85A00BCC15F /* IperfThroughput+pretty.swift in Sources */, 353 | CB7026F0255A9E8E00BCC15F /* IperfTestView.swift in Sources */, 354 | ); 355 | runOnlyForDeploymentPostprocessing = 0; 356 | }; 357 | CBDB6A03255753480010443A /* Sources */ = { 358 | isa = PBXSourcesBuildPhase; 359 | buildActionMask = 2147483647; 360 | files = ( 361 | CBDB6A1625575F910010443A /* iperf-swiftuiRunnerTests.swift in Sources */, 362 | CBDB6A0A255753480010443A /* iperf-swiftuiTests.swift in Sources */, 363 | ); 364 | runOnlyForDeploymentPostprocessing = 0; 365 | }; 366 | /* End PBXSourcesBuildPhase section */ 367 | 368 | /* Begin PBXTargetDependency section */ 369 | CBDB6A0D255753480010443A /* PBXTargetDependency */ = { 370 | isa = PBXTargetDependency; 371 | target = CBBC806825489E9C00D19BFF /* iperf-swiftui */; 372 | targetProxy = CBDB6A0C255753480010443A /* PBXContainerItemProxy */; 373 | }; 374 | /* End PBXTargetDependency section */ 375 | 376 | /* Begin PBXVariantGroup section */ 377 | CBBC807725489E9E00D19BFF /* LaunchScreen.storyboard */ = { 378 | isa = PBXVariantGroup; 379 | children = ( 380 | CBBC807825489E9E00D19BFF /* Base */, 381 | ); 382 | name = LaunchScreen.storyboard; 383 | sourceTree = ""; 384 | }; 385 | /* End PBXVariantGroup section */ 386 | 387 | /* Begin XCBuildConfiguration section */ 388 | CBBC807B25489E9E00D19BFF /* Debug */ = { 389 | isa = XCBuildConfiguration; 390 | buildSettings = { 391 | ALWAYS_SEARCH_USER_PATHS = NO; 392 | CLANG_ANALYZER_NONNULL = YES; 393 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 394 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 395 | CLANG_CXX_LIBRARY = "libc++"; 396 | CLANG_ENABLE_MODULES = YES; 397 | CLANG_ENABLE_OBJC_ARC = YES; 398 | CLANG_ENABLE_OBJC_WEAK = YES; 399 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 400 | CLANG_WARN_BOOL_CONVERSION = YES; 401 | CLANG_WARN_COMMA = YES; 402 | CLANG_WARN_CONSTANT_CONVERSION = YES; 403 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 404 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 405 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 406 | CLANG_WARN_EMPTY_BODY = YES; 407 | CLANG_WARN_ENUM_CONVERSION = YES; 408 | CLANG_WARN_INFINITE_RECURSION = YES; 409 | CLANG_WARN_INT_CONVERSION = YES; 410 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 411 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 412 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 413 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 414 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 415 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 416 | CLANG_WARN_STRICT_PROTOTYPES = YES; 417 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 418 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 419 | CLANG_WARN_UNREACHABLE_CODE = YES; 420 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 421 | COPY_PHASE_STRIP = NO; 422 | DEBUG_INFORMATION_FORMAT = dwarf; 423 | ENABLE_STRICT_OBJC_MSGSEND = YES; 424 | ENABLE_TESTABILITY = YES; 425 | GCC_C_LANGUAGE_STANDARD = gnu11; 426 | GCC_DYNAMIC_NO_PIC = NO; 427 | GCC_NO_COMMON_BLOCKS = YES; 428 | GCC_OPTIMIZATION_LEVEL = 0; 429 | GCC_PREPROCESSOR_DEFINITIONS = ( 430 | "DEBUG=1", 431 | "$(inherited)", 432 | ); 433 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 434 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 435 | GCC_WARN_UNDECLARED_SELECTOR = YES; 436 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 437 | GCC_WARN_UNUSED_FUNCTION = YES; 438 | GCC_WARN_UNUSED_VARIABLE = YES; 439 | IPHONEOS_DEPLOYMENT_TARGET = 14.1; 440 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 441 | MTL_FAST_MATH = YES; 442 | ONLY_ACTIVE_ARCH = YES; 443 | SDKROOT = iphoneos; 444 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 445 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 446 | }; 447 | name = Debug; 448 | }; 449 | CBBC807C25489E9E00D19BFF /* Release */ = { 450 | isa = XCBuildConfiguration; 451 | buildSettings = { 452 | ALWAYS_SEARCH_USER_PATHS = NO; 453 | CLANG_ANALYZER_NONNULL = YES; 454 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 455 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 456 | CLANG_CXX_LIBRARY = "libc++"; 457 | CLANG_ENABLE_MODULES = YES; 458 | CLANG_ENABLE_OBJC_ARC = YES; 459 | CLANG_ENABLE_OBJC_WEAK = YES; 460 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 461 | CLANG_WARN_BOOL_CONVERSION = YES; 462 | CLANG_WARN_COMMA = YES; 463 | CLANG_WARN_CONSTANT_CONVERSION = YES; 464 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 465 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 466 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 467 | CLANG_WARN_EMPTY_BODY = YES; 468 | CLANG_WARN_ENUM_CONVERSION = YES; 469 | CLANG_WARN_INFINITE_RECURSION = YES; 470 | CLANG_WARN_INT_CONVERSION = YES; 471 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 472 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 473 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 474 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 475 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 476 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 477 | CLANG_WARN_STRICT_PROTOTYPES = YES; 478 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 479 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 480 | CLANG_WARN_UNREACHABLE_CODE = YES; 481 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 482 | COPY_PHASE_STRIP = NO; 483 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 484 | ENABLE_NS_ASSERTIONS = NO; 485 | ENABLE_STRICT_OBJC_MSGSEND = YES; 486 | GCC_C_LANGUAGE_STANDARD = gnu11; 487 | GCC_NO_COMMON_BLOCKS = YES; 488 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 489 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 490 | GCC_WARN_UNDECLARED_SELECTOR = YES; 491 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 492 | GCC_WARN_UNUSED_FUNCTION = YES; 493 | GCC_WARN_UNUSED_VARIABLE = YES; 494 | IPHONEOS_DEPLOYMENT_TARGET = 14.1; 495 | MTL_ENABLE_DEBUG_INFO = NO; 496 | MTL_FAST_MATH = YES; 497 | SDKROOT = iphoneos; 498 | SWIFT_COMPILATION_MODE = wholemodule; 499 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 500 | VALIDATE_PRODUCT = YES; 501 | }; 502 | name = Release; 503 | }; 504 | CBBC807E25489E9E00D19BFF /* Debug */ = { 505 | isa = XCBuildConfiguration; 506 | buildSettings = { 507 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 508 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 509 | CODE_SIGN_ENTITLEMENTS = "iperf-swiftui/iperf-swiftui.entitlements"; 510 | CODE_SIGN_STYLE = Automatic; 511 | DEVELOPMENT_ASSET_PATHS = "\"iperf-swiftui/Preview Content\""; 512 | DEVELOPMENT_TEAM = P3VKCDARX4; 513 | ENABLE_PREVIEWS = YES; 514 | INFOPLIST_EXPAND_BUILD_SETTINGS = YES; 515 | INFOPLIST_FILE = "iperf-swiftui//Info.plist"; 516 | "IPHONEOS_DEPLOYMENT_TARGET[sdk=macosx*]" = 14.2; 517 | LD_RUNPATH_SEARCH_PATHS = ( 518 | "$(inherited)", 519 | "@executable_path/Frameworks", 520 | ); 521 | PRODUCT_BUNDLE_IDENTIFIER = "one.beagile.iperf-swiftui"; 522 | PRODUCT_NAME = "$(TARGET_NAME)"; 523 | SUPPORTS_MACCATALYST = YES; 524 | SWIFT_VERSION = 5.0; 525 | TARGETED_DEVICE_FAMILY = "1,2"; 526 | }; 527 | name = Debug; 528 | }; 529 | CBBC807F25489E9E00D19BFF /* Release */ = { 530 | isa = XCBuildConfiguration; 531 | buildSettings = { 532 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 533 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 534 | CODE_SIGN_ENTITLEMENTS = "iperf-swiftui/iperf-swiftui.entitlements"; 535 | CODE_SIGN_STYLE = Automatic; 536 | DEVELOPMENT_ASSET_PATHS = "\"iperf-swiftui/Preview Content\""; 537 | DEVELOPMENT_TEAM = P3VKCDARX4; 538 | ENABLE_PREVIEWS = YES; 539 | INFOPLIST_EXPAND_BUILD_SETTINGS = YES; 540 | INFOPLIST_FILE = "iperf-swiftui//Info.plist"; 541 | "IPHONEOS_DEPLOYMENT_TARGET[sdk=macosx*]" = 14.2; 542 | LD_RUNPATH_SEARCH_PATHS = ( 543 | "$(inherited)", 544 | "@executable_path/Frameworks", 545 | ); 546 | PRODUCT_BUNDLE_IDENTIFIER = "one.beagile.iperf-swiftui"; 547 | PRODUCT_NAME = "$(TARGET_NAME)"; 548 | SUPPORTS_MACCATALYST = YES; 549 | SWIFT_VERSION = 5.0; 550 | TARGETED_DEVICE_FAMILY = "1,2"; 551 | }; 552 | name = Release; 553 | }; 554 | CBDB6A0F255753480010443A /* Debug */ = { 555 | isa = XCBuildConfiguration; 556 | buildSettings = { 557 | BUNDLE_LOADER = "$(TEST_HOST)"; 558 | CODE_SIGN_STYLE = Automatic; 559 | DEVELOPMENT_TEAM = P3VKCDARX4; 560 | INFOPLIST_FILE = "iperf-swiftuiTests/Info.plist"; 561 | LD_RUNPATH_SEARCH_PATHS = ( 562 | "$(inherited)", 563 | "@executable_path/Frameworks", 564 | "@loader_path/Frameworks", 565 | ); 566 | PRODUCT_BUNDLE_IDENTIFIER = "one.beagile.iperf3-swiftTests"; 567 | PRODUCT_NAME = "$(TARGET_NAME)"; 568 | SWIFT_VERSION = 5.0; 569 | TARGETED_DEVICE_FAMILY = "1,2"; 570 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/iperf-swiftui.app/iperf-swiftui"; 571 | }; 572 | name = Debug; 573 | }; 574 | CBDB6A10255753480010443A /* Release */ = { 575 | isa = XCBuildConfiguration; 576 | buildSettings = { 577 | BUNDLE_LOADER = "$(TEST_HOST)"; 578 | CODE_SIGN_STYLE = Automatic; 579 | DEVELOPMENT_TEAM = P3VKCDARX4; 580 | INFOPLIST_FILE = "iperf-swiftuiTests/Info.plist"; 581 | LD_RUNPATH_SEARCH_PATHS = ( 582 | "$(inherited)", 583 | "@executable_path/Frameworks", 584 | "@loader_path/Frameworks", 585 | ); 586 | PRODUCT_BUNDLE_IDENTIFIER = "one.beagile.iperf3-swiftTests"; 587 | PRODUCT_NAME = "$(TARGET_NAME)"; 588 | SWIFT_VERSION = 5.0; 589 | TARGETED_DEVICE_FAMILY = "1,2"; 590 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/iperf-swiftui.app/iperf-swiftui"; 591 | }; 592 | name = Release; 593 | }; 594 | /* End XCBuildConfiguration section */ 595 | 596 | /* Begin XCConfigurationList section */ 597 | CBBC806425489E9C00D19BFF /* Build configuration list for PBXProject "iperf-swiftui" */ = { 598 | isa = XCConfigurationList; 599 | buildConfigurations = ( 600 | CBBC807B25489E9E00D19BFF /* Debug */, 601 | CBBC807C25489E9E00D19BFF /* Release */, 602 | ); 603 | defaultConfigurationIsVisible = 0; 604 | defaultConfigurationName = Release; 605 | }; 606 | CBBC807D25489E9E00D19BFF /* Build configuration list for PBXNativeTarget "iperf-swiftui" */ = { 607 | isa = XCConfigurationList; 608 | buildConfigurations = ( 609 | CBBC807E25489E9E00D19BFF /* Debug */, 610 | CBBC807F25489E9E00D19BFF /* Release */, 611 | ); 612 | defaultConfigurationIsVisible = 0; 613 | defaultConfigurationName = Release; 614 | }; 615 | CBDB6A0E255753480010443A /* Build configuration list for PBXNativeTarget "iperf-swiftuiTests" */ = { 616 | isa = XCConfigurationList; 617 | buildConfigurations = ( 618 | CBDB6A0F255753480010443A /* Debug */, 619 | CBDB6A10255753480010443A /* Release */, 620 | ); 621 | defaultConfigurationIsVisible = 0; 622 | defaultConfigurationName = Release; 623 | }; 624 | /* End XCConfigurationList section */ 625 | 626 | /* Begin XCRemoteSwiftPackageReference section */ 627 | CBC26D3128E18F2C00483F44 /* XCRemoteSwiftPackageReference "iperf-swift" */ = { 628 | isa = XCRemoteSwiftPackageReference; 629 | repositoryURL = "https://github.com/igorskh/iperf-swift"; 630 | requirement = { 631 | kind = upToNextMajorVersion; 632 | minimumVersion = 1.3.0; 633 | }; 634 | }; 635 | /* End XCRemoteSwiftPackageReference section */ 636 | 637 | /* Begin XCSwiftPackageProductDependency section */ 638 | CBC26D3228E18F2C00483F44 /* IperfSwift */ = { 639 | isa = XCSwiftPackageProductDependency; 640 | package = CBC26D3128E18F2C00483F44 /* XCRemoteSwiftPackageReference "iperf-swift" */; 641 | productName = IperfSwift; 642 | }; 643 | /* End XCSwiftPackageProductDependency section */ 644 | }; 645 | rootObject = CBBC806125489E9C00D19BFF /* Project object */; 646 | } 647 | -------------------------------------------------------------------------------- /iperf-swiftui.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /iperf-swiftui.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /iperf-swiftui.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /iperf-swiftui.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "pins" : [ 3 | { 4 | "identity" : "iperf-swift", 5 | "kind" : "remoteSourceControl", 6 | "location" : "https://github.com/igorskh/iperf-swift", 7 | "state" : { 8 | "revision" : "cf0fa8e08b31ded39516ab4d69a6a3b60db6d983", 9 | "version" : "1.3.0" 10 | } 11 | } 12 | ], 13 | "version" : 2 14 | } 15 | -------------------------------------------------------------------------------- /iperf-swiftui.xcodeproj/xcshareddata/xcschemes/iperf3-swiftui.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 34 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 71 | 73 | 79 | 80 | 81 | 82 | 84 | 85 | 88 | 89 | 90 | -------------------------------------------------------------------------------- /iperf-swiftui/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // iperf3-swift 4 | // 5 | // Created by Igor Kim on 27.10.20. 6 | // 7 | 8 | import UIKit 9 | 10 | @UIApplicationMain 11 | class AppDelegate: UIResponder, UIApplicationDelegate { 12 | 13 | 14 | 15 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 16 | // Override point for customization after application launch. 17 | return true 18 | } 19 | 20 | // MARK: UISceneSession Lifecycle 21 | 22 | func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { 23 | // Called when a new scene session is being created. 24 | // Use this method to select a configuration to create the new scene with. 25 | return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) 26 | } 27 | 28 | func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { 29 | // Called when the user discards a scene session. 30 | // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. 31 | // Use this method to release any resources that were specific to the discarded scenes, as they will not return. 32 | } 33 | 34 | 35 | } 36 | 37 | -------------------------------------------------------------------------------- /iperf-swiftui/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /iperf-swiftui/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "Icon-App-20x20@2x.png", 5 | "idiom" : "iphone", 6 | "scale" : "2x", 7 | "size" : "20x20" 8 | }, 9 | { 10 | "filename" : "Icon-App-20x20@3x.png", 11 | "idiom" : "iphone", 12 | "scale" : "3x", 13 | "size" : "20x20" 14 | }, 15 | { 16 | "filename" : "Icon-App-29x29@1x.png", 17 | "idiom" : "iphone", 18 | "scale" : "1x", 19 | "size" : "29x29" 20 | }, 21 | { 22 | "filename" : "Icon-App-29x29@2x.png", 23 | "idiom" : "iphone", 24 | "scale" : "2x", 25 | "size" : "29x29" 26 | }, 27 | { 28 | "filename" : "Icon-App-29x29@3x.png", 29 | "idiom" : "iphone", 30 | "scale" : "3x", 31 | "size" : "29x29" 32 | }, 33 | { 34 | "filename" : "Icon-App-40x40@2x.png", 35 | "idiom" : "iphone", 36 | "scale" : "2x", 37 | "size" : "40x40" 38 | }, 39 | { 40 | "filename" : "Icon-App-40x40@3x.png", 41 | "idiom" : "iphone", 42 | "scale" : "3x", 43 | "size" : "40x40" 44 | }, 45 | { 46 | "filename" : "Icon-App-60x60@2x.png", 47 | "idiom" : "iphone", 48 | "scale" : "2x", 49 | "size" : "60x60" 50 | }, 51 | { 52 | "filename" : "Icon-App-60x60@3x.png", 53 | "idiom" : "iphone", 54 | "scale" : "3x", 55 | "size" : "60x60" 56 | }, 57 | { 58 | "idiom" : "ipad", 59 | "scale" : "1x", 60 | "size" : "20x20" 61 | }, 62 | { 63 | "filename" : "Icon-App-40x40@1x.png", 64 | "idiom" : "ipad", 65 | "scale" : "2x", 66 | "size" : "20x20" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "scale" : "1x", 71 | "size" : "29x29" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "scale" : "2x", 76 | "size" : "29x29" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "scale" : "1x", 81 | "size" : "40x40" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "scale" : "2x", 86 | "size" : "40x40" 87 | }, 88 | { 89 | "filename" : "Icon-App-76x76@1x.png", 90 | "idiom" : "ipad", 91 | "scale" : "1x", 92 | "size" : "76x76" 93 | }, 94 | { 95 | "filename" : "Icon-App-76x76@2x.png", 96 | "idiom" : "ipad", 97 | "scale" : "2x", 98 | "size" : "76x76" 99 | }, 100 | { 101 | "filename" : "Icon-App-167x167.png", 102 | "idiom" : "ipad", 103 | "scale" : "2x", 104 | "size" : "83.5x83.5" 105 | }, 106 | { 107 | "filename" : "iTunesArtwork@2x.png", 108 | "idiom" : "ios-marketing", 109 | "scale" : "1x", 110 | "size" : "1024x1024" 111 | } 112 | ], 113 | "info" : { 114 | "author" : "xcode", 115 | "version" : 1 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /iperf-swiftui/Assets.xcassets/AppIcon.appiconset/Icon-App-167x167.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/igorskh/iperf-swiftui/d7e1fe2f1ca7744d5ded33f4b2debcdf4ca98c83/iperf-swiftui/Assets.xcassets/AppIcon.appiconset/Icon-App-167x167.png -------------------------------------------------------------------------------- /iperf-swiftui/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/igorskh/iperf-swiftui/d7e1fe2f1ca7744d5ded33f4b2debcdf4ca98c83/iperf-swiftui/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /iperf-swiftui/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/igorskh/iperf-swiftui/d7e1fe2f1ca7744d5ded33f4b2debcdf4ca98c83/iperf-swiftui/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /iperf-swiftui/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/igorskh/iperf-swiftui/d7e1fe2f1ca7744d5ded33f4b2debcdf4ca98c83/iperf-swiftui/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /iperf-swiftui/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/igorskh/iperf-swiftui/d7e1fe2f1ca7744d5ded33f4b2debcdf4ca98c83/iperf-swiftui/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /iperf-swiftui/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/igorskh/iperf-swiftui/d7e1fe2f1ca7744d5ded33f4b2debcdf4ca98c83/iperf-swiftui/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /iperf-swiftui/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/igorskh/iperf-swiftui/d7e1fe2f1ca7744d5ded33f4b2debcdf4ca98c83/iperf-swiftui/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /iperf-swiftui/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/igorskh/iperf-swiftui/d7e1fe2f1ca7744d5ded33f4b2debcdf4ca98c83/iperf-swiftui/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /iperf-swiftui/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/igorskh/iperf-swiftui/d7e1fe2f1ca7744d5ded33f4b2debcdf4ca98c83/iperf-swiftui/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /iperf-swiftui/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/igorskh/iperf-swiftui/d7e1fe2f1ca7744d5ded33f4b2debcdf4ca98c83/iperf-swiftui/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /iperf-swiftui/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/igorskh/iperf-swiftui/d7e1fe2f1ca7744d5ded33f4b2debcdf4ca98c83/iperf-swiftui/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /iperf-swiftui/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/igorskh/iperf-swiftui/d7e1fe2f1ca7744d5ded33f4b2debcdf4ca98c83/iperf-swiftui/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /iperf-swiftui/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/igorskh/iperf-swiftui/d7e1fe2f1ca7744d5ded33f4b2debcdf4ca98c83/iperf-swiftui/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /iperf-swiftui/Assets.xcassets/AppIcon.appiconset/iTunesArtwork@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/igorskh/iperf-swiftui/d7e1fe2f1ca7744d5ded33f4b2debcdf4ca98c83/iperf-swiftui/Assets.xcassets/AppIcon.appiconset/iTunesArtwork@2x.png -------------------------------------------------------------------------------- /iperf-swiftui/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /iperf-swiftui/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 | -------------------------------------------------------------------------------- /iperf-swiftui/ContentView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContentView.swift 3 | // iperf3-swift 4 | // 5 | // Created by Igor Kim on 27.10.20. 6 | // 7 | 8 | import SwiftUI 9 | import IperfSwift 10 | 11 | let barButtonHeight: CGFloat = 16.0 12 | 13 | struct ContentView: View { 14 | @ObservedObject var testsController = IperfRunnerTestsController() 15 | @ObservedObject var iperfPresetsController = IperfPresetsController() 16 | 17 | @State var formInput = IperfConfigurationInput( 18 | address: "127.0.0.1", 19 | port: "5201" 20 | ) 21 | 22 | var body: some View { 23 | VStack { 24 | Text("iPerf3 \(formInput.role.description)") 25 | .font(.largeTitle) 26 | 27 | InlineSettingsView( 28 | formInput: $formInput, 29 | serverEntries: $iperfPresetsController.serverEntries, 30 | selectedPresetIndex: $iperfPresetsController.selectedPresetIndex) { 31 | 32 | Button(action: { testsController.addTest(with: formInput) }) { 33 | Image(systemName: "plus") 34 | } 35 | .font(.title) 36 | } 37 | .padding(.vertical, 10) 38 | 39 | ScrollView { 40 | ForEach(testsController.tests.reversed()) { t in 41 | if !t.isDeleted { 42 | IperfTestView(iperfRunnerController: t) 43 | .frame(maxWidth: .infinity, maxHeight: .infinity) 44 | .animation(.easeIn) 45 | .transition(.slide) 46 | } 47 | } 48 | } 49 | } 50 | .padding(10) 51 | } 52 | } 53 | 54 | struct ContentView_Previews: PreviewProvider { 55 | static var previews: some View { 56 | ContentView() 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /iperf-swiftui/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleDisplayName 8 | iPerf App 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UIApplicationSceneManifest 26 | 27 | UIApplicationSupportsMultipleScenes 28 | 29 | UISceneConfigurations 30 | 31 | UIWindowSceneSessionRoleApplication 32 | 33 | 34 | UISceneConfigurationName 35 | Default Configuration 36 | UISceneDelegateClassName 37 | $(PRODUCT_MODULE_NAME).SceneDelegate 38 | 39 | 40 | 41 | 42 | UIApplicationSupportsIndirectInputEvents 43 | 44 | UILaunchStoryboardName 45 | LaunchScreen 46 | UIMainStoryboardFile 47 | LaunchScreen 48 | UIRequiredDeviceCapabilities 49 | 50 | armv7 51 | 52 | UISupportedInterfaceOrientations 53 | 54 | UIInterfaceOrientationPortrait 55 | 56 | UISupportedInterfaceOrientations~ipad 57 | 58 | UIInterfaceOrientationPortrait 59 | UIInterfaceOrientationPortraitUpsideDown 60 | UIInterfaceOrientationLandscapeLeft 61 | UIInterfaceOrientationLandscapeRight 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /iperf-swiftui/IperfPresetsController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // IperfPresetsController.swift 3 | // iperf-swiftui 4 | // 5 | // Created by Igor Kim on 09.11.20. 6 | // 7 | 8 | import Foundation 9 | 10 | enum SaFamily { 11 | case ipv4 12 | case ipv6 13 | } 14 | 15 | struct NetIfaceAddrResult { 16 | let name: String 17 | let addr: String 18 | let saFamily: SaFamily 19 | } 20 | 21 | class IperfPresetsController: ObservableObject { 22 | @Published var serverEntries = [ServerEntry]() 23 | @Published var selectedPresetIndex: Int = -1 24 | 25 | func loadServersFromJson() { 26 | let decoder = JSONDecoder() 27 | if let path = Bundle.main.url(forResource: "servers", withExtension: "json"), 28 | let data = try? Data(contentsOf: path), 29 | let obj = try? decoder.decode([ServerEntry].self, from: data) { 30 | serverEntries = obj 31 | for i in 0.. [String:[NetIfaceAddrResult]] { 39 | var ifaddr: UnsafeMutablePointer? = nil 40 | var result: [String : [NetIfaceAddrResult]] = [:] 41 | 42 | if getifaddrs(&ifaddr) == 0 { 43 | var ptr = ifaddr 44 | while ptr != nil { 45 | defer { ptr = ptr?.pointee.ifa_next } 46 | 47 | guard let interface = ptr?.pointee else { return result } 48 | 49 | let addrFamily = interface.ifa_addr.pointee.sa_family 50 | if addrFamily == UInt8(AF_INET) || addrFamily == UInt8(AF_INET6) { 51 | let name = String(cString: (interface.ifa_name)) 52 | var hostname = [CChar](repeating: 0, count: Int(NI_MAXHOST)) 53 | getnameinfo(interface.ifa_addr, socklen_t((interface.ifa_addr.pointee.sa_len)), &hostname, socklen_t(hostname.count), nil, socklen_t(0), NI_NUMERICHOST) 54 | 55 | if result[name] == nil { 56 | result[name] = [] 57 | } 58 | result[name]!.append( 59 | NetIfaceAddrResult( 60 | name: name, 61 | addr: String(cString: hostname), 62 | saFamily: addrFamily == UInt8(AF_INET) ? .ipv4 : .ipv6 63 | ) 64 | ) 65 | } 66 | } 67 | freeifaddrs(ifaddr) 68 | } 69 | return result 70 | } 71 | 72 | init() { 73 | loadServersFromJson() 74 | 75 | let addrs = getNameToAddressDict() 76 | for k in addrs { 77 | k.value.forEach { iface in 78 | if iface.saFamily == .ipv4 { 79 | self.serverEntries.append( 80 | ServerEntry(server: iface.addr, type: ServerEntryType.listenOn) 81 | ) 82 | } 83 | } 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /iperf-swiftui/IperfRunnerController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // IperfRunnerController.swift 3 | // iperf-swiftui 4 | // 5 | // Created by Igor Kim on 09.11.20. 6 | // 7 | 8 | import Foundation 9 | import IperfSwift 10 | 11 | class IperfRunnerController: ObservableObject, Identifiable { 12 | private var iperfRunner: IperfRunner? 13 | 14 | @Published var isDeleted = false 15 | @Published var formInput = IperfConfigurationInput(address: "") 16 | @Published var runnerState: IperfRunnerState = .ready 17 | @Published var debugDescription: String = "" 18 | @Published var displayError: Bool = false 19 | @Published var results = [IperfIntervalResult]() { 20 | didSet { 21 | objectWillChange.send() 22 | } 23 | } 24 | 25 | func onResultReceived(result: IperfIntervalResult) { 26 | if result.streams.count > 0 { 27 | results.append(result) 28 | } 29 | } 30 | 31 | func onErrorReceived(error: IperfError) { 32 | DispatchQueue.main.async { 33 | self.displayError = error != .IENONE 34 | self.debugDescription = error.debugDescription 35 | } 36 | } 37 | 38 | func onNewState(state: IperfRunnerState) { 39 | if state != .unknown && state != runnerState { 40 | DispatchQueue.main.async { 41 | self.runnerState = state 42 | } 43 | } 44 | } 45 | 46 | func start(with formInput: IperfConfigurationInput) { 47 | self.formInput = formInput 48 | 49 | results = [] 50 | debugDescription = "" 51 | 52 | iperfRunner = IperfRunner(with: IperfConfiguration(formInput)) 53 | iperfRunner!.start( 54 | onResultReceived, 55 | onErrorReceived, 56 | onNewState 57 | ) 58 | } 59 | 60 | func stop() { 61 | iperfRunner!.stop() 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /iperf-swiftui/IperfRunnerTestsController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // IperfRunnerTestsController.swift 3 | // iperf-swiftui 4 | // 5 | // Created by Igor Kim on 10.11.20. 6 | // 7 | 8 | import Foundation 9 | 10 | import Combine 11 | 12 | class IperfRunnerTestsController: ObservableObject { 13 | @Published var tests = [IperfRunnerController]() 14 | private var cancellables = Set() 15 | 16 | func addTest(with formInput: IperfConfigurationInput) { 17 | let filteredTests = tests.filter { t in !t.displayError && (!t.isDeleted && t.runnerState != .stopping) } 18 | tests = filteredTests 19 | 20 | let newTest = IperfRunnerController() 21 | newTest.objectWillChange.sink(receiveValue: {[weak self] _ in 22 | self?.objectWillChange.send() 23 | }) 24 | .store(in: &cancellables) 25 | 26 | tests.append(newTest) 27 | newTest.start(with: formInput) 28 | } 29 | // func removeTest(test: IperfRunnerTest) { 30 | // 31 | // } 32 | } 33 | -------------------------------------------------------------------------------- /iperf-swiftui/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /iperf-swiftui/SceneDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SceneDelegate.swift 3 | // iperf3-swift 4 | // 5 | // Created by Igor Kim on 27.10.20. 6 | // 7 | 8 | import UIKit 9 | import SwiftUI 10 | 11 | class SceneDelegate: UIResponder, UIWindowSceneDelegate { 12 | 13 | var window: UIWindow? 14 | 15 | 16 | func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { 17 | // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`. 18 | // If using a storyboard, the `window` property will automatically be initialized and attached to the scene. 19 | // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead). 20 | 21 | // Create the SwiftUI view that provides the window contents. 22 | let contentView = ContentView() 23 | 24 | // Use a UIHostingController as window root view controller. 25 | if let windowScene = scene as? UIWindowScene { 26 | let window = UIWindow(windowScene: windowScene) 27 | window.rootViewController = UIHostingController(rootView: contentView) 28 | self.window = window 29 | window.makeKeyAndVisible() 30 | } 31 | } 32 | 33 | func sceneDidDisconnect(_ scene: UIScene) { 34 | // Called as the scene is being released by the system. 35 | // This occurs shortly after the scene enters the background, or when its session is discarded. 36 | // Release any resources associated with this scene that can be re-created the next time the scene connects. 37 | // The scene may re-connect later, as its session was not necessarily discarded (see `application:didDiscardSceneSessions` instead). 38 | } 39 | 40 | func sceneDidBecomeActive(_ scene: UIScene) { 41 | // Called when the scene has moved from an inactive state to an active state. 42 | // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. 43 | } 44 | 45 | func sceneWillResignActive(_ scene: UIScene) { 46 | // Called when the scene will move from an active state to an inactive state. 47 | // This may occur due to temporary interruptions (ex. an incoming phone call). 48 | } 49 | 50 | func sceneWillEnterForeground(_ scene: UIScene) { 51 | // Called as the scene transitions from the background to the foreground. 52 | // Use this method to undo the changes made on entering the background. 53 | } 54 | 55 | func sceneDidEnterBackground(_ scene: UIScene) { 56 | // Called as the scene transitions from the foreground to the background. 57 | // Use this method to save data, release shared resources, and store enough scene-specific state information 58 | // to restore the scene back to its current state. 59 | } 60 | 61 | 62 | } 63 | 64 | -------------------------------------------------------------------------------- /iperf-swiftui/SingleTestView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SingleTestView.swift 3 | // iperf-swiftui 4 | // 5 | // Created by Igor Kim on 10.11.20. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct SingleTestView: View { 11 | @ObservedObject var runnerController = IperfRunnerController() 12 | @ObservedObject var presetsController = IperfPresetsController() 13 | 14 | @State var formInput = IperfConfigurationInput( 15 | address: "127.0.0.1", 16 | port: "5201" 17 | ) 18 | 19 | var body: some View { 20 | VStack { 21 | Text("iPerf3 \(formInput.role.description)") 22 | .font(.largeTitle) 23 | 24 | InlineSettingsView( 25 | formInput: $formInput, 26 | serverEntries: $presetsController.serverEntries, 27 | selectedPresetIndex: $presetsController.selectedPresetIndex) { 28 | 29 | 30 | StartButton( 31 | state: $runnerController.runnerState, 32 | onStartClick: { 33 | runnerController.start(with: formInput) 34 | }, 35 | onStopClick: { 36 | runnerController.stop() 37 | }) 38 | } 39 | .padding(.vertical, 10) 40 | 41 | if runnerController.displayError && runnerController.debugDescription.count > 0 { 42 | Text(runnerController.debugDescription).padding(.vertical, 10) 43 | } 44 | ResultsView(results: $runnerController.results) 45 | 46 | Spacer() 47 | }.padding() 48 | } 49 | } 50 | 51 | struct SingleTestView_Previews: PreviewProvider { 52 | static var previews: some View { 53 | SingleTestView() 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /iperf-swiftui/components/AddressPortStack.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AddressPortStack.swift 3 | // iperf-swiftui 4 | // 5 | // Created by Igor Kim on 09.11.20. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct AddressPortStack: View { 11 | @Binding var formInput: IperfConfigurationInput 12 | @Binding var serverEntries: [ServerEntry] 13 | @Binding var selectedPresetIndex: Int 14 | 15 | var body: some View { 16 | HStack { 17 | TextField("Address", text: $formInput.address) 18 | .textFieldStyle(RoundedBorderTextFieldStyle()) 19 | 20 | if serverEntries.count > 0 { 21 | SelectServerView(options: $serverEntries, formInput: $formInput, selectedPresetIndex: $selectedPresetIndex) 22 | } 23 | 24 | TextField("Port", text: $formInput.port) 25 | .textFieldStyle(RoundedBorderTextFieldStyle()) 26 | 27 | if selectedPresetIndex > -1 && selectedPresetIndex < serverEntries.count { 28 | SelectPortView(server: $serverEntries[selectedPresetIndex], formInput: $formInput) 29 | } 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /iperf-swiftui/components/OptionsPicker.swift: -------------------------------------------------------------------------------- 1 | // 2 | // OptionsPicker.swift 3 | // iperf3-swift 4 | // 5 | // Created by Igor Kim on 28.10.20. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct OptionsPicker: View { 11 | let options: [HasDescription] 12 | @Binding var selected: Int 13 | var onChange: (_ newIndex: Int) -> Void = {_ in } 14 | 15 | var body: some View { 16 | HStack(spacing: 0) { 17 | ForEach(0.. { 15 | var value: String 16 | var option: T 17 | var optionIndex: Int 18 | } 19 | 20 | enum RateOption: TextFieldOption { 21 | case Kbps 22 | case Mbps 23 | case Gbps 24 | 25 | var rawValue: String { 26 | switch self { 27 | case .Kbps: 28 | return "Kbps" 29 | case .Mbps: 30 | return "Mbps" 31 | case .Gbps: 32 | return "Gbps" 33 | } 34 | } 35 | var uiImage: String? { 36 | return nil 37 | } 38 | var description: String { 39 | return rawValue 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /iperf-swiftui/components/SelectServerRow.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SelectServerRow.swift 3 | // iperf-swiftui 4 | // 5 | // Created by Igor Kim on 09.11.20. 6 | // 7 | 8 | import Foundation 9 | import SwiftUI 10 | 11 | struct SelectServerRow: View { 12 | var server: ServerEntry 13 | var onClick: () -> Void = {} 14 | 15 | var body: some View { 16 | HStack { 17 | Text(server.countryEmoji ?? "") 18 | Text(server.server) 19 | Spacer() 20 | if server.ports != nil { 21 | if server.ports!.tcp != nil { 22 | Text("TCP") 23 | .font(.footnote) 24 | } 25 | if server.ports!.udp != nil { 26 | Text("UDP") 27 | .font(.footnote) 28 | } 29 | } 30 | }.onTapGesture { 31 | onClick() 32 | } 33 | } 34 | } 35 | 36 | 37 | struct SelectServerRow_Previews: PreviewProvider { 38 | static var previews: some View { 39 | SelectServerRow(server: ServerEntry(server: "127.0.0.1", ports: ServerPortsEntry(tcp: nil, udp: nil), country: "de", location: nil, type: .localServer, portsParsed: nil)) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /iperf-swiftui/components/ServerEntry.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ServerEntry.swift 3 | // iperf-swiftui 4 | // 5 | // Created by Igor Kim on 09.11.20. 6 | // 7 | 8 | import Foundation 9 | 10 | enum ServerEntryType: String, Codable, HasDescription { 11 | var description: String { 12 | switch self { 13 | case .localServer: 14 | return "Local" 15 | case .publicServer: 16 | return "Public" 17 | case .listenOn: 18 | return "Listen" 19 | } 20 | } 21 | 22 | var uiImage: String? { 23 | return nil 24 | } 25 | 26 | case localServer = "local" 27 | case publicServer = "public" 28 | case listenOn = "listen" 29 | } 30 | 31 | struct ServerPortsEntry: Codable { 32 | let tcp: [String]? 33 | let udp: [String]? 34 | } 35 | 36 | struct ServerPortsEntryParsed: Codable { 37 | var tcp: [Int] 38 | var udp: [Int] 39 | 40 | init() { 41 | tcp = [Int]() 42 | udp = [Int]() 43 | } 44 | } 45 | 46 | struct ServerEntry: Codable { 47 | let server: String 48 | 49 | var ports: ServerPortsEntry? = nil 50 | var country: String? = nil 51 | var location: String? = nil 52 | 53 | var type: ServerEntryType? = nil 54 | var portsParsed: ServerPortsEntryParsed? = nil 55 | 56 | private func parsePorts(of sPorts: [String]) -> [Int] { 57 | var result = [Int]() 58 | sPorts.forEach { p in 59 | p.split(separator: ",").forEach { portRange in 60 | if portRange.contains("-") { 61 | let range = portRange.split(separator: "-") 62 | if range.count == 2, 63 | let rangeStart = Int(range[0]), 64 | let rangeEnd = Int(range[1]) { 65 | for portNumeric in rangeStart...rangeEnd { 66 | result.append(portNumeric) 67 | } 68 | } 69 | } else { 70 | if let portNumeric = Int(portRange) { 71 | result.append(portNumeric) 72 | } 73 | } 74 | } 75 | } 76 | return result 77 | } 78 | 79 | mutating func parsePorts() { 80 | portsParsed = ServerPortsEntryParsed() 81 | if let tcpPorts = ports!.tcp { 82 | portsParsed!.tcp = parsePorts(of: tcpPorts) 83 | } 84 | if let udpPorts = ports!.udp { 85 | portsParsed!.udp = parsePorts(of: udpPorts) 86 | } 87 | } 88 | } 89 | 90 | extension ServerEntry { 91 | var countryEmoji: String? { 92 | switch country { 93 | case "fr": 94 | return "🇫🇷" 95 | case "nl": 96 | return "🇳🇱" 97 | case "ee": 98 | return "🇪🇪" 99 | case "ua": 100 | return "🇺🇦" 101 | case "kz": 102 | return "🇰🇿" 103 | case "ru": 104 | return "🇷🇺" 105 | case "id": 106 | return "🇮🇩" 107 | case "us": 108 | return "🇺🇸" 109 | case "de": 110 | return "🇩🇪" 111 | case "ch": 112 | return "🇨🇭" 113 | case "br": 114 | return "🇧🇷" 115 | default: 116 | return nil 117 | } 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /iperf-swiftui/components/ServersList.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ServersList.swift 3 | // iperf-swiftui 4 | // 5 | // Created by Igor Kim on 09.11.20. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct ServersList: View { 11 | @Binding var formInput: IperfConfigurationInput 12 | @Binding var options: [ServerEntry] 13 | @Binding var selectedTypeIndex: Int 14 | @Binding var selectedPresetIndex: Int 15 | @Binding var isPresented: Bool 16 | 17 | var body: some View { 18 | let filteredOptionsIds = options.enumerated().filter { 19 | $0.element.type == serverTypeOptions[selectedTypeIndex] 20 | }.map { $0.offset } 21 | 22 | return List { 23 | ForEach(filteredOptionsIds, id: \.self) { i in 24 | SelectServerRow(server: options[i]) { 25 | if let ports = formInput.prot == .udp ? options[i].portsParsed?.udp : options[i].portsParsed?.tcp, 26 | let firstPort = ports.first { 27 | formInput.port = String(firstPort) 28 | } else { 29 | formInput.port = defaultPort 30 | } 31 | 32 | selectedPresetIndex = i 33 | formInput.address = options[i].server 34 | isPresented = false 35 | } 36 | } 37 | } 38 | } 39 | } 40 | 41 | //struct ServersList_Previews: PreviewProvider { 42 | // static var previews: some View { 43 | // ServersList() 44 | // } 45 | //} 46 | -------------------------------------------------------------------------------- /iperf-swiftui/components/StartButton.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StartButton.swift 3 | // iperf3-swift 4 | // 5 | // Created by Igor Kim on 28.10.20. 6 | // 7 | 8 | import SwiftUI 9 | 10 | import IperfSwift 11 | 12 | struct StartButton: View { 13 | @Binding var state: IperfRunnerState 14 | 15 | var onStartClick: () -> Void = {} 16 | var onStopClick: () -> Void = {} 17 | 18 | var body: some View { 19 | ZStack { 20 | if state == .ready || state == .finished || state == .error { 21 | Button(action: { onStartClick() }) { 22 | Image(systemName: "play") 23 | } 24 | } else if state == .initialising || state == .stopping { 25 | Image(systemName: "clock") 26 | .foregroundColor(.secondary) 27 | } else { 28 | Button(action: { onStopClick() }) { 29 | Image(systemName: "stop") 30 | } 31 | } 32 | }.font(.title) 33 | } 34 | } 35 | 36 | struct StartButton_Previews: PreviewProvider { 37 | static var previews: some View { 38 | StartButton(state: .constant(.initialising)) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /iperf-swiftui/components/TextFieldWithLabel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TextFieldWithLabel.swift 3 | // iperf-swiftui 4 | // 5 | // Created by Igor Kim on 26.10.20. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct TextFieldWithLabel: View { 11 | let label: String 12 | @Binding var text: String 13 | var keyboardType: UIKeyboardType = .default 14 | 15 | var body: some View { 16 | HStack { 17 | Text(label) 18 | Spacer() 19 | TextField(label, text: $text) 20 | .multilineTextAlignment(.trailing) 21 | .keyboardType(keyboardType) 22 | } 23 | } 24 | } 25 | 26 | struct TextFieldWithLabel_Previews: PreviewProvider { 27 | static var previews: some View { 28 | TextFieldWithLabel(label: "Label", text: .constant("value")) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /iperf-swiftui/components/TextFieldWithOption.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TextFieldWithOption.swift 3 | // iperf3-swift 4 | // 5 | // Created by Igor Kim on 07.11.20. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct TextFieldWithOption: View where T: TextFieldOption { 11 | let label: String 12 | @Binding var value: StringWithOption 13 | var keyboardType: UIKeyboardType = .default 14 | var options: [T] 15 | 16 | var body: some View { 17 | VStack { 18 | TextFieldWithLabel( 19 | label: label, 20 | text: $value.value, 21 | keyboardType: keyboardType 22 | ) 23 | HStack { 24 | Spacer() 25 | 26 | OptionsPicker( 27 | options: options, 28 | selected: $value.optionIndex, 29 | onChange: { index in 30 | value.option = options[value.optionIndex] 31 | } 32 | ) 33 | } 34 | } 35 | } 36 | } 37 | 38 | struct TextFieldWithOption_Previews: PreviewProvider { 39 | static var previews: some View { 40 | let opt = StringWithOption(value: "1", option: rateOptions[0], optionIndex: 0) 41 | return TextFieldWithOption(label: "test", value: .constant(opt), options: rateOptions) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /iperf-swiftui/extensions/IperfIntervalResultArray.swift: -------------------------------------------------------------------------------- 1 | // 2 | // IperfIntervalResultArray.swift 3 | // iperf-swiftui 4 | // 5 | // Created by Igor Kim on 10.11.20. 6 | // 7 | 8 | import Foundation 9 | 10 | import IperfSwift 11 | 12 | extension Array where Element == IperfIntervalResult { 13 | func relativeTimeDifference() -> [(Double, Double)] { 14 | self.map { e in (e.startTime - self.first!.startTime, e.endTime - self.first!.startTime) } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /iperf-swiftui/extensions/IperfRunnerState+HasDescription.swift: -------------------------------------------------------------------------------- 1 | // 2 | // IperfRunnerState+HasDescription.swift 3 | // iperf-swiftui 4 | // 5 | // Created by Igor Kim on 10.11.20. 6 | // 7 | 8 | import Foundation 9 | 10 | import IperfSwift 11 | 12 | extension IperfRunnerState: HasDescription { 13 | var description: String { 14 | switch self { 15 | case .unknown: 16 | return "N/A" 17 | case .ready: 18 | return "Ready" 19 | case .initialising: 20 | return "Initialising..." 21 | case .running: 22 | return "Running..." 23 | case .error: 24 | return "Failed" 25 | case .stopping: 26 | return "Stopping..." 27 | case .finished: 28 | return "Finished" 29 | } 30 | } 31 | 32 | var uiImage: String? { 33 | nil 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /iperf-swiftui/extensions/IperfSwift+HasDescription.swift: -------------------------------------------------------------------------------- 1 | // 2 | // types.swift 3 | // iperf3-swift 4 | // 5 | // Created by Igor Kim on 28.10.20. 6 | // 7 | 8 | import Foundation 9 | 10 | import IperfSwift 11 | 12 | protocol HasDescription { 13 | var description: String { get } 14 | var uiImage: String? { get } 15 | } 16 | 17 | extension IperfProtocol: HasDescription { 18 | var uiImage: String? { 19 | return nil 20 | } 21 | var description: String { 22 | switch self { 23 | case .tcp: 24 | return "TCP" 25 | case .udp: 26 | return "UDP" 27 | case .sctp: 28 | return "SCTP" 29 | } 30 | } 31 | } 32 | 33 | extension IperfRole: HasDescription { 34 | var uiImage: String? { 35 | return nil 36 | } 37 | var description: String { 38 | switch self { 39 | case .client: 40 | return "Client" 41 | case .server: 42 | return "Server" 43 | } 44 | } 45 | } 46 | 47 | extension IperfDirection: HasDescription { 48 | var uiImage: String? { 49 | switch self { 50 | case .download: 51 | return "arrow.down" 52 | case .upload: 53 | return "arrow.up" 54 | } 55 | } 56 | var description: String { 57 | switch self { 58 | case .download: 59 | return "Download" 60 | case .upload: 61 | return "Upload" 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /iperf-swiftui/extensions/IperfSwift+IperfConfigurationInput.swift: -------------------------------------------------------------------------------- 1 | // 2 | // typesForUI.swift 3 | // iperf3-swift 4 | // 5 | // Created by Igor Kim on 28.10.20. 6 | // 7 | import Foundation 8 | 9 | import IperfSwift 10 | 11 | struct IperfConfigurationInput { 12 | var address: String 13 | var port: String = "5201" 14 | var duration: String = "5" 15 | var tos: String = "0" 16 | var nofStreams: String = "1" 17 | var rate: StringWithOption = StringWithOption(value: "1", option: .Mbps, optionIndex: 1) 18 | var reportInterval: String = "1" 19 | var timeout: String = "3" 20 | 21 | var prot: IperfProtocol { 22 | protocolOptions[protocolIndex] 23 | } 24 | var role: IperfRole { 25 | roleOptions[roleIndex] 26 | } 27 | var direction: IperfDirection { 28 | directionOptions[directionIndex] 29 | } 30 | 31 | var roleOptions: [IperfRole] = [.client, .server] 32 | var directionOptions: [IperfDirection] = [.download, .upload] 33 | var protocolOptions: [IperfProtocol] = [.tcp, .udp] 34 | 35 | var protocolIndex: Int = 0 36 | var roleIndex: Int = 0 37 | var directionIndex: Int = 0 38 | } 39 | 40 | extension IperfConfiguration { 41 | init(_ input: IperfConfigurationInput) { 42 | self.init() 43 | 44 | address = input.address 45 | role = input.role 46 | reverse = input.direction 47 | prot = input.prot 48 | 49 | if let v = UInt64(input.rate.value) { 50 | rate = v 51 | if input.rate.option == .Kbps { 52 | rate *= 1024 53 | } else if input.rate.option == .Mbps { 54 | rate *= 1024*1024 55 | } else if input.rate.option == .Gbps { 56 | rate *= 1024*1024*1024 57 | } 58 | } 59 | if let v = Int(input.port) { 60 | port = v 61 | } 62 | if let v = Double(input.duration) { 63 | duration = v 64 | } 65 | if let v = Int(input.tos) { 66 | tos = v 67 | } 68 | if let v = Int(input.nofStreams) { 69 | numStreams = v 70 | } 71 | if let v = Double(input.reportInterval) { 72 | reporterInterval = v 73 | } 74 | if let v = Double(input.timeout) { 75 | timeout = v 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /iperf-swiftui/extensions/IperfThroughput+pretty.swift: -------------------------------------------------------------------------------- 1 | // 2 | // IperfThroughput+pretty.swift 3 | // iperf-swiftui 4 | // 5 | // Created by Igor Kim on 10.11.20. 6 | // 7 | 8 | import Foundation 9 | 10 | import IperfSwift 11 | 12 | extension IperfThroughput { 13 | func pretty(format: String = "%.1f") -> String { 14 | if bps <= 1024 { 15 | return "\(String(format: format, bps)) bps" 16 | } 17 | if Kbps <= 1024 { 18 | return "\(String(format: format, Kbps)) Kbps" 19 | } 20 | if Mbps <= 1024 { 21 | return "\(String(format: format, Mbps)) Mbps" 22 | } 23 | return "\(String(format: format, Gbps)) Gbps" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /iperf-swiftui/iperf-swiftui.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.network.client 8 | 9 | com.apple.security.network.server 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /iperf-swiftui/servers.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "server": "bouygues.iperf.fr", 4 | "ports": { 5 | "tcp": ["9200-9222"] 6 | }, 7 | "country": "fr", 8 | "location": "Île-de-France" 9 | }, 10 | 11 | { 12 | "server": "ping.online.net", 13 | "ports": { 14 | "tcp": ["5200-5209"], 15 | "udp": ["5200-5209"] 16 | }, 17 | "country": "fr", 18 | "location": "Île-de-France" 19 | }, 20 | { 21 | "server": "ping6.online.net", 22 | "ports": { 23 | "tcp": ["5200-5209"], 24 | "udp": ["5200-5209"] 25 | }, 26 | "country": "fr", 27 | "location": "Île-de-France" 28 | }, 29 | { 30 | "server": "ping-90ms.online.net", 31 | "ports": { 32 | "tcp": ["5200-5209"] 33 | }, 34 | "country": "fr", 35 | "location": "Île-de-France" 36 | }, 37 | { 38 | "server": "ping6-90ms.online.net", 39 | "ports": { 40 | "tcp": ["5200-5209"] 41 | }, 42 | "country": "fr", 43 | "location": "Île-de-France" 44 | }, 45 | 46 | { 47 | "server": "speedtest.serverius.net", 48 | "ports": { 49 | "tcp": ["5002"], 50 | "udp": ["5002"] 51 | }, 52 | "country": "nl", 53 | "location": "Amsterdam" 54 | }, 55 | 56 | { 57 | "server": "ping-ams1.online.net", 58 | "ports": { 59 | "tcp": ["5200-5209"], 60 | "udp": ["5200-5209"] 61 | }, 62 | "country": "nl", 63 | "location": "Amsterdam" 64 | }, 65 | 66 | { 67 | "server": "iperf.worldstream.nl", 68 | "ports": { 69 | "tcp": ["5201"] 70 | }, 71 | "country": "nl", 72 | "location": "Amsterdam" 73 | }, 74 | 75 | { 76 | "server": "iperf.eenet.ee", 77 | "ports": { 78 | "tcp": ["5201"], 79 | "udp": ["5201"] 80 | }, 81 | "country": "ee" 82 | }, 83 | 84 | { 85 | "server": "iperf.astra.in.ua", 86 | "ports": { 87 | "tcp": ["5201"] 88 | }, 89 | "country": "ua", 90 | "location": "Lviv" 91 | }, 92 | 93 | { 94 | "server": "iperf.volia.net", 95 | "ports": { 96 | "tcp": ["5201"] 97 | }, 98 | "country": "ua", 99 | "location": "Kyiv" 100 | }, 101 | 102 | { 103 | "server": "iperf.it-north.net", 104 | "ports": { 105 | "tcp": ["5200-5209"], 106 | "udp": ["5200-5209"] 107 | }, 108 | "country": "kz" 109 | }, 110 | 111 | { 112 | "server": "iperf.biznetnetworks.com", 113 | "ports": { 114 | "tcp": ["5201-5203"] 115 | }, 116 | "country": "id", 117 | "location": "Jakarta" 118 | }, 119 | 120 | { 121 | "server": "iperf.scottlinux.com", 122 | "ports": { 123 | "tcp": ["5201"], 124 | "udp": ["5201"] 125 | }, 126 | "country": "us", 127 | "location": "California" 128 | }, 129 | 130 | { 131 | "server": "speedtest.iveloz.net.br", 132 | "ports": { 133 | "tcp": ["5201-5209"], 134 | "udp": ["5201-5209"] 135 | }, 136 | "country": "br", 137 | "location": "San Paulo" 138 | }, 139 | 140 | 141 | { 142 | "server": "iperf.he.net", 143 | "ports": { 144 | "tcp": ["5201"], 145 | "udp": ["5201"] 146 | }, 147 | "country": "us", 148 | "location": "California" 149 | }, 150 | 151 | { 152 | "server": "speedtest.wtnet.de", 153 | "ports": { 154 | "tcp": ["5200-5209"] 155 | }, 156 | "country": "de", 157 | "location": "Hamburg" 158 | }, 159 | 160 | { 161 | "server": "speed.myloc.de", 162 | "ports": { 163 | "tcp": ["5209"] 164 | }, 165 | "country": "de", 166 | "location": "Düsseldorf" 167 | }, 168 | 169 | { 170 | "server": "ping.netnik.de", 171 | "ports": { 172 | "tcp": ["5201"] 173 | }, 174 | "country": "de", 175 | "location": "Düsseldorf" 176 | }, 177 | 178 | { 179 | "server": "iperf.wifx.net", 180 | "ports": { 181 | "tcp": ["5200-5209"], 182 | "udp": ["5200-5209"] 183 | }, 184 | "country": "ch", 185 | "location": "Zurich" 186 | }, 187 | 188 | { 189 | "server": "speedtest.hostkey.ru", 190 | "ports": { 191 | "tcp": ["5201-5203"] 192 | }, 193 | "country": "ru", 194 | "location": "Moscow" 195 | } 196 | ] 197 | -------------------------------------------------------------------------------- /iperf-swiftui/views/InlineSettingsView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // InlineSettingsView.swift 3 | // iperf-swiftui 4 | // 5 | // Created by Igor Kim on 10.11.20. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct InlineSettingsView: View { 11 | @Binding var formInput: IperfConfigurationInput 12 | @Binding var serverEntries: [ServerEntry] 13 | @Binding var selectedPresetIndex: Int 14 | 15 | let startButtonContent: Content 16 | 17 | init(formInput: Binding, 18 | serverEntries: Binding<[ServerEntry]>, 19 | selectedPresetIndex: Binding, 20 | @ViewBuilder startButton: @escaping () -> Content) { 21 | self._formInput = formInput 22 | self._serverEntries = serverEntries 23 | self._selectedPresetIndex = selectedPresetIndex 24 | self.startButtonContent = startButton() 25 | } 26 | 27 | var body: some View { 28 | VStack { 29 | AddressPortStack( 30 | formInput: $formInput, 31 | serverEntries: $serverEntries, 32 | selectedPresetIndex: $selectedPresetIndex 33 | ) 34 | .padding(.bottom, 5) 35 | 36 | HStack { 37 | OptionsPicker( 38 | options: formInput.roleOptions, 39 | selected: $formInput.roleIndex 40 | ) 41 | 42 | if formInput.role == .client { 43 | OptionsPicker( 44 | options: formInput.directionOptions, 45 | selected: $formInput.directionIndex 46 | ) 47 | } 48 | MoreSettingsView(formInput: $formInput) 49 | Spacer() 50 | 51 | startButtonContent 52 | } 53 | } 54 | } 55 | } 56 | // 57 | //struct InlineSettingsView_Previews: PreviewProvider { 58 | // static var previews: some View { 59 | // InlineSettingsView() 60 | // } 61 | //} 62 | -------------------------------------------------------------------------------- /iperf-swiftui/views/IperfTestView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // IperfTestView.swift 3 | // iperf-swiftui 4 | // 5 | // Created by Igor Kim on 10.11.20. 6 | // 7 | 8 | import SwiftUI 9 | 10 | import IperfSwift 11 | 12 | struct IperfTestView: View { 13 | @ObservedObject var iperfRunnerController: IperfRunnerController 14 | 15 | var body: some View { 16 | VStack { 17 | ZStack { 18 | HStack { 19 | Text("\(iperfRunnerController.formInput.prot.description)://\(iperfRunnerController.formInput.address):\(iperfRunnerController.formInput.port)") 20 | .font(.subheadline) 21 | } 22 | HStack { 23 | Spacer() 24 | Button(action: { 25 | iperfRunnerController.stop() 26 | iperfRunnerController.isDeleted = true 27 | }) { 28 | Image(systemName: "xmark") 29 | } 30 | 31 | } 32 | } 33 | 34 | if iperfRunnerController.displayError && iperfRunnerController.debugDescription.count > 0 { 35 | Text(iperfRunnerController.debugDescription) 36 | } 37 | IperfTestStatusText(runnerState: $iperfRunnerController.runnerState, role: iperfRunnerController.formInput.role, results: $iperfRunnerController.results) 38 | .padding(.vertical, 5) 39 | 40 | if let lastResult = iperfRunnerController.results.last { 41 | HStack { 42 | Image(systemName: iperfRunnerController.formInput.direction.uiImage!) 43 | .frame(maxHeight: .infinity, alignment: .center) 44 | Text(lastResult.throughput.pretty(format: "%.0f")) 45 | .font(.subheadline) 46 | } 47 | .padding(.bottom) 48 | } 49 | if iperfRunnerController.results.count > 0 { 50 | ResultsView(results: $iperfRunnerController.results) 51 | } 52 | } 53 | .frame(maxWidth: .infinity) 54 | .padding(10) 55 | .background( 56 | Color(UIColor.secondarySystemGroupedBackground) 57 | .shadow(color: .primary, radius: 2.0, x: 0.0, y: 0.0) 58 | .blur(radius: 3.0) 59 | ) 60 | .padding(5) 61 | } 62 | } 63 | 64 | 65 | struct IperfTestStatusText: View { 66 | @Binding var runnerState: IperfRunnerState 67 | var role: IperfRole 68 | @Binding var results: [IperfIntervalResult] 69 | 70 | var body: some View { 71 | ZStack { 72 | if runnerState == .running 73 | && results.count == 0 { 74 | if role == .server { 75 | Text("Listnening...") 76 | } else { 77 | Text("Preparing...") 78 | } 79 | } else if runnerState != .running 80 | && runnerState != .ready 81 | && runnerState != .finished { 82 | Text(runnerState.description) 83 | } 84 | } 85 | .foregroundColor(.primary) 86 | } 87 | } 88 | 89 | //struct IperfTestView_Previews: PreviewProvider { 90 | // static var previews: some View { 91 | // IperfTestView() 92 | // } 93 | //} 94 | -------------------------------------------------------------------------------- /iperf-swiftui/views/MoreSettingsView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MoreSettingsView.swift 3 | // iperf3-swift 4 | // 5 | // Created by Igor Kim on 28.10.20. 6 | // 7 | 8 | import SwiftUI 9 | 10 | let rateOptions: [RateOption] = [.Kbps, .Mbps, .Gbps] 11 | 12 | struct MoreSettingsView: View { 13 | @State var isPresented: Bool = false 14 | @Binding var formInput: IperfConfigurationInput 15 | 16 | var body: some View { 17 | Button(action: { isPresented = true }) { 18 | Image(systemName: "ellipsis") 19 | .frame(height: barButtonHeight) 20 | .padding(10) 21 | .background(Color.secondary) 22 | .foregroundColor(Color.primary) 23 | .cornerRadius(5) 24 | } 25 | .sheet(isPresented: $isPresented) { 26 | NavigationView { 27 | Form { 28 | if formInput.role == .client { 29 | Section { 30 | HStack { 31 | Text("Protocol") 32 | Spacer() 33 | OptionsPicker( 34 | options: formInput.protocolOptions, 35 | selected: $formInput.protocolIndex 36 | ) 37 | } 38 | } 39 | Section { 40 | TextFieldWithLabel(label: "Duration", text: $formInput.duration) 41 | if formInput.prot == .tcp { 42 | TextFieldWithLabel(label: "Streams", text: $formInput.nofStreams) 43 | } 44 | if formInput.prot == .udp { 45 | TextFieldWithOption(label: "Rate", value: $formInput.rate, options: rateOptions) 46 | } 47 | } 48 | } 49 | 50 | Section { 51 | TextFieldWithLabel(label: "Reporting Interval", text: $formInput.reportInterval) 52 | TextFieldWithLabel(label: "Connection Timeout", text: $formInput.timeout) 53 | } 54 | } 55 | .navigationBarTitle("Additional Parameters", displayMode: .inline) 56 | .navigationBarItems(trailing: Button("Done") { isPresented = false} ) 57 | } 58 | .navigationViewStyle(StackNavigationViewStyle()) 59 | } 60 | } 61 | } 62 | 63 | struct MoreSettingsView_Previews: PreviewProvider { 64 | 65 | static var previews: some View { 66 | MoreSettingsView(formInput: .constant(IperfConfigurationInput(address: "123.123.123.123"))) 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /iperf-swiftui/views/ResultsView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ResultsView.swift 3 | // iperf3-swift 4 | // 5 | // Created by Igor Kim on 28.10.20. 6 | // 7 | 8 | import SwiftUI 9 | 10 | import IperfSwift 11 | 12 | struct ResultsView: View { 13 | @Binding var results: [IperfIntervalResult] 14 | 15 | var body: some View { 16 | VStack { 17 | ForEach(results.reversed()) { res in 18 | HStack { 19 | Text("\(String(format: "%.2f-%.2f", res.startTime - results.first!.startTime, res.endTime - results.first!.startTime))") 20 | Spacer() 21 | Text("\(res.throughput.pretty(format: "%.0f"))") 22 | } 23 | } 24 | } 25 | .animation(.linear) 26 | .padding(.horizontal) 27 | .padding(.bottom) 28 | } 29 | } 30 | 31 | struct ResultsView_Previews: PreviewProvider { 32 | static var previews: some View { 33 | let results: [IperfIntervalResult] = [ 34 | IperfIntervalResult(), 35 | IperfIntervalResult() 36 | ] 37 | return ResultsView(results: .constant(results)) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /iperf-swiftui/views/SelectPortView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SelectPortView.swift 3 | // iperf-swiftui 4 | // 5 | // Created by Igor Kim on 09.11.20. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct SelectPortView: View { 11 | @Binding var server: ServerEntry 12 | @Binding var formInput: IperfConfigurationInput 13 | 14 | @State var isPresented = false 15 | 16 | var body: some View { 17 | let ports = formInput.prot == .udp ? server.portsParsed?.udp : server.portsParsed?.tcp 18 | 19 | return Button(action: { isPresented.toggle() }) { 20 | Image(systemName: "chevron.down.square.fill") 21 | .font(.title) 22 | .foregroundColor(Color.primary) 23 | } 24 | .foregroundColor(ports == nil || ports!.count == 0 ? .gray : .primary) 25 | .sheet(isPresented: $isPresented) { 26 | NavigationView { 27 | VStack { 28 | if ports == nil || ports!.count == 0 { 29 | Text("No ports available for \(formInput.prot.description)") 30 | } else { 31 | List { 32 | ForEach(0.. 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 | 22 | 23 | -------------------------------------------------------------------------------- /iperf-swiftuiTests/iperf-swiftuiRunnerTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // iperf3_runnerTests.swift 3 | // iperf3-swiftTests 4 | // 5 | // Created by Igor Kim on 08.11.20. 6 | // 7 | 8 | import XCTest 9 | 10 | @testable import iperf_swiftui 11 | 12 | import IperfSwift 13 | 14 | class iperf3_runnerTests: XCTestCase { 15 | private var runner: IperfRunner = IperfRunner() 16 | private var formInput: IperfConfigurationInput = IperfConfigurationInput(address: "127.0.0.1") 17 | 18 | override func setUpWithError() throws { 19 | let config = IperfConfiguration(formInput) 20 | runner = IperfRunner(with: config) 21 | } 22 | 23 | override func tearDownWithError() throws { 24 | // Put teardown code here. This method is called after the invocation of each test method in the class. 25 | } 26 | 27 | func testRunFailed() throws { 28 | var results: [IperfIntervalResult] = [] 29 | 30 | runner.start { result in 31 | results.append(result) 32 | } 33 | 34 | let result = XCTWaiter.wait(for: [expectation(description: "Running iPerf3 Client")], timeout: 2.0) 35 | if result == XCTWaiter.Result.timedOut { 36 | XCTAssert(results.count > 2) 37 | XCTAssertEqual(results[2].hasError, true) 38 | XCTAssertEqual(results[2].error, .IESENDMESSAGE) 39 | } else { 40 | XCTFail("Delay interrupted") 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /iperf-swiftuiTests/iperf-swiftuiTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // iperf3_swiftTests.swift 3 | // iperf3-swiftTests 4 | // 5 | // Created by Igor Kim on 07.11.20. 6 | // 7 | 8 | import XCTest 9 | 10 | @testable import iperf_swiftui 11 | 12 | import IperfSwift 13 | 14 | class iperf3_swiftTests: XCTestCase { 15 | private var formInput: IperfConfigurationInput = IperfConfigurationInput(address: "127.0.0.1") 16 | 17 | override func setUpWithError() throws { 18 | formInput = IperfConfigurationInput(address: "127.0.0.1") 19 | } 20 | 21 | override func tearDownWithError() throws { 22 | // Put teardown code here. This method is called after the invocation of each test method in the class. 23 | } 24 | 25 | func testRateOptions() throws { 26 | var t = StringWithOption(value: "5", option: .Mbps, optionIndex: 0) 27 | XCTAssertEqual(t.option.description, "Mbps") 28 | 29 | t = StringWithOption(value: "5", option: .Gbps, optionIndex: 0) 30 | XCTAssertEqual(t.option.description, "Gbps") 31 | 32 | t = StringWithOption(value: "1", option: .Kbps, optionIndex: 0) 33 | XCTAssertEqual(t.option.description, "Kbps") 34 | XCTAssertNil(t.option.uiImage) 35 | } 36 | 37 | func testConfigConversion() throws { 38 | formInput.duration = "5" 39 | formInput.nofStreams = "2" 40 | formInput.port = "5202" 41 | 42 | var configuration = IperfConfiguration(formInput) 43 | XCTAssertEqual(configuration.address, "127.0.0.1") 44 | XCTAssertEqual(configuration.duration, 5.0) 45 | XCTAssertEqual(configuration.numStreams, 2) 46 | XCTAssertEqual(configuration.port, 5202) 47 | XCTAssertEqual(configuration.rate, 1024*1024) 48 | 49 | formInput.rate = StringWithOption(value: "5", option: .Mbps, optionIndex: 0) 50 | configuration = IperfConfiguration(formInput) 51 | XCTAssertEqual(configuration.rate, 5*1024*1024) 52 | 53 | formInput.rate = StringWithOption(value: "4", option: .Kbps, optionIndex: 0) 54 | configuration = IperfConfiguration(formInput) 55 | XCTAssertEqual(configuration.rate, 4*1024) 56 | 57 | formInput.rate = StringWithOption(value: "1", option: .Gbps, optionIndex: 0) 58 | configuration = IperfConfiguration(formInput) 59 | XCTAssertEqual(configuration.rate, 1*1024*1024*1024) 60 | } 61 | 62 | func testRateOutput() throws { 63 | struct TpTestCase { 64 | var id = UUID() 65 | var bytes: UInt64 66 | var seconds: Double 67 | var bytesPerSecond: Double { 68 | Double(bytes) / seconds 69 | } 70 | var pretty: String 71 | } 72 | let cases: [TpTestCase] = [ 73 | TpTestCase(bytes: 1000, seconds: 10, pretty: "800.0 bps"), 74 | TpTestCase(bytes: 1000, seconds: 1, pretty: "7.8 Kbps"), 75 | TpTestCase(bytes: 1000, seconds: 2, pretty: "3.9 Kbps"), 76 | TpTestCase(bytes: 2000000, seconds: 1, pretty: "15.3 Mbps"), 77 | TpTestCase(bytes: 2000000000, seconds: 1, pretty: "14.9 Gbps") 78 | ] 79 | 80 | cases.forEach { c in 81 | let tp1 = IperfThroughput(bytes: c.bytes, seconds: c.seconds) 82 | let tp2 = IperfThroughput(bytesPerSecond: c.bytesPerSecond) 83 | 84 | XCTAssertEqual(tp1.rawValue, tp2.rawValue) 85 | XCTAssertEqual(tp1.bps, tp2.bps) 86 | XCTAssertEqual(tp1.Kbps, tp2.Kbps) 87 | XCTAssertEqual(tp1.Mbps, tp2.Mbps) 88 | XCTAssertEqual(tp1.Gbps, tp2.Gbps) 89 | 90 | XCTAssertEqual(tp1.bps, c.bytesPerSecond*8) 91 | XCTAssertEqual(tp1.Kbps, c.bytesPerSecond/1024*8) 92 | XCTAssertEqual(tp1.Mbps, c.bytesPerSecond/1024/1024*8) 93 | XCTAssertEqual(tp1.Gbps, c.bytesPerSecond/1024/1024/1024*8) 94 | 95 | XCTAssertEqual(tp1.pretty, c.pretty) 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /license.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/igorskh/iperf-swiftui/d7e1fe2f1ca7744d5ded33f4b2debcdf4ca98c83/license.md -------------------------------------------------------------------------------- /public/client-main.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/igorskh/iperf-swiftui/d7e1fe2f1ca7744d5ded33f4b2debcdf4ca98c83/public/client-main.png -------------------------------------------------------------------------------- /public/params-tcp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/igorskh/iperf-swiftui/d7e1fe2f1ca7744d5ded33f4b2debcdf4ca98c83/public/params-tcp.png -------------------------------------------------------------------------------- /public/params-udp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/igorskh/iperf-swiftui/d7e1fe2f1ca7744d5ded33f4b2debcdf4ca98c83/public/params-udp.png -------------------------------------------------------------------------------- /public/server-main.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/igorskh/iperf-swiftui/d7e1fe2f1ca7744d5ded33f4b2debcdf4ca98c83/public/server-main.png -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # iPerf3 Client/Server 2 | 3 | This application is using an [iPerf3 Swift wrapper](https://github.com/igorskh/iperf-swift). 4 | 5 | iPerf3 client/server GUI written in SwiftUI for iOS, iPadOS and macOS. 6 | 7 | ![Client main screen](public/client-main.png) 8 | 9 | ![Server main screen](public/server-main.png) 10 | 11 | ![TCP additional parameters](public/params-tcp.png) 12 | 13 | ![UDP additional parameters](public/params-udp.png) --------------------------------------------------------------------------------