├── .gitignore ├── Plistor.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist ├── xcshareddata │ └── xcschemes │ │ └── Plistor.xcscheme └── xcuserdata │ └── .gitignore ├── Plistor ├── AppDelegate.swift ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ ├── Contents.json │ │ ├── Icon-App-20x20@1x.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 │ │ ├── Icon-App-83.5x83.5@2x.png │ │ └── ItunesArtwork@2x.png │ └── Contents.json ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── ConsoleViewController.swift ├── Document.swift ├── DocumentBrowserViewController.swift ├── DocumentViewController.swift ├── Info.plist ├── MovableTextField.swift ├── Plistor.entitlements ├── Property List.json ├── Property List.plist ├── SceneDelegate.swift ├── TextField.xib └── UITextView.swift ├── README.md └── View JSON or Plist ├── ExtensionPreProcessing.js ├── Info.plist └── Media.xcassets ├── AppIcon.appiconset ├── Contents.json ├── Icon-App-20x20@1x.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 └── Icon-App-83.5x83.5@2x.png ├── Contents.json └── TouchBarBezel.colorset └── Contents.json /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.xcuserdatad 3 | -------------------------------------------------------------------------------- /Plistor.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | C903DD6823A5430900CEA7AF /* Property List.json in Resources */ = {isa = PBXBuildFile; fileRef = C903DD6723A5430900CEA7AF /* Property List.json */; }; 11 | C9304B8C23AEB810000B9388 /* Media.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C9304B8B23AEB810000B9388 /* Media.xcassets */; }; 12 | C9304B9523AEB810000B9388 /* View JSON or Plist.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = C9304B8923AEB810000B9388 /* View JSON or Plist.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 13 | C9304B9B23AEBE61000B9388 /* ExtensionPreProcessing.js in Resources */ = {isa = PBXBuildFile; fileRef = C9304B9A23AEBE61000B9388 /* ExtensionPreProcessing.js */; }; 14 | C9304B9C23AEC454000B9388 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = C935AA5523A445FA00C9B34A /* Main.storyboard */; }; 15 | C9304B9D23AEC45B000B9388 /* TextField.xib in Resources */ = {isa = PBXBuildFile; fileRef = C958540123AC4FF7001B26CE /* TextField.xib */; }; 16 | C9304B9E23AEC46B000B9388 /* DocumentViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C935AA5123A445FA00C9B34A /* DocumentViewController.swift */; }; 17 | C9304B9F23AEC46B000B9388 /* ConsoleViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C95853FF23AC4F26001B26CE /* ConsoleViewController.swift */; }; 18 | C9304BA023AEC46B000B9388 /* Document.swift in Sources */ = {isa = PBXBuildFile; fileRef = C935AA5323A445FA00C9B34A /* Document.swift */; }; 19 | C9304BA123AEC46B000B9388 /* MovableTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = C95853FD23AC4ED2001B26CE /* MovableTextField.swift */; }; 20 | C9304BA223AEC46B000B9388 /* UITextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C958540523AC545E001B26CE /* UITextView.swift */; }; 21 | C9304BA323AECAA6000B9388 /* DocumentBrowserViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C935AA4F23A445FA00C9B34A /* DocumentBrowserViewController.swift */; }; 22 | C935AA4E23A445FA00C9B34A /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C935AA4D23A445FA00C9B34A /* AppDelegate.swift */; }; 23 | C935AA5023A445FA00C9B34A /* DocumentBrowserViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C935AA4F23A445FA00C9B34A /* DocumentBrowserViewController.swift */; }; 24 | C935AA5223A445FA00C9B34A /* DocumentViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C935AA5123A445FA00C9B34A /* DocumentViewController.swift */; }; 25 | C935AA5423A445FA00C9B34A /* Document.swift in Sources */ = {isa = PBXBuildFile; fileRef = C935AA5323A445FA00C9B34A /* Document.swift */; }; 26 | C935AA5723A445FA00C9B34A /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = C935AA5523A445FA00C9B34A /* Main.storyboard */; }; 27 | C935AA5923A445FC00C9B34A /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C935AA5823A445FC00C9B34A /* Assets.xcassets */; }; 28 | C935AA5C23A445FC00C9B34A /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = C935AA5A23A445FC00C9B34A /* LaunchScreen.storyboard */; }; 29 | C935AA6623A4477500C9B34A /* Property List.plist in Resources */ = {isa = PBXBuildFile; fileRef = C935AA6523A4477500C9B34A /* Property List.plist */; }; 30 | C95853FE23AC4ED2001B26CE /* MovableTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = C95853FD23AC4ED2001B26CE /* MovableTextField.swift */; }; 31 | C958540023AC4F26001B26CE /* ConsoleViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C95853FF23AC4F26001B26CE /* ConsoleViewController.swift */; }; 32 | C958540223AC4FF7001B26CE /* TextField.xib in Resources */ = {isa = PBXBuildFile; fileRef = C958540123AC4FF7001B26CE /* TextField.xib */; }; 33 | C958540623AC545E001B26CE /* UITextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C958540523AC545E001B26CE /* UITextView.swift */; }; 34 | C97E7A4123A842170061DAE2 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C97E7A4023A842170061DAE2 /* SceneDelegate.swift */; }; 35 | /* End PBXBuildFile section */ 36 | 37 | /* Begin PBXContainerItemProxy section */ 38 | C9304B9323AEB810000B9388 /* PBXContainerItemProxy */ = { 39 | isa = PBXContainerItemProxy; 40 | containerPortal = C935AA4223A445FA00C9B34A /* Project object */; 41 | proxyType = 1; 42 | remoteGlobalIDString = C9304B8823AEB810000B9388; 43 | remoteInfo = "View JSON or Plist"; 44 | }; 45 | /* End PBXContainerItemProxy section */ 46 | 47 | /* Begin PBXCopyFilesBuildPhase section */ 48 | C9304B9623AEB810000B9388 /* Embed App Extensions */ = { 49 | isa = PBXCopyFilesBuildPhase; 50 | buildActionMask = 2147483647; 51 | dstPath = ""; 52 | dstSubfolderSpec = 13; 53 | files = ( 54 | C9304B9523AEB810000B9388 /* View JSON or Plist.appex in Embed App Extensions */, 55 | ); 56 | name = "Embed App Extensions"; 57 | runOnlyForDeploymentPostprocessing = 0; 58 | }; 59 | /* End PBXCopyFilesBuildPhase section */ 60 | 61 | /* Begin PBXFileReference section */ 62 | C903DD6723A5430900CEA7AF /* Property List.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "Property List.json"; sourceTree = ""; }; 63 | C903DD6923A5452200CEA7AF /* Plistor.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Plistor.entitlements; sourceTree = ""; }; 64 | C9304B8923AEB810000B9388 /* View JSON or Plist.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "View JSON or Plist.appex"; sourceTree = BUILT_PRODUCTS_DIR; }; 65 | C9304B8B23AEB810000B9388 /* Media.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Media.xcassets; sourceTree = ""; }; 66 | C9304B9223AEB810000B9388 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 67 | C9304B9A23AEBE61000B9388 /* ExtensionPreProcessing.js */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.javascript; path = ExtensionPreProcessing.js; sourceTree = ""; }; 68 | C935AA4A23A445FA00C9B34A /* Plistor.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Plistor.app; sourceTree = BUILT_PRODUCTS_DIR; }; 69 | C935AA4D23A445FA00C9B34A /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 70 | C935AA4F23A445FA00C9B34A /* DocumentBrowserViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DocumentBrowserViewController.swift; sourceTree = ""; }; 71 | C935AA5123A445FA00C9B34A /* DocumentViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DocumentViewController.swift; sourceTree = ""; }; 72 | C935AA5323A445FA00C9B34A /* Document.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Document.swift; sourceTree = ""; }; 73 | C935AA5623A445FA00C9B34A /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 74 | C935AA5823A445FC00C9B34A /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 75 | C935AA5B23A445FC00C9B34A /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 76 | C935AA5D23A445FC00C9B34A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 77 | C935AA6523A4477500C9B34A /* Property List.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Property List.plist"; sourceTree = ""; }; 78 | C95853FD23AC4ED2001B26CE /* MovableTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MovableTextField.swift; sourceTree = ""; }; 79 | C95853FF23AC4F26001B26CE /* ConsoleViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConsoleViewController.swift; sourceTree = ""; }; 80 | C958540123AC4FF7001B26CE /* TextField.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = TextField.xib; sourceTree = ""; }; 81 | C958540523AC545E001B26CE /* UITextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UITextView.swift; sourceTree = ""; }; 82 | C97BD6E823A9B5ED00CB4681 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; 83 | C97E7A4023A842170061DAE2 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; 84 | /* End PBXFileReference section */ 85 | 86 | /* Begin PBXFrameworksBuildPhase section */ 87 | C9304B8623AEB810000B9388 /* Frameworks */ = { 88 | isa = PBXFrameworksBuildPhase; 89 | buildActionMask = 2147483647; 90 | files = ( 91 | ); 92 | runOnlyForDeploymentPostprocessing = 0; 93 | }; 94 | C935AA4723A445FA00C9B34A /* Frameworks */ = { 95 | isa = PBXFrameworksBuildPhase; 96 | buildActionMask = 2147483647; 97 | files = ( 98 | ); 99 | runOnlyForDeploymentPostprocessing = 0; 100 | }; 101 | /* End PBXFrameworksBuildPhase section */ 102 | 103 | /* Begin PBXGroup section */ 104 | C9304B8A23AEB810000B9388 /* View JSON or Plist */ = { 105 | isa = PBXGroup; 106 | children = ( 107 | C9304B8B23AEB810000B9388 /* Media.xcassets */, 108 | C9304B9223AEB810000B9388 /* Info.plist */, 109 | C9304B9A23AEBE61000B9388 /* ExtensionPreProcessing.js */, 110 | ); 111 | path = "View JSON or Plist"; 112 | sourceTree = ""; 113 | }; 114 | C935AA4123A445FA00C9B34A = { 115 | isa = PBXGroup; 116 | children = ( 117 | C97BD6E823A9B5ED00CB4681 /* README.md */, 118 | C935AA4C23A445FA00C9B34A /* Plistor */, 119 | C9304B8A23AEB810000B9388 /* View JSON or Plist */, 120 | C935AA4B23A445FA00C9B34A /* Products */, 121 | ); 122 | sourceTree = ""; 123 | }; 124 | C935AA4B23A445FA00C9B34A /* Products */ = { 125 | isa = PBXGroup; 126 | children = ( 127 | C935AA4A23A445FA00C9B34A /* Plistor.app */, 128 | C9304B8923AEB810000B9388 /* View JSON or Plist.appex */, 129 | ); 130 | name = Products; 131 | sourceTree = ""; 132 | }; 133 | C935AA4C23A445FA00C9B34A /* Plistor */ = { 134 | isa = PBXGroup; 135 | children = ( 136 | C903DD6923A5452200CEA7AF /* Plistor.entitlements */, 137 | C935AA4D23A445FA00C9B34A /* AppDelegate.swift */, 138 | C97E7A4023A842170061DAE2 /* SceneDelegate.swift */, 139 | C935AA4F23A445FA00C9B34A /* DocumentBrowserViewController.swift */, 140 | C935AA5123A445FA00C9B34A /* DocumentViewController.swift */, 141 | C95853FF23AC4F26001B26CE /* ConsoleViewController.swift */, 142 | C935AA5323A445FA00C9B34A /* Document.swift */, 143 | C95853FD23AC4ED2001B26CE /* MovableTextField.swift */, 144 | C958540523AC545E001B26CE /* UITextView.swift */, 145 | C958540123AC4FF7001B26CE /* TextField.xib */, 146 | C935AA5523A445FA00C9B34A /* Main.storyboard */, 147 | C935AA5823A445FC00C9B34A /* Assets.xcassets */, 148 | C935AA5A23A445FC00C9B34A /* LaunchScreen.storyboard */, 149 | C935AA5D23A445FC00C9B34A /* Info.plist */, 150 | C935AA6523A4477500C9B34A /* Property List.plist */, 151 | C903DD6723A5430900CEA7AF /* Property List.json */, 152 | ); 153 | path = Plistor; 154 | sourceTree = ""; 155 | }; 156 | /* End PBXGroup section */ 157 | 158 | /* Begin PBXNativeTarget section */ 159 | C9304B8823AEB810000B9388 /* View JSON or Plist */ = { 160 | isa = PBXNativeTarget; 161 | buildConfigurationList = C9304B9923AEB810000B9388 /* Build configuration list for PBXNativeTarget "View JSON or Plist" */; 162 | buildPhases = ( 163 | C9304B8523AEB810000B9388 /* Sources */, 164 | C9304B8623AEB810000B9388 /* Frameworks */, 165 | C9304B8723AEB810000B9388 /* Resources */, 166 | ); 167 | buildRules = ( 168 | ); 169 | dependencies = ( 170 | ); 171 | name = "View JSON or Plist"; 172 | productName = "View JSON or Plist"; 173 | productReference = C9304B8923AEB810000B9388 /* View JSON or Plist.appex */; 174 | productType = "com.apple.product-type.app-extension"; 175 | }; 176 | C935AA4923A445FA00C9B34A /* Plistor */ = { 177 | isa = PBXNativeTarget; 178 | buildConfigurationList = C935AA6023A445FC00C9B34A /* Build configuration list for PBXNativeTarget "Plistor" */; 179 | buildPhases = ( 180 | C935AA4623A445FA00C9B34A /* Sources */, 181 | C935AA4723A445FA00C9B34A /* Frameworks */, 182 | C935AA4823A445FA00C9B34A /* Resources */, 183 | C9304B9623AEB810000B9388 /* Embed App Extensions */, 184 | ); 185 | buildRules = ( 186 | ); 187 | dependencies = ( 188 | C9304B9423AEB810000B9388 /* PBXTargetDependency */, 189 | ); 190 | name = Plistor; 191 | productName = Plistor; 192 | productReference = C935AA4A23A445FA00C9B34A /* Plistor.app */; 193 | productType = "com.apple.product-type.application"; 194 | }; 195 | /* End PBXNativeTarget section */ 196 | 197 | /* Begin PBXProject section */ 198 | C935AA4223A445FA00C9B34A /* Project object */ = { 199 | isa = PBXProject; 200 | attributes = { 201 | LastSwiftUpdateCheck = 1120; 202 | LastUpgradeCheck = 1120; 203 | ORGANIZATIONNAME = "Adrian Labbé"; 204 | TargetAttributes = { 205 | C9304B8823AEB810000B9388 = { 206 | CreatedOnToolsVersion = 11.2.1; 207 | }; 208 | C935AA4923A445FA00C9B34A = { 209 | CreatedOnToolsVersion = 11.2.1; 210 | }; 211 | }; 212 | }; 213 | buildConfigurationList = C935AA4523A445FA00C9B34A /* Build configuration list for PBXProject "Plistor" */; 214 | compatibilityVersion = "Xcode 9.3"; 215 | developmentRegion = en; 216 | hasScannedForEncodings = 0; 217 | knownRegions = ( 218 | en, 219 | Base, 220 | ); 221 | mainGroup = C935AA4123A445FA00C9B34A; 222 | productRefGroup = C935AA4B23A445FA00C9B34A /* Products */; 223 | projectDirPath = ""; 224 | projectRoot = ""; 225 | targets = ( 226 | C935AA4923A445FA00C9B34A /* Plistor */, 227 | C9304B8823AEB810000B9388 /* View JSON or Plist */, 228 | ); 229 | }; 230 | /* End PBXProject section */ 231 | 232 | /* Begin PBXResourcesBuildPhase section */ 233 | C9304B8723AEB810000B9388 /* Resources */ = { 234 | isa = PBXResourcesBuildPhase; 235 | buildActionMask = 2147483647; 236 | files = ( 237 | C9304B9C23AEC454000B9388 /* Main.storyboard in Resources */, 238 | C9304B8C23AEB810000B9388 /* Media.xcassets in Resources */, 239 | C9304B9B23AEBE61000B9388 /* ExtensionPreProcessing.js in Resources */, 240 | C9304B9D23AEC45B000B9388 /* TextField.xib in Resources */, 241 | ); 242 | runOnlyForDeploymentPostprocessing = 0; 243 | }; 244 | C935AA4823A445FA00C9B34A /* Resources */ = { 245 | isa = PBXResourcesBuildPhase; 246 | buildActionMask = 2147483647; 247 | files = ( 248 | C935AA5C23A445FC00C9B34A /* LaunchScreen.storyboard in Resources */, 249 | C935AA5923A445FC00C9B34A /* Assets.xcassets in Resources */, 250 | C935AA6623A4477500C9B34A /* Property List.plist in Resources */, 251 | C958540223AC4FF7001B26CE /* TextField.xib in Resources */, 252 | C903DD6823A5430900CEA7AF /* Property List.json in Resources */, 253 | C935AA5723A445FA00C9B34A /* Main.storyboard in Resources */, 254 | ); 255 | runOnlyForDeploymentPostprocessing = 0; 256 | }; 257 | /* End PBXResourcesBuildPhase section */ 258 | 259 | /* Begin PBXSourcesBuildPhase section */ 260 | C9304B8523AEB810000B9388 /* Sources */ = { 261 | isa = PBXSourcesBuildPhase; 262 | buildActionMask = 2147483647; 263 | files = ( 264 | C9304BA023AEC46B000B9388 /* Document.swift in Sources */, 265 | C9304BA223AEC46B000B9388 /* UITextView.swift in Sources */, 266 | C9304B9F23AEC46B000B9388 /* ConsoleViewController.swift in Sources */, 267 | C9304B9E23AEC46B000B9388 /* DocumentViewController.swift in Sources */, 268 | C9304BA323AECAA6000B9388 /* DocumentBrowserViewController.swift in Sources */, 269 | C9304BA123AEC46B000B9388 /* MovableTextField.swift in Sources */, 270 | ); 271 | runOnlyForDeploymentPostprocessing = 0; 272 | }; 273 | C935AA4623A445FA00C9B34A /* Sources */ = { 274 | isa = PBXSourcesBuildPhase; 275 | buildActionMask = 2147483647; 276 | files = ( 277 | C95853FE23AC4ED2001B26CE /* MovableTextField.swift in Sources */, 278 | C958540623AC545E001B26CE /* UITextView.swift in Sources */, 279 | C97E7A4123A842170061DAE2 /* SceneDelegate.swift in Sources */, 280 | C935AA5423A445FA00C9B34A /* Document.swift in Sources */, 281 | C935AA5223A445FA00C9B34A /* DocumentViewController.swift in Sources */, 282 | C958540023AC4F26001B26CE /* ConsoleViewController.swift in Sources */, 283 | C935AA5023A445FA00C9B34A /* DocumentBrowserViewController.swift in Sources */, 284 | C935AA4E23A445FA00C9B34A /* AppDelegate.swift in Sources */, 285 | ); 286 | runOnlyForDeploymentPostprocessing = 0; 287 | }; 288 | /* End PBXSourcesBuildPhase section */ 289 | 290 | /* Begin PBXTargetDependency section */ 291 | C9304B9423AEB810000B9388 /* PBXTargetDependency */ = { 292 | isa = PBXTargetDependency; 293 | target = C9304B8823AEB810000B9388 /* View JSON or Plist */; 294 | targetProxy = C9304B9323AEB810000B9388 /* PBXContainerItemProxy */; 295 | }; 296 | /* End PBXTargetDependency section */ 297 | 298 | /* Begin PBXVariantGroup section */ 299 | C935AA5523A445FA00C9B34A /* Main.storyboard */ = { 300 | isa = PBXVariantGroup; 301 | children = ( 302 | C935AA5623A445FA00C9B34A /* Base */, 303 | ); 304 | name = Main.storyboard; 305 | sourceTree = ""; 306 | }; 307 | C935AA5A23A445FC00C9B34A /* LaunchScreen.storyboard */ = { 308 | isa = PBXVariantGroup; 309 | children = ( 310 | C935AA5B23A445FC00C9B34A /* Base */, 311 | ); 312 | name = LaunchScreen.storyboard; 313 | sourceTree = ""; 314 | }; 315 | /* End PBXVariantGroup section */ 316 | 317 | /* Begin XCBuildConfiguration section */ 318 | C9304B9723AEB810000B9388 /* Debug */ = { 319 | isa = XCBuildConfiguration; 320 | buildSettings = { 321 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 322 | CODE_SIGN_STYLE = Automatic; 323 | DEVELOPMENT_TEAM = Y96PL6G497; 324 | GCC_PREPROCESSOR_DEFINITIONS = "DEBUG=1"; 325 | INFOPLIST_FILE = "View JSON or Plist/Info.plist"; 326 | LD_RUNPATH_SEARCH_PATHS = ( 327 | "$(inherited)", 328 | "@executable_path/Frameworks", 329 | "@executable_path/../../Frameworks", 330 | ); 331 | OTHER_CFLAGS = ""; 332 | PRODUCT_BUNDLE_IDENTIFIER = "ch.ada.Plistor.View-JSON-or-Plist"; 333 | PRODUCT_NAME = "View JSON or Plist"; 334 | SKIP_INSTALL = YES; 335 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG APP_EXTENSION"; 336 | SWIFT_VERSION = 5.0; 337 | TARGETED_DEVICE_FAMILY = "1,2"; 338 | }; 339 | name = Debug; 340 | }; 341 | C9304B9823AEB810000B9388 /* Release */ = { 342 | isa = XCBuildConfiguration; 343 | buildSettings = { 344 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 345 | CODE_SIGN_STYLE = Automatic; 346 | DEVELOPMENT_TEAM = Y96PL6G497; 347 | GCC_PREPROCESSOR_DEFINITIONS = ""; 348 | INFOPLIST_FILE = "View JSON or Plist/Info.plist"; 349 | LD_RUNPATH_SEARCH_PATHS = ( 350 | "$(inherited)", 351 | "@executable_path/Frameworks", 352 | "@executable_path/../../Frameworks", 353 | ); 354 | OTHER_CFLAGS = ""; 355 | PRODUCT_BUNDLE_IDENTIFIER = "ch.ada.Plistor.View-JSON-or-Plist"; 356 | PRODUCT_NAME = "View JSON or Plist"; 357 | SKIP_INSTALL = YES; 358 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = APP_EXTENSION; 359 | SWIFT_VERSION = 5.0; 360 | TARGETED_DEVICE_FAMILY = "1,2"; 361 | }; 362 | name = Release; 363 | }; 364 | C935AA5E23A445FC00C9B34A /* Debug */ = { 365 | isa = XCBuildConfiguration; 366 | buildSettings = { 367 | ALWAYS_SEARCH_USER_PATHS = NO; 368 | CLANG_ANALYZER_NONNULL = YES; 369 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 370 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 371 | CLANG_CXX_LIBRARY = "libc++"; 372 | CLANG_ENABLE_MODULES = YES; 373 | CLANG_ENABLE_OBJC_ARC = YES; 374 | CLANG_ENABLE_OBJC_WEAK = YES; 375 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 376 | CLANG_WARN_BOOL_CONVERSION = YES; 377 | CLANG_WARN_COMMA = YES; 378 | CLANG_WARN_CONSTANT_CONVERSION = YES; 379 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 380 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 381 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 382 | CLANG_WARN_EMPTY_BODY = YES; 383 | CLANG_WARN_ENUM_CONVERSION = YES; 384 | CLANG_WARN_INFINITE_RECURSION = YES; 385 | CLANG_WARN_INT_CONVERSION = YES; 386 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 387 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 388 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 389 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 390 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 391 | CLANG_WARN_STRICT_PROTOTYPES = YES; 392 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 393 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 394 | CLANG_WARN_UNREACHABLE_CODE = YES; 395 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 396 | COPY_PHASE_STRIP = NO; 397 | DEBUG_INFORMATION_FORMAT = dwarf; 398 | ENABLE_STRICT_OBJC_MSGSEND = YES; 399 | ENABLE_TESTABILITY = YES; 400 | GCC_C_LANGUAGE_STANDARD = gnu11; 401 | GCC_DYNAMIC_NO_PIC = NO; 402 | GCC_NO_COMMON_BLOCKS = YES; 403 | GCC_OPTIMIZATION_LEVEL = 0; 404 | GCC_PREPROCESSOR_DEFINITIONS = ( 405 | "DEBUG=1", 406 | "$(inherited)", 407 | ); 408 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 409 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 410 | GCC_WARN_UNDECLARED_SELECTOR = YES; 411 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 412 | GCC_WARN_UNUSED_FUNCTION = YES; 413 | GCC_WARN_UNUSED_VARIABLE = YES; 414 | IPHONEOS_DEPLOYMENT_TARGET = 13.2; 415 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 416 | MTL_FAST_MATH = YES; 417 | ONLY_ACTIVE_ARCH = YES; 418 | SDKROOT = iphoneos; 419 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 420 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 421 | }; 422 | name = Debug; 423 | }; 424 | C935AA5F23A445FC00C9B34A /* Release */ = { 425 | isa = XCBuildConfiguration; 426 | buildSettings = { 427 | ALWAYS_SEARCH_USER_PATHS = NO; 428 | CLANG_ANALYZER_NONNULL = YES; 429 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 430 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 431 | CLANG_CXX_LIBRARY = "libc++"; 432 | CLANG_ENABLE_MODULES = YES; 433 | CLANG_ENABLE_OBJC_ARC = YES; 434 | CLANG_ENABLE_OBJC_WEAK = YES; 435 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 436 | CLANG_WARN_BOOL_CONVERSION = YES; 437 | CLANG_WARN_COMMA = YES; 438 | CLANG_WARN_CONSTANT_CONVERSION = YES; 439 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 440 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 441 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 442 | CLANG_WARN_EMPTY_BODY = YES; 443 | CLANG_WARN_ENUM_CONVERSION = YES; 444 | CLANG_WARN_INFINITE_RECURSION = YES; 445 | CLANG_WARN_INT_CONVERSION = YES; 446 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 447 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 448 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 449 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 450 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 451 | CLANG_WARN_STRICT_PROTOTYPES = YES; 452 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 453 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 454 | CLANG_WARN_UNREACHABLE_CODE = YES; 455 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 456 | COPY_PHASE_STRIP = NO; 457 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 458 | ENABLE_NS_ASSERTIONS = NO; 459 | ENABLE_STRICT_OBJC_MSGSEND = YES; 460 | GCC_C_LANGUAGE_STANDARD = gnu11; 461 | GCC_NO_COMMON_BLOCKS = YES; 462 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 463 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 464 | GCC_WARN_UNDECLARED_SELECTOR = YES; 465 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 466 | GCC_WARN_UNUSED_FUNCTION = YES; 467 | GCC_WARN_UNUSED_VARIABLE = YES; 468 | IPHONEOS_DEPLOYMENT_TARGET = 13.2; 469 | MTL_ENABLE_DEBUG_INFO = NO; 470 | MTL_FAST_MATH = YES; 471 | SDKROOT = iphoneos; 472 | SWIFT_COMPILATION_MODE = wholemodule; 473 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 474 | VALIDATE_PRODUCT = YES; 475 | }; 476 | name = Release; 477 | }; 478 | C935AA6123A445FC00C9B34A /* Debug */ = { 479 | isa = XCBuildConfiguration; 480 | buildSettings = { 481 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 482 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 483 | CODE_SIGN_ENTITLEMENTS = Plistor/Plistor.entitlements; 484 | CODE_SIGN_STYLE = Automatic; 485 | DERIVE_MACCATALYST_PRODUCT_BUNDLE_IDENTIFIER = YES; 486 | DEVELOPMENT_TEAM = Y96PL6G497; 487 | INFOPLIST_FILE = Plistor/Info.plist; 488 | IPHONEOS_DEPLOYMENT_TARGET = 13.0; 489 | LD_RUNPATH_SEARCH_PATHS = ( 490 | "$(inherited)", 491 | "@executable_path/Frameworks", 492 | ); 493 | PRODUCT_BUNDLE_IDENTIFIER = ch.ada.Plistor; 494 | PRODUCT_NAME = "$(TARGET_NAME)"; 495 | SUPPORTS_MACCATALYST = YES; 496 | SWIFT_VERSION = 5.0; 497 | TARGETED_DEVICE_FAMILY = "1,2"; 498 | }; 499 | name = Debug; 500 | }; 501 | C935AA6223A445FC00C9B34A /* Release */ = { 502 | isa = XCBuildConfiguration; 503 | buildSettings = { 504 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 505 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 506 | CODE_SIGN_ENTITLEMENTS = Plistor/Plistor.entitlements; 507 | CODE_SIGN_STYLE = Automatic; 508 | DERIVE_MACCATALYST_PRODUCT_BUNDLE_IDENTIFIER = YES; 509 | DEVELOPMENT_TEAM = Y96PL6G497; 510 | INFOPLIST_FILE = Plistor/Info.plist; 511 | IPHONEOS_DEPLOYMENT_TARGET = 13.0; 512 | LD_RUNPATH_SEARCH_PATHS = ( 513 | "$(inherited)", 514 | "@executable_path/Frameworks", 515 | ); 516 | PRODUCT_BUNDLE_IDENTIFIER = ch.ada.Plistor; 517 | PRODUCT_NAME = "$(TARGET_NAME)"; 518 | SUPPORTS_MACCATALYST = YES; 519 | SWIFT_VERSION = 5.0; 520 | TARGETED_DEVICE_FAMILY = "1,2"; 521 | }; 522 | name = Release; 523 | }; 524 | /* End XCBuildConfiguration section */ 525 | 526 | /* Begin XCConfigurationList section */ 527 | C9304B9923AEB810000B9388 /* Build configuration list for PBXNativeTarget "View JSON or Plist" */ = { 528 | isa = XCConfigurationList; 529 | buildConfigurations = ( 530 | C9304B9723AEB810000B9388 /* Debug */, 531 | C9304B9823AEB810000B9388 /* Release */, 532 | ); 533 | defaultConfigurationIsVisible = 0; 534 | defaultConfigurationName = Release; 535 | }; 536 | C935AA4523A445FA00C9B34A /* Build configuration list for PBXProject "Plistor" */ = { 537 | isa = XCConfigurationList; 538 | buildConfigurations = ( 539 | C935AA5E23A445FC00C9B34A /* Debug */, 540 | C935AA5F23A445FC00C9B34A /* Release */, 541 | ); 542 | defaultConfigurationIsVisible = 0; 543 | defaultConfigurationName = Release; 544 | }; 545 | C935AA6023A445FC00C9B34A /* Build configuration list for PBXNativeTarget "Plistor" */ = { 546 | isa = XCConfigurationList; 547 | buildConfigurations = ( 548 | C935AA6123A445FC00C9B34A /* Debug */, 549 | C935AA6223A445FC00C9B34A /* Release */, 550 | ); 551 | defaultConfigurationIsVisible = 0; 552 | defaultConfigurationName = Release; 553 | }; 554 | /* End XCConfigurationList section */ 555 | }; 556 | rootObject = C935AA4223A445FA00C9B34A /* Project object */; 557 | } 558 | -------------------------------------------------------------------------------- /Plistor.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Plistor.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Plistor.xcodeproj/xcshareddata/xcschemes/Plistor.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 45 | 51 | 52 | 53 | 54 | 60 | 62 | 68 | 69 | 70 | 71 | 73 | 74 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /Plistor.xcodeproj/xcuserdata/.gitignore: -------------------------------------------------------------------------------- 1 | *.xcuserdatad 2 | -------------------------------------------------------------------------------- /Plistor/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // Plistor 4 | // 5 | // Created by Adrian Labbé on 13-12-19. 6 | // Copyright © 2019 Adrian Labbé. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | 17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 18 | // Override point for customization after application launch. 19 | return true 20 | } 21 | 22 | func applicationWillResignActive(_ application: UIApplication) { 23 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 24 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 25 | } 26 | 27 | func applicationDidEnterBackground(_ application: UIApplication) { 28 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 29 | } 30 | 31 | func applicationWillEnterForeground(_ application: UIApplication) { 32 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. 33 | } 34 | 35 | func applicationDidBecomeActive(_ application: UIApplication) { 36 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 37 | } 38 | 39 | func application(_ app: UIApplication, open inputURL: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool { 40 | // Ensure the URL is a file URL 41 | guard inputURL.isFileURL else { return false } 42 | 43 | // Reveal / import the document at the URL 44 | guard let documentBrowserViewController = window?.rootViewController as? DocumentBrowserViewController else { return false } 45 | 46 | documentBrowserViewController.revealDocument(at: inputURL, importIfNeeded: true) { (revealedDocumentURL, error) in 47 | if let error = error { 48 | // Handle the error appropriately 49 | print("Failed to reveal the document at URL \(inputURL) with error: '\(error)'") 50 | return 51 | } 52 | 53 | // Present the Document View Controller for the revealed URL 54 | documentBrowserViewController.presentDocument(at: revealedDocumentURL!) 55 | } 56 | 57 | return true 58 | } 59 | 60 | 61 | } 62 | 63 | -------------------------------------------------------------------------------- /Plistor/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "Icon-App-20x20@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "Icon-App-20x20@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "Icon-App-29x29@1x.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "Icon-App-29x29@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "29x29", 29 | "idiom" : "iphone", 30 | "filename" : "Icon-App-29x29@3x.png", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-App-40x40@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "40x40", 41 | "idiom" : "iphone", 42 | "filename" : "Icon-App-40x40@3x.png", 43 | "scale" : "3x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "Icon-App-60x60@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "60x60", 53 | "idiom" : "iphone", 54 | "filename" : "Icon-App-60x60@3x.png", 55 | "scale" : "3x" 56 | }, 57 | { 58 | "size" : "20x20", 59 | "idiom" : "ipad", 60 | "filename" : "Icon-App-20x20@1x.png", 61 | "scale" : "1x" 62 | }, 63 | { 64 | "size" : "20x20", 65 | "idiom" : "ipad", 66 | "filename" : "Icon-App-20x20@2x.png", 67 | "scale" : "2x" 68 | }, 69 | { 70 | "size" : "29x29", 71 | "idiom" : "ipad", 72 | "filename" : "Icon-App-29x29@1x.png", 73 | "scale" : "1x" 74 | }, 75 | { 76 | "size" : "29x29", 77 | "idiom" : "ipad", 78 | "filename" : "Icon-App-29x29@2x.png", 79 | "scale" : "2x" 80 | }, 81 | { 82 | "size" : "40x40", 83 | "idiom" : "ipad", 84 | "filename" : "Icon-App-40x40@1x.png", 85 | "scale" : "1x" 86 | }, 87 | { 88 | "size" : "40x40", 89 | "idiom" : "ipad", 90 | "filename" : "Icon-App-40x40@2x.png", 91 | "scale" : "2x" 92 | }, 93 | { 94 | "size" : "76x76", 95 | "idiom" : "ipad", 96 | "filename" : "Icon-App-76x76@1x.png", 97 | "scale" : "1x" 98 | }, 99 | { 100 | "size" : "76x76", 101 | "idiom" : "ipad", 102 | "filename" : "Icon-App-76x76@2x.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "83.5x83.5", 107 | "idiom" : "ipad", 108 | "filename" : "Icon-App-83.5x83.5@2x.png", 109 | "scale" : "2x" 110 | }, 111 | { 112 | "size" : "1024x1024", 113 | "idiom" : "ios-marketing", 114 | "filename" : "ItunesArtwork@2x.png", 115 | "scale" : "1x" 116 | } 117 | ], 118 | "info" : { 119 | "version" : 1, 120 | "author" : "xcode" 121 | } 122 | } -------------------------------------------------------------------------------- /Plistor/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ColdGrub1384/Plistor/8cf8451817f0a25f19629b63c0cae34b2a75986d/Plistor/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /Plistor/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ColdGrub1384/Plistor/8cf8451817f0a25f19629b63c0cae34b2a75986d/Plistor/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /Plistor/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ColdGrub1384/Plistor/8cf8451817f0a25f19629b63c0cae34b2a75986d/Plistor/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /Plistor/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ColdGrub1384/Plistor/8cf8451817f0a25f19629b63c0cae34b2a75986d/Plistor/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /Plistor/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ColdGrub1384/Plistor/8cf8451817f0a25f19629b63c0cae34b2a75986d/Plistor/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /Plistor/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ColdGrub1384/Plistor/8cf8451817f0a25f19629b63c0cae34b2a75986d/Plistor/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /Plistor/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ColdGrub1384/Plistor/8cf8451817f0a25f19629b63c0cae34b2a75986d/Plistor/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /Plistor/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ColdGrub1384/Plistor/8cf8451817f0a25f19629b63c0cae34b2a75986d/Plistor/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /Plistor/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ColdGrub1384/Plistor/8cf8451817f0a25f19629b63c0cae34b2a75986d/Plistor/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /Plistor/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ColdGrub1384/Plistor/8cf8451817f0a25f19629b63c0cae34b2a75986d/Plistor/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /Plistor/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ColdGrub1384/Plistor/8cf8451817f0a25f19629b63c0cae34b2a75986d/Plistor/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /Plistor/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ColdGrub1384/Plistor/8cf8451817f0a25f19629b63c0cae34b2a75986d/Plistor/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /Plistor/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ColdGrub1384/Plistor/8cf8451817f0a25f19629b63c0cae34b2a75986d/Plistor/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /Plistor/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ColdGrub1384/Plistor/8cf8451817f0a25f19629b63c0cae34b2a75986d/Plistor/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /Plistor/Assets.xcassets/AppIcon.appiconset/ItunesArtwork@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ColdGrub1384/Plistor/8cf8451817f0a25f19629b63c0cae34b2a75986d/Plistor/Assets.xcassets/AppIcon.appiconset/ItunesArtwork@2x.png -------------------------------------------------------------------------------- /Plistor/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Plistor/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 | -------------------------------------------------------------------------------- /Plistor/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | -------------------------------------------------------------------------------- /Plistor/ConsoleViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ConsoleViewController.swift 3 | // Plistor 4 | // 5 | // Created by Adrian Labbé on 19-12-19. 6 | // Copyright © 2019 Adrian Labbé. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import JavaScriptCore 11 | 12 | /// A JavaScript console for changing values programatically. 13 | class ConsoleViewController: UIViewController { 14 | 15 | /// The text view showing results. 16 | var textView: UITextView! 17 | 18 | /// The text field where the code is typed. 19 | var movableTextField: MovableTextField! 20 | 21 | /// The JavaScript context. 22 | var context: JSContext! { 23 | didSet { 24 | context.setObject(NSNull(), forKeyedSubscript: "console" as NSCopying & NSObjectProtocol) 25 | } 26 | } 27 | 28 | /// The editor from where the view controller is presented. 29 | var editor: DocumentViewController? 30 | 31 | /// The key of the edited value. 32 | var key: String? 33 | 34 | /// Closes the view controller 35 | @objc func done() { 36 | dismiss(animated: true, completion: nil) 37 | } 38 | 39 | /// Returns an escaped description from a JavaScript value. 40 | /// 41 | /// - Parameters: 42 | /// - value: The JavaScript value to descript. 43 | /// 44 | /// - Returns: An escaped description. 45 | func description(value: JSValue) -> String { 46 | let description: String 47 | 48 | if value.isNull { 49 | description = "null" 50 | } else if value.isArray || value.isObject { 51 | description = "\(String(data: (try? JSONSerialization.data(withJSONObject: value.toObject() ?? NSArray(), options: .prettyPrinted)) ?? Data(), encoding: .utf8) ?? "")" 52 | } else if value.isString, let str = value.toString() { 53 | description = "\"\(str.replacingOccurrences(of: "\n", with: "\\n").replacingOccurrences(of: "\"", with: "\\\""))\"" 54 | } else if value.isNumber, let number = value.toNumber() { 55 | description = "\(number)" 56 | } else if value.isBoolean { 57 | description = value.isBoolean ? "true" : "false" 58 | } else if value.isDate, let date = value.toDate() { 59 | description = "\(date)" 60 | } else { 61 | description = "" 62 | } 63 | 64 | return description 65 | } 66 | 67 | // MARK: - View controller 68 | 69 | override func viewDidLoad() { 70 | super.viewDidLoad() 71 | 72 | title = "JavaScript Console" 73 | 74 | edgesForExtendedLayout = [] 75 | 76 | view.backgroundColor = .systemBackground 77 | 78 | textView = UITextView() 79 | textView.isEditable = false 80 | textView.font = UIFont(name: "Menlo", size: UIFont.systemFontSize) 81 | 82 | textView.text = "The 'value' variable will be saved to disk replacing the old value.\n\nvalue = \(description(value: context?.evaluateScript("value") ?? JSValue()))" 83 | 84 | textView.translatesAutoresizingMaskIntoConstraints = false 85 | textView.frame.size.height = view.frame.height 86 | 87 | view.addSubview(textView) 88 | 89 | NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow), name: UIResponder.keyboardWillShowNotification, object: nil) 90 | NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide), name:UIResponder.keyboardWillHideNotification, object: nil) 91 | 92 | movableTextField = MovableTextField(console: self) 93 | movableTextField?.placeholder = "> " 94 | movableTextField?.textField.inputAssistantItem.leadingBarButtonGroups = [] 95 | movableTextField?.textField.inputAssistantItem.trailingBarButtonGroups = [] 96 | 97 | navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(done)) 98 | 99 | context.exceptionHandler = { context, error in 100 | self.textView.text += "\n\(error ?? JSValue())" 101 | } 102 | } 103 | 104 | override func viewDidAppear(_ animated: Bool) { 105 | super.viewDidAppear(animated) 106 | 107 | textView.frame = view.safeAreaLayoutGuide.layoutFrame 108 | textView.frame.size.height -= 44 109 | textView.frame.origin.y = view.safeAreaLayoutGuide.layoutFrame.origin.y 110 | 111 | movableTextField.focus() 112 | } 113 | 114 | override func viewDidDisappear(_ animated: Bool) { 115 | super.viewDidDisappear(animated) 116 | 117 | movableTextField?.toolbar.removeFromSuperview() 118 | movableTextField = nil 119 | } 120 | 121 | override func viewWillAppear(_ animated: Bool) { 122 | super.viewWillAppear(animated) 123 | 124 | navigationController?.navigationBar.isTranslucent = false 125 | 126 | if movableTextField == nil { 127 | movableTextField = MovableTextField(console: self) 128 | movableTextField?.placeholder = "> " 129 | } 130 | movableTextField?.show() 131 | movableTextField?.handler = { text in 132 | self.textView.text += "\n> \(text)" 133 | if let result = self.context.evaluateScript(text) { 134 | self.textView.text += "\n\(self.description(value: result))" 135 | } 136 | 137 | self.movableTextField?.focus() 138 | 139 | guard let value = self.context.evaluateScript("value"), let editor = self.editor else { 140 | return 141 | } 142 | 143 | var newValue: Any = NSNull() 144 | 145 | if value.isNull, editor.document?.fileURL.pathExtension.lowercased() == "json" { 146 | newValue = NSNull() 147 | } else if value.isArray || value.isObject { 148 | if value.isArray { 149 | newValue = value.toArray() ?? NSArray() 150 | } else if value.isObject { 151 | newValue = value.toDictionary() ?? NSDictionary() 152 | } 153 | } else if value.isString, let str = value.toString() { 154 | newValue = str 155 | } else if value.isNumber, let number = value.toNumber() { 156 | newValue = number 157 | } else if value.isBoolean { 158 | newValue = value.toBool() 159 | } else if value.isDate, let date = value.toDate(), editor.document?.fileURL.pathExtension.lowercased() == "plist" { 160 | newValue = date 161 | } else { 162 | newValue = self.description(value: value) 163 | } 164 | 165 | guard let key = self.key else { 166 | editor.element = newValue 167 | editor.tableView.reloadData() 168 | return 169 | } 170 | 171 | if let dict = editor.element as? NSDictionary { 172 | let mutable = NSMutableDictionary(dictionary: dict) 173 | mutable[key] = newValue 174 | editor.element = mutable 175 | } else if let arr = editor.element as? NSArray, let i = Int(key) { 176 | let mutable = NSMutableArray(array: arr) 177 | mutable.removeObject(at: i) 178 | mutable.insert(newValue, at: i) 179 | editor.element = mutable 180 | } 181 | 182 | editor.tableView.reloadData() 183 | } 184 | } 185 | 186 | override func viewDidLayoutSubviews() { 187 | super.viewDidLayoutSubviews() 188 | 189 | let wasFirstResponder = movableTextField?.textField.isFirstResponder ?? false 190 | movableTextField?.textField.resignFirstResponder() 191 | movableTextField?.toolbar.frame.size.width = view.safeAreaLayoutGuide.layoutFrame.width 192 | movableTextField?.toolbar.frame.origin.x = view.safeAreaInsets.left 193 | textView.frame = view.safeAreaLayoutGuide.layoutFrame 194 | textView.frame.size.height = view.safeAreaLayoutGuide.layoutFrame.height-44 195 | textView.frame.origin.y = view.safeAreaLayoutGuide.layoutFrame.origin.y 196 | if wasFirstResponder { 197 | movableTextField?.textField.becomeFirstResponder() 198 | } 199 | movableTextField?.toolbar.isHidden = (view.frame.size.height == 0) 200 | } 201 | 202 | // MARK: - Keyboard 203 | 204 | @objc func keyboardWillShow(_ notification:Notification) { 205 | if parent?.parent?.modalPresentationStyle != .popover || parent?.parent?.view.frame.width != parent?.parent?.preferredContentSize.width { 206 | let d = notification.userInfo! 207 | let r = d[UIResponder.keyboardFrameEndUserInfoKey] as! CGRect 208 | 209 | let point = (view.window)?.convert(r.origin, to: view) ?? r.origin 210 | 211 | textView.frame.size.height = point.y-44 212 | 213 | #if APP_EXTENSION 214 | if UIDevice.current.userInterfaceIdiom == .pad { 215 | textView.frame.size.height -= 22 216 | } 217 | #endif 218 | } else { 219 | textView.frame.size.height = view.safeAreaLayoutGuide.layoutFrame.height-44 220 | } 221 | 222 | textView.frame.origin.y = view.safeAreaLayoutGuide.layoutFrame.origin.y 223 | 224 | textView.scrollToBottom() 225 | 226 | movableTextField?.placeholder = "> " 227 | } 228 | 229 | @objc func keyboardWillHide(_ notification:Notification) { 230 | textView.frame.size.height = view.safeAreaLayoutGuide.layoutFrame.height-44 231 | textView.frame.origin.y = view.safeAreaLayoutGuide.layoutFrame.origin.y 232 | } 233 | } 234 | -------------------------------------------------------------------------------- /Plistor/Document.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Document.swift 3 | // Plistor 4 | // 5 | // Created by Adrian Labbé on 13-12-19. 6 | // Copyright © 2019 Adrian Labbé. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | /// A Plist or JSON document. 12 | class Document: UIDocument { 13 | 14 | /// The top item. Can be a dictionnary or an array. 15 | var propertyList: Any = [String:Any]() 16 | 17 | /// The error while saving the document. 18 | var error: Error? 19 | 20 | // MARK: - Document 21 | 22 | override func contents(forType typeName: String) throws -> Any { 23 | // Encode your document with an instance of NSData or NSFileWrapper 24 | if typeName.contains("json"), propertyList is NSDictionary || propertyList is NSArray { 25 | return try JSONSerialization.data(withJSONObject: propertyList, options: JSONSerialization.WritingOptions.prettyPrinted) 26 | } else if typeName.contains("property-list") { 27 | 28 | let url = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent("tmp.plist") 29 | 30 | if let propertyListDict = propertyList as? [String:Any] { 31 | let dict = NSDictionary(dictionary: propertyListDict) 32 | try dict.write(to: url) 33 | } else if let propertyListArray = propertyList as? [Any] { 34 | let arr = NSArray(array: propertyListArray) 35 | try arr.write(to: url) 36 | } 37 | 38 | return try Data(contentsOf: url) 39 | } else { 40 | return Data() 41 | } 42 | } 43 | 44 | override func load(fromContents contents: Any, ofType typeName: String?) throws { 45 | if fileURL.pathExtension.lowercased() == "plist" { 46 | propertyList = (NSDictionary(contentsOfFile: fileURL.path) ?? NSArray(contentsOf: fileURL)) ?? [String:Any]() 47 | } else if fileURL.pathExtension.lowercased() == "json" { 48 | let data = try Data(contentsOf: fileURL) 49 | propertyList = try JSONSerialization.jsonObject(with: data, options: .allowFragments) 50 | } 51 | } 52 | 53 | override func handleError(_ error: Error, userInteractionPermitted: Bool) { 54 | super.handleError(error, userInteractionPermitted: userInteractionPermitted) 55 | 56 | self.error = error 57 | } 58 | 59 | override func presentedItemDidChange() { 60 | super.presentedItemDidChange() 61 | 62 | #if !APP_EXTENSION 63 | do { 64 | if fileURL.pathExtension.lowercased() == "plist" { 65 | propertyList = (NSDictionary(contentsOfFile: fileURL.path) ?? NSArray(contentsOf: fileURL)) ?? [String:Any]() 66 | } else if fileURL.pathExtension.lowercased() == "json" { 67 | let data = try Data(contentsOf: fileURL) 68 | propertyList = try JSONSerialization.jsonObject(with: data, options: .allowFragments) 69 | } 70 | 71 | DispatchQueue.main.async { 72 | for scence in UIApplication.shared.connectedScenes { 73 | let vcs = ((scence as? UIWindowScene)?.windows.first?.rootViewController?.presentedViewController as? UINavigationController)?.viewControllers ?? [] 74 | for vc in vcs { 75 | if let vc = vc as? DocumentViewController { 76 | 77 | guard vc.document?.fileURL == self.fileURL else { 78 | continue 79 | } 80 | 81 | guard vc.syncsElement else { 82 | vc.syncsElement = true 83 | continue 84 | } 85 | 86 | guard let key = vc.key else { 87 | vc.save = false 88 | vc.element = self.propertyList 89 | vc.tableView.reloadData() 90 | continue 91 | } 92 | 93 | vc.syncsElement = false 94 | if let arr = vc.parentElement?.element as? NSArray, let i = Int(key), arr.count > i { 95 | if let item = arr[i] as? NSArray, item != (vc.element as? NSArray) { 96 | vc.element = arr[i] 97 | } else if let item = arr[i] as? NSDictionary, item != (vc.element as? NSDictionary) { 98 | vc.element = arr[i] 99 | } 100 | } else if let dict = vc.parentElement?.element as? NSDictionary { 101 | if let item = dict[key] as? NSArray, item != (vc.element as? NSArray) { 102 | vc.element = dict[key] ?? vc.element 103 | } else if let item = dict[key] as? NSDictionary, item != (vc.element as? NSDictionary) { 104 | vc.element = dict[key] ?? vc.element 105 | } 106 | } else { 107 | vc.element = self.propertyList 108 | } 109 | vc.tableView.reloadData() 110 | vc.syncsElement = true 111 | } 112 | } 113 | } 114 | } 115 | } catch { 116 | print(error.localizedDescription) 117 | } 118 | #endif 119 | } 120 | } 121 | 122 | -------------------------------------------------------------------------------- /Plistor/DocumentBrowserViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DocumentBrowserViewController.swift 3 | // Plistor 4 | // 5 | // Created by Adrian Labbé on 13-12-19. 6 | // Copyright © 2019 Adrian Labbé. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | #if APP_EXTENSION 11 | import MobileCoreServices 12 | #endif 13 | 14 | /// The main document browser view controller. 15 | class DocumentBrowserViewController: UIDocumentBrowserViewController, UIDocumentBrowserViewControllerDelegate, UIViewControllerTransitioningDelegate { 16 | 17 | /// The URL of the document to open. 18 | var documentURL: URL? 19 | 20 | // MARK: - Document browser view controller 21 | 22 | override func viewDidLoad() { 23 | super.viewDidLoad() 24 | 25 | delegate = self 26 | 27 | allowsDocumentCreation = true 28 | allowsPickingMultipleItems = false 29 | } 30 | 31 | override func viewDidAppear(_ animated: Bool) { 32 | super.viewDidAppear(animated) 33 | 34 | #if APP_EXTENSION 35 | let extensionItem = extensionContext?.inputItems.first as? NSExtensionItem 36 | let itemProvider = extensionItem?.attachments?.first 37 | let propertyList = String(kUTTypePropertyList) 38 | if itemProvider?.hasItemConformingToTypeIdentifier(propertyList) == true { 39 | itemProvider?.loadItem(forTypeIdentifier: propertyList, options: nil, completionHandler: { (item, error) -> Void in 40 | let dictionary = item as? NSDictionary 41 | OperationQueue.main.addOperation { 42 | let results = dictionary?[NSExtensionJavaScriptPreprocessingResultsKey] as? NSDictionary 43 | 44 | let title = (results?["name"] as? String) ?? "Property List" 45 | 46 | if let content = results?["content"] as? String { 47 | if (try? JSONSerialization.jsonObject(with: content.data(using: .utf8) ?? Data(), options: [])) != nil { 48 | var tmpURL = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent(title) 49 | if NSString(string: title).pathExtension.lowercased() != "json" { 50 | tmpURL = tmpURL.appendingPathExtension("json") 51 | } 52 | try? content.write(to: tmpURL, atomically: true, encoding: .utf8) 53 | self.presentDocument(at: tmpURL) 54 | } else { 55 | var tmpURL = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent(title) 56 | if NSString(string: title).pathExtension.lowercased() != "plist" { 57 | tmpURL = tmpURL.appendingPathExtension("plist") 58 | } 59 | try? content.write(to: tmpURL, atomically: true, encoding: .utf8) 60 | self.presentDocument(at: tmpURL) 61 | } 62 | } 63 | 64 | } 65 | }) 66 | } 67 | #endif 68 | 69 | if let doc = documentURL { 70 | documentURL = nil 71 | presentDocument(at: doc) 72 | } 73 | } 74 | 75 | // MARK: Document browser view controller delegate 76 | 77 | func documentBrowser(_ controller: UIDocumentBrowserViewController, didRequestDocumentCreationWithHandler importHandler: @escaping (URL?, UIDocumentBrowserViewController.ImportMode) -> Void) { 78 | 79 | 80 | let alert = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet) 81 | alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: { (_) in 82 | importHandler(nil, .none) 83 | })) 84 | 85 | alert.addAction(UIAlertAction(title: "JSON", style: .default, handler: { (_) in 86 | let newDocumentURL = Bundle.main.url(forResource: "Property List", withExtension: "json") 87 | 88 | if newDocumentURL != nil { 89 | importHandler(newDocumentURL, .copy) 90 | } else { 91 | importHandler(nil, .none) 92 | } 93 | })) 94 | 95 | alert.addAction(UIAlertAction(title: "Plist", style: .default, handler: { (_) in 96 | let newDocumentURL = Bundle.main.url(forResource: "Property List", withExtension: "plist") 97 | 98 | if newDocumentURL != nil { 99 | importHandler(newDocumentURL, .copy) 100 | } else { 101 | importHandler(nil, .none) 102 | } 103 | })) 104 | 105 | alert.popoverPresentationController?.sourceView = view 106 | alert.popoverPresentationController?.sourceRect = CGRect(x: view.frame.width/2, y: view.frame.height/2, width: 1, height: 1) 107 | 108 | present(alert, animated: true, completion: nil) 109 | } 110 | 111 | func documentBrowser(_ controller: UIDocumentBrowserViewController, didPickDocumentsAt documentURLs: [URL]) { 112 | guard let sourceURL = documentURLs.first else { return } 113 | 114 | // Present the Document View Controller for the first document that was picked. 115 | // If you support picking multiple items, make sure you handle them all. 116 | presentDocument(at: sourceURL) 117 | } 118 | 119 | func documentBrowser(_ controller: UIDocumentBrowserViewController, didImportDocumentAt sourceURL: URL, toDestinationURL destinationURL: URL) { 120 | // Present the Document View Controller for the new newly created document 121 | presentDocument(at: destinationURL) 122 | } 123 | 124 | func documentBrowser(_ controller: UIDocumentBrowserViewController, failedToImportDocumentAt documentURL: URL, error: Error?) { 125 | // Make sure to handle the failed import appropriately, e.g., by presenting an error message to the user. 126 | } 127 | 128 | // MARK: Document Presentation 129 | 130 | /// Edits the given document. 131 | /// 132 | /// - Parameters: 133 | /// - documentURL: The URL of a Plist or JSON document. 134 | func presentDocument(at documentURL: URL) { 135 | 136 | let doc = Document(fileURL: documentURL) 137 | 138 | let storyBoard = UIStoryboard(name: "Main", bundle: nil) 139 | let navVC = storyBoard.instantiateViewController(withIdentifier: "editor") as! UINavigationController 140 | let documentViewController = navVC.viewControllers.first as! DocumentViewController 141 | documentViewController.document = doc 142 | navVC.modalPresentationStyle = .fullScreen 143 | 144 | #if !APP_EXTENSION 145 | transitionController = transitionController(forDocumentAt: documentURL) 146 | transitionController?.loadingProgress = doc.progress 147 | transitionController?.targetView = navVC.view 148 | 149 | navVC.transitioningDelegate = self 150 | #endif 151 | 152 | present(navVC, animated: true, completion: nil) 153 | } 154 | 155 | // MARK: - Animation 156 | 157 | /// Transition controller for presenting and dismissing View controllers. 158 | var transitionController: UIDocumentBrowserTransitionController? 159 | 160 | // MARK: - View controller transition delegate 161 | 162 | func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? { 163 | return transitionController 164 | } 165 | 166 | func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? { 167 | return transitionController 168 | } 169 | } 170 | 171 | -------------------------------------------------------------------------------- /Plistor/DocumentViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DocumentViewController.swift 3 | // Plistor 4 | // 5 | // Created by Adrian Labbé on 13-12-19. 6 | // Copyright © 2019 Adrian Labbé. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import JavaScriptCore 11 | 12 | /// The Plist / JSON editor. 13 | class DocumentViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, UITextViewDelegate, UIContextMenuInteractionDelegate { 14 | 15 | /// The color for representing String values. 16 | static let stringColor = UIColor.systemRed 17 | 18 | /// The color for representing Number values. 19 | static let numberColor = UIColor.systemPurple 20 | 21 | /// The color for representing Boolean values. 22 | static let boolColor = UIColor.systemGreen 23 | 24 | /// The color for representing Data values. 25 | static let dataColor = UIColor.systemTeal 26 | 27 | /// The color for representing Date values. 28 | static let dateColor = UIColor.systemYellow 29 | 30 | /// The color for representing Dictionary values. 31 | static let dictColor = UIColor.systemPink 32 | 33 | /// The color for representing Array values. 34 | static let arrayColor = UIColor.systemIndigo 35 | 36 | private func isBoolNumber(num:NSNumber) -> Bool { 37 | let boolID = CFBooleanGetTypeID() // the type ID of CFBoolean 38 | let numID = CFGetTypeID(num) // the type ID of num 39 | return numID == boolID 40 | } 41 | 42 | private func color(for element: Any) -> UIColor { 43 | if element is NSString { 44 | return DocumentViewController.stringColor 45 | } else if let num = element as? NSNumber, isBoolNumber(num: num) { 46 | return DocumentViewController.boolColor 47 | } else if element is NSNumber { 48 | return DocumentViewController.numberColor 49 | } else if element is NSDate { 50 | return DocumentViewController.dateColor 51 | } else if element is NSData { 52 | return DocumentViewController.dataColor 53 | } else if element is NSDictionary { 54 | return DocumentViewController.dictColor 55 | } else if element is NSArray { 56 | return DocumentViewController.arrayColor 57 | } else { 58 | return .label 59 | } 60 | } 61 | 62 | private func type(of element: Any) -> String { 63 | if element is NSString { 64 | return "String" 65 | } else if let num = element as? NSNumber, isBoolNumber(num: num) { 66 | return "Boolean" 67 | } else if element is NSNumber { 68 | return "Number" 69 | } else if element is NSDate { 70 | return "Date" 71 | } else if element is NSData { 72 | return "Data" 73 | } else if element is NSDictionary { 74 | return "Dictionary" 75 | } else if element is NSArray { 76 | return "Array" 77 | } else { 78 | return "Null" 79 | } 80 | } 81 | 82 | /// The Plist or JSON document. 83 | var document: Document? 84 | 85 | /// The key for current value. 86 | var key: String? 87 | 88 | /// A boolean indicating whether the file should be saved after setting `element`. 89 | var save = true 90 | 91 | /// A boolean indicating whether the root element should be synced. 92 | var syncsElement = true 93 | 94 | /// The object represented by the editor. May not be the root object, 95 | var element: Any = [String:Any]() { 96 | didSet { 97 | 98 | let pathExtension = document?.fileURL.pathExtension.lowercased() 99 | if pathExtension == "json", element is NSDictionary || element is NSArray { 100 | if let data = try? JSONSerialization.data(withJSONObject: element, options: JSONSerialization.WritingOptions.prettyPrinted), let str = String(data: data, encoding: .utf8) { 101 | textView.text = str 102 | } else { 103 | textView.text = "" 104 | } 105 | } else if pathExtension == "plist" { 106 | 107 | let url = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent("tmp.plist") 108 | 109 | if let propertyListDict = element as? [String:Any] { 110 | let dict = NSDictionary(dictionary: propertyListDict) 111 | try? dict.write(to: url) 112 | } else if let propertyListArray = element as? [Any] { 113 | let arr = NSArray(array: propertyListArray) 114 | try? arr.write(to: url) 115 | } 116 | 117 | textView.text = (try? String(contentsOf: url)) ?? "" 118 | } 119 | 120 | guard save else { 121 | save = true 122 | return 123 | } 124 | 125 | guard let key = key else { 126 | 127 | guard let doc = document else { 128 | return 129 | } 130 | 131 | guard title?.isEmpty == false else { 132 | return 133 | } 134 | 135 | doc.propertyList = element 136 | doc.save(to: doc.fileURL, for: .forOverwriting) { (success) in 137 | if !success { 138 | let alert = UIAlertController(title: "Error writing to file", message: nil, preferredStyle: .alert) 139 | alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil)) 140 | self.present(alert, animated: true, completion: nil) 141 | } 142 | } 143 | 144 | return 145 | } 146 | 147 | if let _dict = parentElement?.element as? [String:Any] { 148 | let dict = NSMutableDictionary(dictionary: _dict) 149 | dict[key] = element 150 | parentElement?.element = dict 151 | } else if let i = Int(key), let _arr = parentElement?.element as? [Any] { 152 | let arr = NSMutableArray(array: _arr) 153 | arr[i] = element 154 | parentElement?.element = arr 155 | } 156 | } 157 | } 158 | 159 | /// The parent editor that presented the current editor. This editor contains the parent element of the current value. 160 | var parentElement: DocumentViewController? 161 | 162 | private var isDocOpen = false 163 | 164 | /// Updates the display mode. 165 | func checkDisplayMode() { 166 | if traitCollection.horizontalSizeClass == .compact { 167 | if mode == 0 { 168 | tableView.isHidden = false 169 | accessoryView.isHidden = true 170 | } else { 171 | tableView.isHidden = true 172 | accessoryView.isHidden = false 173 | } 174 | } else { 175 | tableView.isHidden = false 176 | accessoryView.isHidden = false 177 | segmentedControl.selectedSegmentIndex = 0 178 | } 179 | } 180 | 181 | /// Dismisses the editor. 182 | @IBAction func dismissDocumentViewController() { 183 | 184 | #if APP_EXTENSION 185 | let alert = UIAlertController(title: "Save file", message: "Do you want to save this file?", preferredStyle: .alert) 186 | alert.addAction(UIAlertAction(title: "Save", style: .default, handler: { (_) in 187 | let picker = UIDocumentPickerViewController(url: self.document?.fileURL ?? URL(fileURLWithPath: "/"), in: .exportToService) 188 | picker.delegate = self 189 | self.present(picker, animated: true, completion: nil) 190 | })) 191 | alert.addAction(UIAlertAction(title: "Don't save", style: .destructive, handler: { (_) in 192 | self.view.window?.rootViewController?.children.first?.extensionContext?.completeRequest(returningItems: nil, completionHandler: nil) 193 | })) 194 | alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil)) 195 | present(alert, animated: true, completion: nil) 196 | #else 197 | dismiss(animated: true) { 198 | self.document?.close(completionHandler: nil) 199 | } 200 | #endif 201 | } 202 | 203 | /// Adds an item to the current Dictionary or Array. 204 | @IBAction func addItem(_ sender: UIBarButtonItem) { 205 | let types = ["Dictionary", "Array", "String", "Number", "Boolean"]+(document?.fileURL.pathExtension.lowercased() == "plist" ? ["Data", "Date"] : ["Null"]) 206 | 207 | let alert = UIAlertController(title: "New item", message: "Select the new item's type", preferredStyle: .actionSheet) 208 | 209 | for type in types { 210 | alert.addAction(UIAlertAction(title: type, style: .default, handler: { (_) in 211 | 212 | func setValue(_ value: Any, forKey key: String? = nil) { 213 | if let arr = self.element as? NSArray { 214 | let mutable = NSMutableArray(array: arr) 215 | mutable.add(value) 216 | self.element = mutable 217 | } else if let key = key, let dict = self.element as? NSDictionary { 218 | let mutable = NSMutableDictionary(dictionary: dict) 219 | mutable[key] = value 220 | self.element = mutable 221 | } 222 | self.tableView.reloadData() 223 | } 224 | 225 | if self.element is NSDictionary { 226 | let keyAlert = UIAlertController(title: "New item", message: "Type the new item's key", preferredStyle: .alert) 227 | 228 | var textField: UITextField? 229 | 230 | keyAlert.addTextField { (_textField) in 231 | _textField.placeholder = "New key" 232 | textField = _textField 233 | } 234 | 235 | keyAlert.addAction(UIAlertAction(title: "Confirm", style: .default, handler: { (_) in 236 | if let newKey = textField?.text, !newKey.isEmpty { 237 | 238 | func _setValue(_ value: Any) { 239 | setValue(value, forKey: newKey) 240 | } 241 | 242 | switch type { 243 | case "Dictionary": 244 | _setValue(NSDictionary()) 245 | case "Array": 246 | _setValue(NSArray()) 247 | case "String": 248 | _setValue(NSString()) 249 | case "Number": 250 | _setValue(NSNumber(0)) 251 | case "Boolean": 252 | _setValue(Bool()) 253 | case "Data": 254 | _setValue(NSData()) 255 | case "Date": 256 | _setValue(NSDate()) 257 | case "Null": 258 | _setValue(NSNull()) 259 | default: 260 | break 261 | } 262 | 263 | } else { 264 | self.addItem(sender) 265 | } 266 | })) 267 | 268 | keyAlert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: { (_) in 269 | self.addItem(sender) 270 | })) 271 | 272 | self.present(keyAlert, animated: true, completion: nil) 273 | } else { 274 | switch type { 275 | case "Dictionary": 276 | setValue(NSDictionary()) 277 | case "Array": 278 | setValue(NSArray()) 279 | case "String": 280 | setValue(NSString()) 281 | case "Number": 282 | setValue(NSNumber(0)) 283 | case "Boolean": 284 | setValue(Bool()) 285 | case "Data": 286 | setValue(Data()) 287 | case "Date": 288 | setValue(Date()) 289 | case "Null": 290 | setValue(NSNull()) 291 | default: 292 | break 293 | } 294 | } 295 | })) 296 | } 297 | 298 | alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil)) 299 | 300 | alert.popoverPresentationController?.barButtonItem = sender 301 | 302 | present(alert, animated: true, completion: nil) 303 | } 304 | 305 | /// The table view containing values. 306 | @IBOutlet weak var tableView: UITableView! 307 | 308 | /// The Text View containing source code. 309 | var textView: UITextView! 310 | 311 | /// A view displayed at right or from a segmented control on compact views. 312 | @IBOutlet weak var accessoryView: UIView! 313 | 314 | /// A view for switching from Property List mode and Source Code mode on compact views. 315 | @IBOutlet weak var segmentedControl: UISegmentedControl! 316 | 317 | /// Switches from Property List mode and Source Code mode. 318 | @IBAction func switchMode(_ sender: Any) { 319 | if mode == 0 { 320 | tableView.isHidden = false 321 | accessoryView.isHidden = true 322 | } else { 323 | tableView.isHidden = true 324 | accessoryView.isHidden = false 325 | } 326 | } 327 | 328 | /// Inspects the current element with JavaScript. 329 | @IBAction func inspectWithJS() { 330 | 331 | guard let context = JSContext() else { 332 | return 333 | } 334 | context.setObject(element, forKeyedSubscript: "value" as NSCopying & NSObjectProtocol) 335 | 336 | let console = ConsoleViewController() 337 | console.context = context 338 | console.editor = self 339 | let navVC = UINavigationController(rootViewController: console) 340 | navVC.modalPresentationStyle = .fullScreen 341 | self.present(navVC, animated: true, completion: nil) 342 | } 343 | 344 | private var mode: Int { 345 | return segmentedControl.selectedSegmentIndex 346 | } 347 | 348 | // MARK: - Document view controller 349 | 350 | override func setEditing(_ editing: Bool, animated: Bool) { 351 | super.setEditing(editing, animated: true) 352 | tableView.setEditing(!tableView.isEditing, animated: true) 353 | } 354 | 355 | override func viewDidLoad() { 356 | super.viewDidLoad() 357 | 358 | navigationItem.rightBarButtonItems?.append(editButtonItem) 359 | navigationItem.leftItemsSupplementBackButton = true 360 | 361 | NotificationCenter.default.addObserver(self, selector: #selector(keyboardDidShow), name: UIResponder.keyboardDidShowNotification, object: nil) 362 | NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide), name: UIResponder.keyboardWillHideNotification, object: nil) 363 | 364 | textView = UITextView(frame: accessoryView.bounds) 365 | textView.delegate = self 366 | textView.autocapitalizationType = .none 367 | textView.autocorrectionType = .no 368 | textView.smartDashesType = .no 369 | textView.smartQuotesType = .no 370 | textView.font = UIFont(name: "Menlo", size: UIFont.smallSystemFontSize) 371 | textView.autoresizingMask = [.flexibleWidth, .flexibleHeight] 372 | accessoryView.addSubview(textView) 373 | 374 | let toolbar = UIToolbar(frame: CGRect(x: 0, y: 0, width: 0, height: 44)) 375 | toolbar.items = [ 376 | UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil), 377 | UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(dismissKeyboard)) 378 | ] 379 | textView.inputAccessoryView = toolbar 380 | } 381 | 382 | override func viewWillAppear(_ animated: Bool) { 383 | super.viewWillAppear(animated) 384 | 385 | // Access the document 386 | if !isDocOpen { 387 | document?.open(completionHandler: { (success) in 388 | 389 | self.isDocOpen = true 390 | 391 | if success { 392 | // Display the content of the document, e.g.: 393 | 394 | if self.key == nil { 395 | self.element = self.document?.propertyList ?? [String:Any]() 396 | } 397 | 398 | self.title = self.document?.fileURL.lastPathComponent 399 | 400 | self.tableView.reloadData() 401 | } else { 402 | let alert = UIAlertController(title: "Error opening file", message: self.document?.error?.localizedDescription, preferredStyle: .alert) 403 | alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: { (_) in 404 | self.dismiss(animated: true, completion: nil) 405 | })) 406 | self.present(alert, animated: true, completion: nil) 407 | } 408 | }) 409 | } else { 410 | title = document?.fileURL.lastPathComponent 411 | self.tableView.reloadData() 412 | } 413 | 414 | checkDisplayMode() 415 | } 416 | 417 | override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { 418 | super.traitCollectionDidChange(previousTraitCollection) 419 | 420 | checkDisplayMode() 421 | } 422 | 423 | // MARK: - Keyboard 424 | 425 | /// Dismisses keyboard from text view. 426 | @objc func dismissKeyboard() { 427 | textView.resignFirstResponder() 428 | } 429 | 430 | /// Resizes `textView`. 431 | @objc func keyboardDidShow(_ notification:Notification) { 432 | let d = notification.userInfo! 433 | let r = d[UIResponder.keyboardFrameEndUserInfoKey] as! CGRect 434 | let point = (view.window)?.convert(r.origin, to: textView) ?? r.origin 435 | 436 | textView.contentInset.bottom = (point.y >= textView.frame.height ? 0 : textView.frame.height-point.y) 437 | textView.verticalScrollIndicatorInsets.bottom = textView.contentInset.bottom 438 | } 439 | 440 | /// Set `textView` to the default size. 441 | @objc func keyboardWillHide(_ notification:Notification) { 442 | textView.contentInset = .zero 443 | textView.scrollIndicatorInsets = .zero 444 | } 445 | 446 | // MARK: - Table view data source 447 | 448 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 449 | return ((element as? NSDictionary)?.count ?? (element as? NSArray)?.count ?? -1)+1 450 | } 451 | 452 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 453 | 454 | guard indexPath.row > 0 else { 455 | let cell = UITableViewCell(style: .value1, reuseIdentifier: nil) 456 | 457 | let attrString = NSMutableAttributedString(string: key ?? "Root", attributes: [.font : cell.detailTextLabel?.font ?? UIFont.systemFont(ofSize: UIFont.systemFontSize), .foregroundColor : UIColor.label]) 458 | attrString.append(NSAttributedString(string: " \(type(of: element))", attributes: [.font : UIFont.boldSystemFont(ofSize: UIFont.systemFontSize), .foregroundColor : color(for: element)])) 459 | 460 | cell.textLabel?.attributedText = attrString 461 | cell.detailTextLabel?.text = "▿" 462 | 463 | cell.addInteraction(UIContextMenuInteraction(delegate: self)) 464 | return cell 465 | } 466 | 467 | if let element = element as? NSDictionary { 468 | guard let key = (element.allKeys as? [String])?[indexPath.row-1] else { 469 | return UITableViewCell() 470 | } 471 | 472 | let cell = UITableViewCell(style: .value1, reuseIdentifier: nil) 473 | 474 | 475 | let attrString = NSMutableAttributedString(string: " "+key, attributes: [.font : cell.detailTextLabel?.font ?? UIFont.systemFont(ofSize: UIFont.systemFontSize), .foregroundColor : UIColor.label]) 476 | attrString.append(NSAttributedString(string: " \(type(of: element[key] ?? NSObject()))", attributes: [.font : UIFont.boldSystemFont(ofSize: UIFont.systemFontSize), .foregroundColor : color(for: element[key] ?? NSObject())])) 477 | 478 | cell.textLabel?.attributedText = attrString 479 | 480 | if element[key] is NSArray || element[key] is NSDictionary { 481 | cell.accessoryType = .disclosureIndicator 482 | } else { 483 | cell.accessoryType = .none 484 | if let bool = element[key] as? NSNumber, isBoolNumber(num: bool) { 485 | cell.detailTextLabel?.text = "\(bool.boolValue ? "YES" : "NO")" 486 | } else { 487 | cell.detailTextLabel?.text = "\(element[key] ?? "")" 488 | } 489 | } 490 | 491 | cell.addInteraction(UIContextMenuInteraction(delegate: self)) 492 | return cell 493 | } else if let element = element as? NSArray { 494 | let cell = UITableViewCell(style: .value1, reuseIdentifier: nil) 495 | 496 | let attrString = NSMutableAttributedString(string: " \(indexPath.row-1)", attributes: [.font : cell.detailTextLabel?.font ?? UIFont.systemFont(ofSize: UIFont.systemFontSize), .foregroundColor : UIColor.label]) 497 | attrString.append(NSAttributedString(string: " \(type(of: element[indexPath.row-1]))", attributes: [.font : UIFont.boldSystemFont(ofSize: UIFont.systemFontSize), .foregroundColor : color(for: element[indexPath.row-1])])) 498 | 499 | cell.textLabel?.attributedText = attrString 500 | 501 | if element[indexPath.row-1] is NSArray || element[indexPath.row-1] is NSDictionary { 502 | cell.accessoryType = .disclosureIndicator 503 | } else { 504 | cell.accessoryType = .none 505 | if let bool = element[indexPath.row-1] as? NSNumber, isBoolNumber(num: bool) { 506 | cell.detailTextLabel?.text = "\(bool.boolValue ? "YES" : "NO")" 507 | } else { 508 | cell.detailTextLabel?.text = "\(element[indexPath.row-1])" 509 | } 510 | } 511 | 512 | cell.addInteraction(UIContextMenuInteraction(delegate: self)) 513 | return cell 514 | } else { 515 | return UITableViewCell() 516 | } 517 | } 518 | 519 | func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool { 520 | return indexPath.row != 0 521 | } 522 | 523 | func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) { 524 | if editingStyle == .delete { 525 | if let arr = element as? NSArray { 526 | let mutable = NSMutableArray(array: arr) 527 | mutable.removeObject(at: indexPath.row-1) 528 | self.element = mutable 529 | } else if let dict = element as? NSDictionary, let key = dict.allKeys[indexPath.row-1] as? String { 530 | let mutable = NSMutableDictionary(dictionary: dict) 531 | mutable.removeObject(forKey: key) 532 | self.element = mutable 533 | } 534 | 535 | tableView.reloadData() 536 | } 537 | } 538 | 539 | func tableView(_ tableView: UITableView, canMoveRowAt indexPath: IndexPath) -> Bool { 540 | return element is NSArray && indexPath.row != 0 541 | } 542 | 543 | func tableView(_ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) { 544 | if let arr = element as? NSArray { 545 | let mutable = NSMutableArray(array: arr) 546 | let item = mutable[sourceIndexPath.row-1] 547 | mutable.removeObject(at: sourceIndexPath.row-1) 548 | mutable.insert(item, at: destinationIndexPath.row-1) 549 | element = mutable 550 | } 551 | } 552 | 553 | // MARK: - Table view delegate 554 | 555 | func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 556 | 557 | let isPresentedFromContextMenu = tableView.indexPathForSelectedRow == nil // If there is no selected row, this function is called manually 558 | tableView.deselectRow(at: indexPath, animated: true) 559 | 560 | var key: String? 561 | 562 | if indexPath.row != 0 { 563 | key = (element as? NSDictionary)?.allKeys[indexPath.row-1] as? String 564 | } else if element is NSArray && indexPath.row != 0 { 565 | key = nil 566 | } else { 567 | key = self.key ?? "Root" 568 | } 569 | 570 | var _value: Any? 571 | 572 | if let key = key, let element = self.element as? [String:Any] { 573 | _value = element[key] 574 | } else if let element = self.element as? [Any], indexPath.row != 0 { 575 | _value = element[indexPath.row-1] 576 | } else { 577 | _value = nil 578 | } 579 | 580 | func showSheet() { 581 | 582 | if indexPath.row != 0 { 583 | key = (element as? NSDictionary)?.allKeys[indexPath.row-1] as? String 584 | } else if element is NSArray && indexPath.row != 0 { 585 | key = nil 586 | } else { 587 | key = self.key ?? "Root" 588 | } 589 | 590 | if let key = key, let element = self.element as? [String:Any] { 591 | _value = element[key] 592 | } else if let element = self.element as? [Any], indexPath.row != 0 { 593 | _value = element[indexPath.row-1] 594 | } else { 595 | _value = nil 596 | } 597 | 598 | let alert = UIAlertController(title: key ?? "\(indexPath.row-1)", message: nil, preferredStyle: .actionSheet) 599 | 600 | func setValue(_ value: Any) { 601 | if let key = key, let dict = self.element as? [String:Any] { 602 | let nsDict = NSMutableDictionary(dictionary: dict) 603 | nsDict[key] = value 604 | self.element = nsDict 605 | } else if let arr = self.element as? [Any] { 606 | let nsArr = NSMutableArray(array: arr) 607 | nsArr.removeObject(at: indexPath.row-1) 608 | nsArr.insert(value, at: indexPath.row-1) 609 | self.element = nsArr 610 | } 611 | 612 | tableView.reloadData() 613 | } 614 | 615 | func selectType() { 616 | 617 | let value: Any 618 | if indexPath.row != 0 { 619 | if _value == nil { 620 | return 621 | } 622 | value = _value! 623 | } else { 624 | value = element 625 | } 626 | 627 | let typesAlert = UIAlertController(title: "Select a type", message: nil, preferredStyle: .actionSheet) 628 | 629 | let types = indexPath.row == 0 ? ["Dictionary", "Array"] : ["String", "Number", "Boolean"]+(self.document?.fileURL.pathExtension.lowercased() == "plist" ? ["Data", "Date"] : ["Null"]) 630 | 631 | for type in types { 632 | typesAlert.addAction(UIAlertAction(title: type, style: .default, handler: { (_) in 633 | 634 | switch type { 635 | case "String": 636 | setValue("\(value)") 637 | case "Number": 638 | setValue(NSNumber(value: Float("\(value)") ?? 0)) 639 | case "Boolean": 640 | setValue(Bool(truncating: NSNumber(value: Float("\(value)") ?? 0))) 641 | case "Data": 642 | setValue("\(value)".data(using: .utf8) ?? Data()) 643 | case "Date": 644 | setValue(Date()) 645 | case "Null": 646 | setValue(NSNull()) 647 | case "Dictionary": 648 | if let arr = self.element as? NSArray { 649 | let dict = NSMutableDictionary() 650 | 651 | var i = 0 652 | for obj in arr { 653 | dict["\(i)"] = obj 654 | i += 1 655 | } 656 | 657 | self.element = dict 658 | 659 | tableView.reloadData() 660 | } 661 | 662 | return 663 | case "Array": 664 | if let dict = self.element as? NSDictionary { 665 | 666 | let arr = NSMutableArray() 667 | 668 | for (_, value) in dict { 669 | arr.add(value) 670 | } 671 | 672 | self.element = arr 673 | 674 | tableView.reloadData() 675 | 676 | return 677 | } 678 | default: 679 | break 680 | } 681 | 682 | showSheet() 683 | })) 684 | } 685 | 686 | typesAlert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: { (_) in 687 | showSheet() 688 | })) 689 | 690 | typesAlert.popoverPresentationController?.sourceView = tableView.cellForRow(at: indexPath) 691 | typesAlert.popoverPresentationController?.sourceRect = tableView.cellForRow(at: indexPath)?.bounds ?? .zero 692 | 693 | self.present(typesAlert, animated: true, completion: nil) 694 | } 695 | 696 | func changeValue() { 697 | guard let value = _value else { 698 | return 699 | } 700 | 701 | let valueAlert = UIAlertController(title: alert.title, message: nil, preferredStyle: .actionSheet) 702 | 703 | let description = "\((value is NSNumber && self.isBoolNumber(num: value as! NSNumber)) ? ((value as! Bool) ? "YES" : "NO") : value)" 704 | if !(value is NSNull) { 705 | valueAlert.addAction(UIAlertAction(title: "\(description)".isEmpty ? "Set" : "\(description)", style: .default, handler: { (_) in 706 | 707 | if let num = value as? NSNumber, self.isBoolNumber(num: num) { 708 | let alert = UIAlertController(title: key, message: "Change value", preferredStyle: .alert) 709 | 710 | let _switch = UISwitch() 711 | _switch.isOn = (value as? Bool) ?? false 712 | 713 | let controller = UIViewController() 714 | 715 | _switch.center = controller.view.center 716 | _switch.autoresizingMask = [.flexibleTopMargin, .flexibleLeftMargin, .flexibleRightMargin, .flexibleBottomMargin] 717 | controller.view.addSubview(_switch) 718 | 719 | alert.setValue(controller, forKey: "contentViewController") 720 | 721 | alert.addAction(UIAlertAction(title: "Confirm", style: .default, handler: { (_) in 722 | setValue(_switch.isOn) 723 | })) 724 | 725 | alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: { (_) in 726 | showSheet() 727 | })) 728 | 729 | self.present(alert, animated: true, completion: nil) 730 | } else if value is NSNumber { 731 | let alert = UIAlertController(title: key, message: "Change value", preferredStyle: .alert) 732 | 733 | var textField: UITextField? 734 | 735 | alert.addTextField { (_textField) in 736 | _textField.keyboardType = .decimalPad 737 | _textField.text = "\(value)" 738 | textField = _textField 739 | } 740 | 741 | alert.addAction(UIAlertAction(title: "Confirm", style: .default, handler: { (_) in 742 | if let text = textField?.text?.replacingOccurrences(of: ",", with: "."), !text.isEmpty { 743 | setValue(NSNumber(value: Float(text) ?? 0)) 744 | } else { 745 | showSheet() 746 | } 747 | })) 748 | 749 | alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: { (_) in 750 | showSheet() 751 | })) 752 | 753 | self.present(alert, animated: true, completion: nil) 754 | } else if value is NSString { 755 | let alert = UIAlertController(title: key, message: "Change value", preferredStyle: .alert) 756 | 757 | let textView = UITextView() 758 | textView.text = value as? String 759 | textView.autoresizingMask = [.flexibleWidth, .flexibleHeight] 760 | 761 | let controller = UIViewController() 762 | 763 | textView.frame = controller.view.frame 764 | controller.view.addSubview(textView) 765 | 766 | alert.setValue(controller, forKey: "contentViewController") 767 | 768 | let height: NSLayoutConstraint = NSLayoutConstraint(item: alert.view ?? UIView(), attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: self.view.frame.height * 0.8) 769 | alert.view.addConstraint(height) 770 | 771 | alert.addAction(UIAlertAction(title: "Confirm", style: .default, handler: { (_) in 772 | setValue(textView.text ?? "") 773 | })) 774 | 775 | alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: { (_) in 776 | showSheet() 777 | })) 778 | 779 | self.present(alert, animated: true, completion: { 780 | textView.becomeFirstResponder() 781 | }) 782 | } else if let date = value as? Date { 783 | let alert = UIAlertController(title: key, message: "Change value", preferredStyle: .alert) 784 | 785 | let picker = UIDatePicker() 786 | picker.datePickerMode = .dateAndTime 787 | picker.date = date 788 | 789 | let controller = UIViewController() 790 | picker.center = controller.view.center 791 | picker.autoresizingMask = [.flexibleBottomMargin, .flexibleTopMargin, .flexibleLeftMargin, .flexibleRightMargin] 792 | controller.view.addSubview(picker) 793 | 794 | alert.setValue(controller, forKey: "contentViewController") 795 | alert.addAction(UIAlertAction(title: "Confirm", style: .default, handler: { (_) in 796 | setValue(picker.date) 797 | })) 798 | 799 | alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: { (_) in 800 | showSheet() 801 | })) 802 | 803 | self.present(alert, animated: true, completion: nil) 804 | } else if let data = value as? Data { 805 | let alert = UIAlertController(title: key, message: "Change value\nPaste a base 64 encoded string", preferredStyle: .alert) 806 | 807 | let textView = UITextView() 808 | textView.text = data.base64EncodedString() 809 | textView.autoresizingMask = [.flexibleWidth, .flexibleHeight] 810 | 811 | let controller = UIViewController() 812 | 813 | textView.frame = controller.view.frame 814 | controller.view.addSubview(textView) 815 | 816 | alert.setValue(controller, forKey: "contentViewController") 817 | 818 | let height: NSLayoutConstraint = NSLayoutConstraint(item: alert.view ?? UIView(), attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: self.view.frame.height * 0.8) 819 | alert.view.addConstraint(height) 820 | 821 | alert.addAction(UIAlertAction(title: "Confirm", style: .default, handler: { (_) in 822 | var components = textView.text.components(separatedBy: "base64,") 823 | if components.count > 1 { 824 | components.remove(at: 0) 825 | } 826 | setValue(Data(base64Encoded: components.joined()) ?? Data()) 827 | })) 828 | 829 | alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: { (_) in 830 | showSheet() 831 | })) 832 | 833 | self.present(alert, animated: true, completion: { 834 | textView.becomeFirstResponder() 835 | }) 836 | } 837 | })) 838 | } 839 | 840 | valueAlert.addAction(UIAlertAction(title: self.type(of: value), style: .default, handler: { (_) in 841 | selectType() 842 | })) 843 | 844 | valueAlert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil)) 845 | 846 | valueAlert.popoverPresentationController?.sourceView = tableView.cellForRow(at: indexPath) 847 | valueAlert.popoverPresentationController?.sourceRect = tableView.cellForRow(at: indexPath)?.bounds ?? .zero 848 | 849 | self.present(valueAlert, animated: true, completion: nil) 850 | } 851 | 852 | if indexPath.row != 0 { 853 | alert.addAction(UIAlertAction(title: "Change value", style: .default, handler: { (_) in 854 | changeValue() 855 | })) 856 | } else { 857 | alert.addAction(UIAlertAction(title: self.type(of: element), style: .default, handler: { (_) in 858 | selectType() 859 | })) 860 | } 861 | 862 | if let dict = element as? NSDictionary, indexPath.row != 0, let key = key { 863 | alert.addAction(UIAlertAction(title: "Change key", style: .default, handler: { (_) in 864 | let keyAlert = UIAlertController(title: key, message: "Change key", preferredStyle: .alert) 865 | 866 | var textField: UITextField? 867 | 868 | keyAlert.addTextField { (_textField) in 869 | _textField.text = key 870 | textField = _textField 871 | } 872 | 873 | keyAlert.addAction(UIAlertAction(title: "Confirm", style: .default, handler: { (_) in 874 | if let textField = textField, let str = textField.text, !str.isEmpty { 875 | let mutable = NSMutableDictionary(dictionary: dict) 876 | let obj = mutable[key] 877 | mutable.removeObject(forKey: key) 878 | mutable[str] = obj 879 | self.element = mutable 880 | 881 | tableView.reloadData() 882 | } else { 883 | showSheet() 884 | } 885 | })) 886 | 887 | keyAlert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: { (_) in 888 | showSheet() 889 | })) 890 | 891 | self.present(keyAlert, animated: true, completion: nil) 892 | })) 893 | } 894 | 895 | if !(indexPath.row == 0 && self.key == nil) { 896 | alert.addAction(UIAlertAction(title: "Delete", style: .destructive, handler: { (_) in 897 | if indexPath.row == 0 { 898 | 899 | if let key = self.key, let element = self.parentElement?.element as? [String:Any] { 900 | let dict = NSMutableDictionary(dictionary: element) 901 | dict[key] = nil 902 | self.parentElement?.element = dict 903 | } else if let element = self.parentElement?.element as? [Any] { 904 | let arr = NSMutableArray(array: element) 905 | arr.removeObject(at: indexPath.row) 906 | self.parentElement?.element = arr 907 | } 908 | 909 | self.navigationController?.popViewController(animated: true) 910 | } else { 911 | if let key = key, let element = self.element as? [String:Any] { 912 | let dict = NSMutableDictionary(dictionary: element) 913 | dict[key] = nil 914 | self.element = dict 915 | } else if let element = self.element as? [Any] { 916 | let arr = NSMutableArray(array: element) 917 | arr.removeObject(at: indexPath.row-1) 918 | self.element = arr 919 | } 920 | 921 | tableView.reloadData() 922 | } 923 | })) 924 | } 925 | 926 | alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil)) 927 | 928 | alert.popoverPresentationController?.sourceView = tableView.cellForRow(at: indexPath) 929 | alert.popoverPresentationController?.sourceRect = tableView.cellForRow(at: indexPath)?.bounds ?? .zero 930 | 931 | if !isPresentedFromContextMenu { 932 | present(alert, animated: true, completion: nil) 933 | } else { 934 | changeValue() 935 | } 936 | 937 | return 938 | } 939 | 940 | if indexPath.row == 0 { 941 | return showSheet() 942 | } 943 | 944 | if _value is NSDictionary || _value is NSArray { 945 | guard let vc = storyboard?.instantiateViewController(withIdentifier: "DocumentViewController") as? DocumentViewController else { 946 | return 947 | } 948 | 949 | vc.loadViewIfNeeded() 950 | 951 | vc.navigationItem.largeTitleDisplayMode = .never 952 | 953 | vc.isDocOpen = isDocOpen 954 | vc.document = document 955 | vc.key = key ?? "\(indexPath.row-1)" 956 | vc.element = (element as? NSArray)?[indexPath.row-1] ?? (element as? NSDictionary)?[vc.key ?? ""] ?? [String:Any]() 957 | vc.parentElement = self 958 | 959 | navigationController?.pushViewController(vc, animated: true) 960 | } else { 961 | showSheet() 962 | } 963 | } 964 | 965 | // MARK: - Text view delegate 966 | 967 | func textViewDidEndEditing(_ textView: UITextView) { 968 | let pathExtension = document?.fileURL.pathExtension.lowercased() 969 | 970 | let data = textView.text.data(using: .utf8) ?? Data() 971 | 972 | do { 973 | if pathExtension == "json" { 974 | element = try JSONSerialization.jsonObject(with: data, options: .allowFragments) 975 | } else if pathExtension == "plist" { 976 | 977 | let url = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent("tmp.plist") 978 | try data.write(to: url) 979 | 980 | element = (NSDictionary(contentsOfFile: url.path) ?? NSArray(contentsOf: url)) ?? [String:Any]() 981 | } 982 | } catch { 983 | print(error.localizedDescription) 984 | } 985 | 986 | tableView.reloadData() 987 | } 988 | 989 | // MARK: - Context menu interaction delegate 990 | 991 | func contextMenuInteraction(_ interaction: UIContextMenuInteraction, configurationForMenuAtLocation location: CGPoint) -> UIContextMenuConfiguration? { 992 | 993 | guard let cell = interaction.view as? UITableViewCell else { 994 | return nil 995 | } 996 | 997 | guard let indexPath = self.tableView.indexPath(for: cell) else { 998 | return nil 999 | } 1000 | 1001 | let key: String? 1002 | 1003 | if indexPath.row != 0 { 1004 | key = (element as? NSDictionary)?.allKeys[indexPath.row-1] as? String 1005 | } else if element is NSArray && indexPath.row != 0 { 1006 | key = nil 1007 | } else { 1008 | key = self.key ?? "Root" 1009 | } 1010 | 1011 | let _value: Any? 1012 | if let key = key, let element = self.element as? [String:Any] { 1013 | _value = element[key] 1014 | } else if let element = self.element as? [Any], indexPath.row != 0 { 1015 | _value = element[indexPath.row-1] 1016 | } else { 1017 | _value = nil 1018 | } 1019 | 1020 | guard let value = _value else { 1021 | return nil 1022 | } 1023 | 1024 | var stringValue: String { 1025 | if value is NSDictionary || value is NSArray { 1026 | let pathExtension = self.document?.fileURL.pathExtension.lowercased() 1027 | if pathExtension == "json" { 1028 | if let data = try? JSONSerialization.data(withJSONObject: value, options: JSONSerialization.WritingOptions.prettyPrinted), let str = String(data: data, encoding: .utf8) { 1029 | return str 1030 | } 1031 | } else if pathExtension == "plist" { 1032 | 1033 | let url = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent("tmp.plist") 1034 | 1035 | if let propertyListDict = value as? [String:Any] { 1036 | let dict = NSDictionary(dictionary: propertyListDict) 1037 | try? dict.write(to: url) 1038 | } else if let propertyListArray = value as? [Any] { 1039 | let arr = NSArray(array: propertyListArray) 1040 | try? arr.write(to: url) 1041 | } 1042 | 1043 | return (try? String(contentsOf: url)) ?? "" 1044 | } 1045 | } else { 1046 | return "\(value)" 1047 | } 1048 | 1049 | return "" 1050 | } 1051 | 1052 | let edit = UIAction(title: "Edit", image: UIImage(systemName: "pencil")) { action in 1053 | self.tableView(self.tableView, didSelectRowAt: indexPath) 1054 | } 1055 | 1056 | let duplicate = UIAction(title: "Duplicate", image: UIImage(systemName: "doc.on.doc.fill")) { action in 1057 | 1058 | guard let key = key else { 1059 | return 1060 | } 1061 | 1062 | if let arr = self.element as? NSArray { 1063 | let mutable = NSMutableArray(array: arr) 1064 | mutable.insert(value, at: indexPath.row+1) 1065 | self.element = mutable 1066 | self.tableView.reloadData() 1067 | } else if let dict = self.element as? NSDictionary { 1068 | let mutable = NSMutableDictionary(dictionary: dict) 1069 | 1070 | var i = 1 1071 | 1072 | var _key = key 1073 | 1074 | while let last = _key.last, Int(String(last)) != nil { 1075 | _key.removeLast() 1076 | } 1077 | 1078 | var newKey: String { 1079 | return _key+"\(i)" 1080 | } 1081 | 1082 | while dict[newKey] != nil { 1083 | i += 1 1084 | } 1085 | 1086 | mutable[newKey] = value 1087 | 1088 | self.element = mutable 1089 | self.tableView.reloadData() 1090 | } 1091 | } 1092 | 1093 | let rename = UIAction(title: "Rename", image: UIImage(systemName: "tag.fill")) { action in 1094 | 1095 | if let dict = self.element as? NSDictionary, indexPath.row != 0, let key = key { 1096 | let keyAlert = UIAlertController(title: key, message: "Change key", preferredStyle: .alert) 1097 | 1098 | var textField: UITextField? 1099 | 1100 | keyAlert.addTextField { (_textField) in 1101 | _textField.text = key 1102 | textField = _textField 1103 | } 1104 | 1105 | keyAlert.addAction(UIAlertAction(title: "Confirm", style: .default, handler: { (_) in 1106 | if let textField = textField, let str = textField.text, !str.isEmpty { 1107 | let mutable = NSMutableDictionary(dictionary: dict) 1108 | let obj = mutable[key] 1109 | mutable.removeObject(forKey: key) 1110 | mutable[str] = obj 1111 | self.element = mutable 1112 | 1113 | self.tableView.reloadData() 1114 | } 1115 | })) 1116 | 1117 | keyAlert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil)) 1118 | 1119 | self.present(keyAlert, animated: true, completion: nil) 1120 | } 1121 | } 1122 | 1123 | let copy = UIAction(title: "Copy value", image: UIImage(systemName: "doc.on.clipboard")) { action in 1124 | UIPasteboard.general.string = stringValue 1125 | } 1126 | 1127 | #if !APP_EXTENSION 1128 | let newWindow = UIAction(title: "Open in new Window", image: UIImage(systemName: "plus.square.fill")) { action in 1129 | 1130 | guard let vc = self.storyboard?.instantiateViewController(withIdentifier: "DocumentViewController") as? DocumentViewController else { 1131 | return 1132 | } 1133 | 1134 | vc.loadViewIfNeeded() 1135 | 1136 | vc.navigationItem.largeTitleDisplayMode = .never 1137 | 1138 | vc.document = self.document 1139 | vc.key = key ?? "\(indexPath.row-1)" 1140 | vc.element = (self.element as? NSArray)?[indexPath.row-1] ?? (self.element as? NSDictionary)?[vc.key ?? ""] ?? [String:Any]() 1141 | vc.parentElement = self 1142 | 1143 | SceneDelegate.customViewController = UINavigationController(rootViewController: vc) 1144 | UIApplication.shared.requestSceneSessionActivation(nil, userActivity: nil, options: nil, errorHandler: nil) 1145 | } 1146 | #endif 1147 | 1148 | let inspectWithJS = UIAction(title: "Inspect with JavaScript", image: UIImage(systemName: "chevron.left.slash.chevron.right")) { action in 1149 | guard let context = JSContext() else { 1150 | return 1151 | } 1152 | context.setObject(value, forKeyedSubscript: "value" as NSCopying & NSObjectProtocol) 1153 | 1154 | let console = ConsoleViewController() 1155 | console.context = context 1156 | console.editor = self 1157 | console.key = key ?? "\(indexPath.row-1)" 1158 | let navVC = UINavigationController(rootViewController: console) 1159 | navVC.modalPresentationStyle = .fullScreen 1160 | self.present(navVC, animated: true, completion: nil) 1161 | } 1162 | 1163 | let delete = UIAction(title: "Delete", image: UIImage(systemName: "trash.fill"), attributes: .destructive) { action in 1164 | 1165 | if indexPath.row == 0 { 1166 | if let key = self.key, let element = self.parentElement?.element as? [String:Any] { 1167 | let dict = NSMutableDictionary(dictionary: element) 1168 | dict[key] = nil 1169 | self.parentElement?.element = dict 1170 | } else if let element = self.parentElement?.element as? [Any] { 1171 | let arr = NSMutableArray(array: element) 1172 | arr.removeObject(at: indexPath.row) 1173 | self.parentElement?.element = arr 1174 | } 1175 | 1176 | self.navigationController?.popViewController(animated: true) 1177 | } else { 1178 | if let key = key, let element = self.element as? [String:Any] { 1179 | let dict = NSMutableDictionary(dictionary: element) 1180 | dict[key] = nil 1181 | self.element = dict 1182 | } else if let element = self.element as? [Any] { 1183 | let arr = NSMutableArray(array: element) 1184 | arr.removeObject(at: indexPath.row-1) 1185 | self.element = arr 1186 | } 1187 | 1188 | self.tableView.reloadData() 1189 | } 1190 | } 1191 | 1192 | return UIContextMenuConfiguration(identifier: nil, previewProvider: { () -> UIViewController? in 1193 | 1194 | let vc = UIViewController() 1195 | 1196 | vc.view = UITextView() 1197 | 1198 | let textView = (vc.view as! UITextView) 1199 | textView.text = stringValue 1200 | textView.backgroundColor = .systemBackground 1201 | textView.textColor = .label 1202 | textView.font = UIFont(name: "Menlo", size: UIFont.systemFontSize) 1203 | 1204 | return vc 1205 | }) { (_) -> UIMenu? in 1206 | 1207 | let children: [UIAction] 1208 | 1209 | if indexPath.row == 0 { 1210 | children = [delete] 1211 | } else if UIDevice.current.userInterfaceIdiom == .pad { 1212 | #if !APP_EXTENSION 1213 | if self.element is NSDictionary { 1214 | if value is NSDictionary || value is NSArray { 1215 | children = [edit, inspectWithJS, rename, duplicate, copy, newWindow, delete] 1216 | } else { 1217 | children = [edit, inspectWithJS, rename, duplicate, copy, delete] 1218 | } 1219 | } else if self.element is NSArray { 1220 | if value is NSDictionary || value is NSArray { 1221 | children = [edit, inspectWithJS, duplicate, copy, newWindow, delete] 1222 | } else { 1223 | children = [edit, inspectWithJS, duplicate, copy, delete] 1224 | } 1225 | } else { 1226 | children = [edit, inspectWithJS, duplicate, copy, delete] 1227 | } 1228 | #else 1229 | if self.element is NSDictionary { 1230 | children = [edit, inspectWithJS, rename, duplicate, copy, delete] 1231 | } else { 1232 | children = [edit, inspectWithJS, duplicate, copy, delete] 1233 | } 1234 | #endif 1235 | } else { 1236 | if self.element is NSDictionary { 1237 | children = [edit, inspectWithJS, rename, duplicate, copy, delete] 1238 | } else { 1239 | children = [edit, inspectWithJS, duplicate, copy, delete] 1240 | } 1241 | } 1242 | 1243 | return UIMenu(title: cell.textLabel?.text ?? "", children: children) 1244 | } 1245 | } 1246 | } 1247 | 1248 | #if APP_EXTENSION 1249 | extension DocumentViewController: UIDocumentPickerDelegate { 1250 | 1251 | func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) { 1252 | view.window?.rootViewController?.children.first?.extensionContext?.completeRequest(returningItems: nil, completionHandler: nil) 1253 | } 1254 | 1255 | func documentPickerWasCancelled(_ controller: UIDocumentPickerViewController) { 1256 | view.window?.rootViewController?.children.first?.extensionContext?.completeRequest(returningItems: nil, completionHandler: nil) 1257 | } 1258 | } 1259 | #endif 1260 | -------------------------------------------------------------------------------- /Plistor/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleDocumentTypes 8 | 9 | 10 | CFBundleTypeIconFiles 11 | 12 | CFBundleTypeName 13 | JSON 14 | CFBundleTypeRole 15 | Editor 16 | LSHandlerRank 17 | Owner 18 | LSItemContentTypes 19 | 20 | public.json 21 | 22 | 23 | 24 | CFBundleTypeIconFiles 25 | 26 | CFBundleTypeName 27 | Property List 28 | CFBundleTypeRole 29 | Editor 30 | LSHandlerRank 31 | Owner 32 | LSItemContentTypes 33 | 34 | com.apple.property-list 35 | 36 | 37 | 38 | CFBundleExecutable 39 | $(EXECUTABLE_NAME) 40 | CFBundleIdentifier 41 | $(PRODUCT_BUNDLE_IDENTIFIER) 42 | CFBundleInfoDictionaryVersion 43 | 6.0 44 | CFBundleName 45 | $(PRODUCT_NAME) 46 | CFBundlePackageType 47 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 48 | CFBundleShortVersionString 49 | 1.0 50 | CFBundleVersion 51 | 1 52 | LSRequiresIPhoneOS 53 | 54 | UIApplicationSceneManifest 55 | 56 | UIApplicationSupportsMultipleScenes 57 | 58 | UISceneConfigurations 59 | 60 | UIWindowSceneSessionRoleApplication 61 | 62 | 63 | UISceneConfigurationName 64 | Document Browser 65 | UISceneDelegateClassName 66 | $(PRODUCT_MODULE_NAME).SceneDelegate 67 | UISceneStoryboardFile 68 | Main 69 | UILaunchStoryboardName 70 | LaunchScreen 71 | 72 | 73 | 74 | 75 | UILaunchStoryboardName 76 | LaunchScreen 77 | UIMainStoryboardFile 78 | Main 79 | UIRequiredDeviceCapabilities 80 | 81 | armv7 82 | 83 | UISupportedInterfaceOrientations 84 | 85 | UIInterfaceOrientationPortrait 86 | UIInterfaceOrientationLandscapeLeft 87 | UIInterfaceOrientationLandscapeRight 88 | 89 | UISupportedInterfaceOrientations~ipad 90 | 91 | UIInterfaceOrientationPortrait 92 | UIInterfaceOrientationPortraitUpsideDown 93 | UIInterfaceOrientationLandscapeLeft 94 | UIInterfaceOrientationLandscapeRight 95 | 96 | UISupportsDocumentBrowser 97 | 98 | UTImportedTypeDeclarations 99 | 100 | 101 | 102 | NSAppTransportSecurity 103 | 104 | NSAllowsArbitraryLoads 105 | 106 | 107 | 108 | 109 | -------------------------------------------------------------------------------- /Plistor/MovableTextField.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MovableTextField.swift 3 | // MovableTextField 4 | // 5 | // Created by Adrian Labbé on 3/30/19. 6 | // Copyright © 2019 Adrian Labbé. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | /// A class for managing a movable text field. 12 | class MovableTextField: NSObject, UITextFieldDelegate { 13 | 14 | /// The view containing this text field. 15 | let console: ConsoleViewController 16 | 17 | /// The placeholder of the text field. 18 | var placeholder = "" { 19 | didSet { 20 | textField.placeholder = placeholder 21 | } 22 | } 23 | 24 | /// The toolbar containing the text field 25 | let toolbar: UIToolbar 26 | 27 | /// The text field. 28 | let textField: UITextField 29 | 30 | /// Initializes the manager. 31 | /// 32 | /// - Parameters: 33 | /// - console: The console containing the text field. 34 | init(console: ConsoleViewController) { 35 | self.console = console 36 | toolbar = Bundle(for: MovableTextField.self).loadNibNamed("TextField", owner: nil, options: nil)?.first as! UIToolbar 37 | textField = toolbar.items!.first!.customView as! UITextField 38 | 39 | super.init() 40 | 41 | #if MAIN 42 | applyTheme() 43 | #endif 44 | 45 | textField.delegate = self 46 | if #available(iOS 13.0, *) { 47 | textField.textColor = UIColor.label 48 | } 49 | 50 | NotificationCenter.default.addObserver(self, selector: #selector(keyboardDidShow(_:)), name: UIResponder.keyboardDidShowNotification, object: nil) 51 | NotificationCenter.default.addObserver(self, selector: #selector(keyboardDidHide(_:)), name: UIResponder.keyboardDidHideNotification, object: nil) 52 | NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide(_:)), name: UIResponder.keyboardWillHideNotification, object: nil) 53 | NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow(_:)), name: UIResponder.keyboardWillShowNotification, object: nil) 54 | } 55 | 56 | /// Shows the text field. 57 | func show() { 58 | toolbar.frame.size.width = console.view.safeAreaLayoutGuide.layoutFrame.width 59 | toolbar.frame.origin.x = console.view.safeAreaInsets.left 60 | toolbar.frame.origin.y = console.view.safeAreaLayoutGuide.layoutFrame.height-toolbar.frame.height 61 | toolbar.autoresizingMask = [.flexibleWidth, .flexibleBottomMargin, .flexibleTopMargin] 62 | console.view.clipsToBounds = false 63 | console.view.addSubview(toolbar) 64 | } 65 | 66 | /// Shows keyboard. 67 | func focus() { 68 | DispatchQueue.main.asyncAfter(deadline: .now()+0.25) { 69 | self.textField.becomeFirstResponder() 70 | } 71 | } 72 | 73 | /// Code called when text is sent. Receives the text. 74 | var handler: ((String) -> Void)? 75 | 76 | // MARK: - Keyboard 77 | 78 | @objc private func keyboardDidShow(_ notification: NSNotification) { 79 | 80 | // That's the typical code you just cannot understand when you are playing Clash Royale while coding 81 | // So please, open iTunes, play your playlist, focus and then go back. 82 | 83 | if console.parent?.parent?.modalPresentationStyle != .popover || console.parent?.parent?.view.frame.width != console.parent?.parent?.preferredContentSize.width { 84 | 85 | var r = notification.userInfo![UIResponder.keyboardFrameEndUserInfoKey] as! CGRect 86 | r = console.textView.convert(r, from:nil) 87 | toolbar.frame.origin.y = console.view.frame.height-r.height-toolbar.frame.height 88 | #if APP_EXTENSION 89 | if UIDevice.current.userInterfaceIdiom == .pad { 90 | toolbar.frame.origin.y -= 22 91 | } 92 | #endif 93 | } 94 | 95 | UIView.animate(withDuration: 0.5) { 96 | self.toolbar.alpha = 1 97 | } 98 | 99 | console.textView.scrollToBottom() 100 | } 101 | 102 | @objc private func keyboardDidHide(_ notification: NSNotification) { 103 | toolbar.frame.origin.y = console.view.safeAreaLayoutGuide.layoutFrame.height-toolbar.frame.height 104 | 105 | if textField.isFirstResponder { // Still editing, but with a hardware keyboard 106 | keyboardDidShow(notification) 107 | } 108 | 109 | UIView.animate(withDuration: 0.5) { 110 | self.toolbar.alpha = 1 111 | } 112 | } 113 | 114 | @objc private func keyboardWillHide(_ notification: NSNotification) { 115 | UIView.animate(withDuration: 0.5) { 116 | self.toolbar.alpha = 0 117 | } 118 | } 119 | 120 | @objc private func keyboardWillShow(_ notification: NSNotification) { 121 | UIView.animate(withDuration: 0.5) { 122 | self.toolbar.alpha = 0 123 | } 124 | } 125 | 126 | // MARK: - Text field delegate 127 | 128 | func textFieldShouldReturn(_ textField: UITextField) -> Bool { 129 | 130 | textField.resignFirstResponder() 131 | 132 | defer { 133 | handler?(textField.text ?? "") 134 | placeholder = "" 135 | 136 | #if MAIN 137 | if let text = textField.text, !text.isEmpty { 138 | if let i = history.firstIndex(of: text) { 139 | history.remove(at: i) 140 | } 141 | history.insert(text, at: 0) 142 | historyIndex = -1 143 | } 144 | currentInput = nil 145 | #endif 146 | 147 | textField.text = "" 148 | } 149 | 150 | return true 151 | } 152 | 153 | // MARK: - History 154 | 155 | /// The current command that is not in the history. 156 | var currentInput: String? 157 | 158 | /// The index of current input in the history. `-1` if the command is not in the history. 159 | var historyIndex = -1 { 160 | didSet { 161 | if historyIndex == -1 { 162 | textField.text = currentInput 163 | } else if history.indices.contains(historyIndex) { 164 | textField.text = history[historyIndex] 165 | } 166 | } 167 | } 168 | 169 | /// The history of input. This array is reversed. The first command in the history is the last in this array. 170 | var history: [String] { 171 | get { 172 | return (UserDefaults.standard.array(forKey: "inputHistory") as? [String]) ?? [] 173 | } 174 | 175 | set { 176 | UserDefaults.standard.set(newValue, forKey: "inputHistory") 177 | UserDefaults.standard.synchronize() // Yes, I know, that's not needed, but I call it BECAUSE I WANT, I CALL THIS FUNCTION BECAUSE I WANT OK 178 | } 179 | } 180 | 181 | /// Scrolls down on the history. 182 | @objc func down() { 183 | if historyIndex > -1 { 184 | historyIndex -= 1 185 | } 186 | } 187 | 188 | /// Scrolls up on the history. 189 | @objc func up() { 190 | if history.indices.contains(historyIndex+1) { 191 | historyIndex += 1 192 | } 193 | } 194 | } 195 | 196 | -------------------------------------------------------------------------------- /Plistor/Plistor.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.files.user-selected.read-write 8 | 9 | com.apple.security.network.client 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /Plistor/Property List.json: -------------------------------------------------------------------------------- 1 | { 2 | } 3 | -------------------------------------------------------------------------------- /Plistor/Property List.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /Plistor/SceneDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SceneDelegate.swift 3 | // Plistor 4 | // 5 | // Created by Adrian Labbé on 16-12-19. 6 | // Copyright © 2019 Adrian Labbé. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | /// The scene delegate. 12 | class SceneDelegate: UIResponder, UIWindowSceneDelegate { 13 | 14 | /// The document browser associated with this scene. 15 | var documentBrowserViewController: DocumentBrowserViewController? { 16 | return window?.rootViewController as? DocumentBrowserViewController 17 | } 18 | 19 | /// A view controller to present on a new scene. 20 | static var customViewController: UIViewController? 21 | 22 | // MARK: - Scene delegate 23 | 24 | var window: UIWindow? 25 | 26 | @available(iOS 13.0, *) 27 | func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { 28 | 29 | if let customVC = SceneDelegate.customViewController { 30 | 31 | class ViewController: UIViewController { 32 | 33 | var presented = false 34 | 35 | var vc: UIViewController! 36 | 37 | override func viewDidAppear(_ animated: Bool) { 38 | super.viewWillAppear(animated) 39 | 40 | if presented, let session = view.window?.windowScene?.session { 41 | UIApplication.shared.requestSceneSessionDestruction(session, options: nil, errorHandler: nil) 42 | } else { 43 | vc.modalPresentationStyle = .fullScreen 44 | present(vc, animated: false, completion: nil) 45 | presented = true 46 | } 47 | } 48 | } 49 | 50 | let vc = ViewController() 51 | vc.vc = customVC 52 | window?.rootViewController = vc 53 | 54 | SceneDelegate.customViewController = nil 55 | return 56 | } 57 | 58 | if connectionOptions.urlContexts.count > 0 { 59 | self.scene(scene, openURLContexts: connectionOptions.urlContexts) 60 | return 61 | } 62 | 63 | if connectionOptions.userActivities.count > 0 { 64 | self.scene(scene, continue: connectionOptions.userActivities.first!) 65 | return 66 | } 67 | 68 | if let restorationActivity = session.stateRestorationActivity, let data = restorationActivity.userInfo?["bookmarkData"] as? Data { 69 | do { 70 | var isStale = false 71 | let url = try URL(resolvingBookmarkData: data, bookmarkDataIsStale: &isStale) 72 | 73 | (window?.rootViewController as? DocumentBrowserViewController)?.documentURL = url 74 | } catch { 75 | print(error.localizedDescription) 76 | } 77 | } 78 | } 79 | 80 | @available(iOS 13.0, *) 81 | func scene(_ scene: UIScene, continue userActivity: NSUserActivity) { 82 | 83 | let root = window?.rootViewController 84 | 85 | func runScript() { 86 | if let data = userActivity.userInfo?["filePath"] as? Data { 87 | do { 88 | var isStale = false 89 | let url = try URL(resolvingBookmarkData: data, bookmarkDataIsStale: &isStale) 90 | 91 | if let arguments = userActivity.userInfo?["arguments"] as? String { 92 | UserDefaults.standard.set(arguments, forKey: "arguments\(url.path.replacingOccurrences(of: "//", with: "/"))") 93 | } 94 | 95 | _ = Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true, block: { (timer) in 96 | if let doc = self.documentBrowserViewController { 97 | doc.revealDocument(at: url, importIfNeeded: true) { (url_, _) in 98 | doc.presentDocument(at: url_ ?? url) 99 | } 100 | timer.invalidate() 101 | } 102 | }) 103 | } catch { 104 | print(error.localizedDescription) 105 | } 106 | } 107 | } 108 | 109 | if root?.presentedViewController != nil { 110 | root?.dismiss(animated: true, completion: { 111 | runScript() 112 | }) 113 | } else { 114 | runScript() 115 | } 116 | } 117 | 118 | @available(iOS 13.0, *) 119 | func scene(_ scene: UIScene, openURLContexts URLContexts: Set) { 120 | 121 | guard let inputURL = URLContexts.first?.url else { 122 | return 123 | } 124 | 125 | guard let documentBrowserViewController = documentBrowserViewController else { 126 | window?.rootViewController?.dismiss(animated: true, completion: { 127 | self.scene(scene, openURLContexts: URLContexts) 128 | }) 129 | return 130 | } 131 | 132 | // Ensure the URL is a file URL 133 | guard inputURL.isFileURL else { 134 | return 135 | } 136 | 137 | _ = inputURL.startAccessingSecurityScopedResource() 138 | 139 | // Reveal / import the document at the URL 140 | 141 | documentBrowserViewController.revealDocument(at: inputURL, importIfNeeded: true, completion: { (url, _) in 142 | 143 | documentBrowserViewController.presentDocument(at: url ?? inputURL) 144 | }) 145 | } 146 | 147 | // MARK: - State restoration 148 | 149 | @available(iOS 13.0, *) 150 | func stateRestorationActivity(for scene: UIScene) -> NSUserActivity? { 151 | if let url = ((documentBrowserViewController?.presentedViewController as? UINavigationController)?.viewControllers.first as? DocumentViewController)?.document?.fileURL { 152 | 153 | do { 154 | 155 | let bookmarkData = try url.bookmarkData() 156 | 157 | let activity = NSUserActivity(activityType: "stateRestoration") 158 | activity.userInfo?["bookmarkData"] = bookmarkData 159 | return activity 160 | } catch { 161 | return nil 162 | } 163 | } 164 | 165 | return nil 166 | } 167 | } 168 | 169 | -------------------------------------------------------------------------------- /Plistor/TextField.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /Plistor/UITextView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UITextView.swift 3 | // Plistor 4 | // 5 | // Created by Adrian Labbé on 19-12-19. 6 | // Copyright © 2019 Adrian Labbé. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | extension UITextView { 12 | 13 | /// Scrolls to bottom. 14 | func scrollToBottom() { 15 | 16 | let text_ = text 17 | 18 | let range = NSMakeRange(((text_ ?? "") as NSString).length - 1, 1) 19 | scrollRangeToVisible(range) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Icon](https://raw.githubusercontent.com/ColdGrub1384/Plistor/master/Plistor/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5%402x.png) 2 | 3 | # Plistor 4 | 5 | Plistor is a JSON and Plist editor for iOS. 6 | -------------------------------------------------------------------------------- /View JSON or Plist/ExtensionPreProcessing.js: -------------------------------------------------------------------------------- 1 | var Action = function() {}; 2 | 3 | Action.prototype = { 4 | 5 | run: function(parameters) { 6 | parameters.completionFunction({ "content" : document.body.innerText, "name" : window.location.pathname.split("/").reverse()[0] }); 7 | }, 8 | 9 | finalize: function(parameters) { 10 | 11 | } 12 | 13 | }; 14 | 15 | var ExtensionPreprocessingJS = new Action 16 | -------------------------------------------------------------------------------- /View JSON or Plist/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleDisplayName 8 | $(PRODUCT_NAME) 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 | NSExtension 24 | 25 | NSExtensionActionWantsFullScreenPresentation 26 | 27 | NSExtensionAttributes 28 | 29 | NSExtensionJavaScriptPreprocessingFile 30 | ExtensionPreProcessing 31 | NSExtensionActivationRule 32 | 33 | NSExtensionActivationSupportsWebPageWithMaxCount 34 | 1 35 | 36 | NSExtensionServiceAllowsFinderPreviewItem 37 | 38 | NSExtensionServiceAllowsTouchBarItem 39 | 40 | NSExtensionServiceFinderPreviewIconName 41 | NSActionTemplate 42 | NSExtensionServiceTouchBarBezelColorName 43 | TouchBarBezel 44 | NSExtensionServiceTouchBarIconName 45 | NSActionTemplate 46 | 47 | NSExtensionMainStoryboard 48 | Main 49 | NSExtensionPointIdentifier 50 | com.apple.ui-services 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /View JSON or Plist/Media.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "Icon-App-20x20@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "Icon-App-20x20@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "Icon-App-29x29@1x.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "Icon-App-29x29@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "29x29", 29 | "idiom" : "iphone", 30 | "filename" : "Icon-App-29x29@3x.png", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-App-40x40@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "40x40", 41 | "idiom" : "iphone", 42 | "filename" : "Icon-App-40x40@3x.png", 43 | "scale" : "3x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "Icon-App-60x60@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "60x60", 53 | "idiom" : "iphone", 54 | "filename" : "Icon-App-60x60@3x.png", 55 | "scale" : "3x" 56 | }, 57 | { 58 | "size" : "20x20", 59 | "idiom" : "ipad", 60 | "filename" : "Icon-App-20x20@1x.png", 61 | "scale" : "1x" 62 | }, 63 | { 64 | "size" : "20x20", 65 | "idiom" : "ipad", 66 | "filename" : "Icon-App-20x20@2x.png", 67 | "scale" : "2x" 68 | }, 69 | { 70 | "size" : "29x29", 71 | "idiom" : "ipad", 72 | "filename" : "Icon-App-29x29@1x.png", 73 | "scale" : "1x" 74 | }, 75 | { 76 | "size" : "29x29", 77 | "idiom" : "ipad", 78 | "filename" : "Icon-App-29x29@2x.png", 79 | "scale" : "2x" 80 | }, 81 | { 82 | "size" : "40x40", 83 | "idiom" : "ipad", 84 | "filename" : "Icon-App-40x40@1x.png", 85 | "scale" : "1x" 86 | }, 87 | { 88 | "size" : "40x40", 89 | "idiom" : "ipad", 90 | "filename" : "Icon-App-40x40@2x.png", 91 | "scale" : "2x" 92 | }, 93 | { 94 | "size" : "76x76", 95 | "idiom" : "ipad", 96 | "filename" : "Icon-App-76x76@1x.png", 97 | "scale" : "1x" 98 | }, 99 | { 100 | "size" : "76x76", 101 | "idiom" : "ipad", 102 | "filename" : "Icon-App-76x76@2x.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "83.5x83.5", 107 | "idiom" : "ipad", 108 | "filename" : "Icon-App-83.5x83.5@2x.png", 109 | "scale" : "2x" 110 | }, 111 | { 112 | "idiom" : "ios-marketing", 113 | "size" : "1024x1024", 114 | "scale" : "1x" 115 | } 116 | ], 117 | "info" : { 118 | "version" : 1, 119 | "author" : "xcode" 120 | } 121 | } -------------------------------------------------------------------------------- /View JSON or Plist/Media.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ColdGrub1384/Plistor/8cf8451817f0a25f19629b63c0cae34b2a75986d/View JSON or Plist/Media.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /View JSON or Plist/Media.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ColdGrub1384/Plistor/8cf8451817f0a25f19629b63c0cae34b2a75986d/View JSON or Plist/Media.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /View JSON or Plist/Media.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ColdGrub1384/Plistor/8cf8451817f0a25f19629b63c0cae34b2a75986d/View JSON or Plist/Media.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /View JSON or Plist/Media.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ColdGrub1384/Plistor/8cf8451817f0a25f19629b63c0cae34b2a75986d/View JSON or Plist/Media.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /View JSON or Plist/Media.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ColdGrub1384/Plistor/8cf8451817f0a25f19629b63c0cae34b2a75986d/View JSON or Plist/Media.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /View JSON or Plist/Media.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ColdGrub1384/Plistor/8cf8451817f0a25f19629b63c0cae34b2a75986d/View JSON or Plist/Media.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /View JSON or Plist/Media.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ColdGrub1384/Plistor/8cf8451817f0a25f19629b63c0cae34b2a75986d/View JSON or Plist/Media.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /View JSON or Plist/Media.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ColdGrub1384/Plistor/8cf8451817f0a25f19629b63c0cae34b2a75986d/View JSON or Plist/Media.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /View JSON or Plist/Media.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ColdGrub1384/Plistor/8cf8451817f0a25f19629b63c0cae34b2a75986d/View JSON or Plist/Media.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /View JSON or Plist/Media.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ColdGrub1384/Plistor/8cf8451817f0a25f19629b63c0cae34b2a75986d/View JSON or Plist/Media.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /View JSON or Plist/Media.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ColdGrub1384/Plistor/8cf8451817f0a25f19629b63c0cae34b2a75986d/View JSON or Plist/Media.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /View JSON or Plist/Media.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ColdGrub1384/Plistor/8cf8451817f0a25f19629b63c0cae34b2a75986d/View JSON or Plist/Media.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /View JSON or Plist/Media.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ColdGrub1384/Plistor/8cf8451817f0a25f19629b63c0cae34b2a75986d/View JSON or Plist/Media.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /View JSON or Plist/Media.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ColdGrub1384/Plistor/8cf8451817f0a25f19629b63c0cae34b2a75986d/View JSON or Plist/Media.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /View JSON or Plist/Media.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /View JSON or Plist/Media.xcassets/TouchBarBezel.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | }, 6 | "colors" : [ 7 | { 8 | "idiom" : "mac", 9 | "color" : { 10 | "reference" : "systemPurpleColor" 11 | } 12 | } 13 | ] 14 | } --------------------------------------------------------------------------------