Эх сурвалжийг харах

more things

- improved logging
- shake to debug
- handle configure and restore better
- mark messages as seen
dignifiedquire 6 жил өмнө
parent
commit
8f8ff6271b
100 өөрчлөгдсөн 8924 нэмэгдсэн , 1 устгасан
  1. 2 0
      Podfile
  2. 9 1
      Podfile.lock
  3. 50 0
      Pods/DBDebugToolkit/DBDebugToolkit/Classes/BuildInfo/DBBuildInfoProvider.h
  4. 51 0
      Pods/DBDebugToolkit/DBDebugToolkit/Classes/BuildInfo/DBBuildInfoProvider.m
  5. 35 0
      Pods/DBDebugToolkit/DBDebugToolkit/Classes/Categories/NSBundle+DBDebugToolkit.h
  6. 34 0
      Pods/DBDebugToolkit/DBDebugToolkit/Classes/Categories/NSBundle+DBDebugToolkit.m
  7. 54 0
      Pods/DBDebugToolkit/DBDebugToolkit/Classes/Categories/NSObject+DBDebugToolkit.h
  8. 80 0
      Pods/DBDebugToolkit/DBDebugToolkit/Classes/Categories/NSObject+DBDebugToolkit.m
  9. 35 0
      Pods/DBDebugToolkit/DBDebugToolkit/Classes/Categories/UIApplication+DBDebugToolkit.h
  10. 87 0
      Pods/DBDebugToolkit/DBDebugToolkit/Classes/Categories/UIApplication+DBDebugToolkit.m
  11. 37 0
      Pods/DBDebugToolkit/DBDebugToolkit/Classes/Categories/UIColor+DBDebugToolkit.h
  12. 34 0
      Pods/DBDebugToolkit/DBDebugToolkit/Classes/Categories/UIColor+DBDebugToolkit.m
  13. 35 0
      Pods/DBDebugToolkit/DBDebugToolkit/Classes/Categories/UILabel+DBDebugToolkit.h
  14. 36 0
      Pods/DBDebugToolkit/DBDebugToolkit/Classes/Categories/UILabel+DBDebugToolkit.m
  15. 33 0
      Pods/DBDebugToolkit/DBDebugToolkit/Classes/Categories/UIView+Snapshot.h
  16. 35 0
      Pods/DBDebugToolkit/DBDebugToolkit/Classes/Categories/UIView+Snapshot.m
  17. 67 0
      Pods/DBDebugToolkit/DBDebugToolkit/Classes/Cells/ColorPicker/DBColorCheckbox.h
  18. 91 0
      Pods/DBDebugToolkit/DBDebugToolkit/Classes/Cells/ColorPicker/DBColorCheckbox.m
  19. 68 0
      Pods/DBDebugToolkit/DBDebugToolkit/Classes/Cells/ColorPicker/DBColorPickerTableViewCell.h
  20. 88 0
      Pods/DBDebugToolkit/DBDebugToolkit/Classes/Cells/ColorPicker/DBColorPickerTableViewCell.m
  21. 36 0
      Pods/DBDebugToolkit/DBDebugToolkit/Classes/Cells/DBMenuChartTableViewCell.h
  22. 32 0
      Pods/DBDebugToolkit/DBDebugToolkit/Classes/Cells/DBMenuChartTableViewCell.m
  23. 65 0
      Pods/DBDebugToolkit/DBDebugToolkit/Classes/Cells/DBMenuSegmentedControlTableViewCell.h
  24. 50 0
      Pods/DBDebugToolkit/DBDebugToolkit/Classes/Cells/DBMenuSegmentedControlTableViewCell.m
  25. 62 0
      Pods/DBDebugToolkit/DBDebugToolkit/Classes/Cells/DBMenuSwitchTableViewCell.h
  26. 42 0
      Pods/DBDebugToolkit/DBDebugToolkit/Classes/Cells/DBMenuSwitchTableViewCell.m
  27. 38 0
      Pods/DBDebugToolkit/DBDebugToolkit/Classes/Cells/DBRequestTableViewCell.h
  28. 102 0
      Pods/DBDebugToolkit/DBDebugToolkit/Classes/Cells/DBRequestTableViewCell.m
  29. 92 0
      Pods/DBDebugToolkit/DBDebugToolkit/Classes/Cells/DBSliderTableViewCell.h
  30. 73 0
      Pods/DBDebugToolkit/DBDebugToolkit/Classes/Cells/DBSliderTableViewCell.m
  31. 69 0
      Pods/DBDebugToolkit/DBDebugToolkit/Classes/Cells/DBTextViewTableViewCell.h
  32. 49 0
      Pods/DBDebugToolkit/DBDebugToolkit/Classes/Cells/DBTextViewTableViewCell.m
  33. 39 0
      Pods/DBDebugToolkit/DBDebugToolkit/Classes/Cells/TitleValue/DBTitleValueTableViewCell.h
  34. 45 0
      Pods/DBDebugToolkit/DBDebugToolkit/Classes/Cells/TitleValue/DBTitleValueTableViewCell.m
  35. 48 0
      Pods/DBDebugToolkit/DBDebugToolkit/Classes/Cells/TitleValue/DBTitleValueTableViewCellDataSource.h
  36. 50 0
      Pods/DBDebugToolkit/DBDebugToolkit/Classes/Cells/TitleValue/DBTitleValueTableViewCellDataSource.m
  37. 82 0
      Pods/DBDebugToolkit/DBDebugToolkit/Classes/Chart/DBChartView.h
  38. 147 0
      Pods/DBDebugToolkit/DBDebugToolkit/Classes/Chart/DBChartView.m
  39. 79 0
      Pods/DBDebugToolkit/DBDebugToolkit/Classes/Console/DBConsoleOutputCaptor.h
  40. 171 0
      Pods/DBDebugToolkit/DBDebugToolkit/Classes/Console/DBConsoleOutputCaptor.m
  41. 49 0
      Pods/DBDebugToolkit/DBDebugToolkit/Classes/Console/DBConsoleViewController.h
  42. 127 0
      Pods/DBDebugToolkit/DBDebugToolkit/Classes/Console/DBConsoleViewController.m
  43. 115 0
      Pods/DBDebugToolkit/DBDebugToolkit/Classes/CrashReports/DBCrashReport.h
  44. 141 0
      Pods/DBDebugToolkit/DBDebugToolkit/Classes/CrashReports/DBCrashReport.m
  45. 48 0
      Pods/DBDebugToolkit/DBDebugToolkit/Classes/CrashReports/DBCrashReportDetailsTableViewController.h
  46. 322 0
      Pods/DBDebugToolkit/DBDebugToolkit/Classes/CrashReports/DBCrashReportDetailsTableViewController.m
  47. 36 0
      Pods/DBDebugToolkit/DBDebugToolkit/Classes/CrashReports/DBCrashReportsTableViewController.h
  48. 113 0
      Pods/DBDebugToolkit/DBDebugToolkit/Classes/CrashReports/DBCrashReportsTableViewController.m
  49. 74 0
      Pods/DBDebugToolkit/DBDebugToolkit/Classes/CrashReports/DBCrashReportsToolkit.h
  50. 314 0
      Pods/DBDebugToolkit/DBDebugToolkit/Classes/CrashReports/DBCrashReportsToolkit.m
  51. 39 0
      Pods/DBDebugToolkit/DBDebugToolkit/Classes/CrashReports/DBImageViewViewController.h
  52. 63 0
      Pods/DBDebugToolkit/DBDebugToolkit/Classes/CrashReports/DBImageViewViewController.m
  53. 58 0
      Pods/DBDebugToolkit/DBDebugToolkit/Classes/CustomActions/DBCustomAction.h
  54. 58 0
      Pods/DBDebugToolkit/DBDebugToolkit/Classes/CustomActions/DBCustomAction.m
  55. 36 0
      Pods/DBDebugToolkit/DBDebugToolkit/Classes/CustomActions/DBCustomActionsTableViewController.h
  56. 76 0
      Pods/DBDebugToolkit/DBDebugToolkit/Classes/CustomActions/DBCustomActionsTableViewController.m
  57. 80 0
      Pods/DBDebugToolkit/DBDebugToolkit/Classes/CustomVariables/DBCustomVariable.h
  58. 124 0
      Pods/DBDebugToolkit/DBDebugToolkit/Classes/CustomVariables/DBCustomVariable.m
  59. 36 0
      Pods/DBDebugToolkit/DBDebugToolkit/Classes/CustomVariables/DBCustomVariablesTableViewController.h
  60. 281 0
      Pods/DBDebugToolkit/DBDebugToolkit/Classes/CustomVariables/DBCustomVariablesTableViewController.m
  61. 198 0
      Pods/DBDebugToolkit/DBDebugToolkit/Classes/DBDebugToolkit.h
  62. 497 0
      Pods/DBDebugToolkit/DBDebugToolkit/Classes/DBDebugToolkit.m
  63. 45 0
      Pods/DBDebugToolkit/DBDebugToolkit/Classes/Device/DBDeviceInfoProvider.h
  64. 114 0
      Pods/DBDebugToolkit/DBDebugToolkit/Classes/Device/DBDeviceInfoProvider.m
  65. 30 0
      Pods/DBDebugToolkit/DBDebugToolkit/Classes/Location/CLLocationManager+DBLocationToolkit.h
  66. 62 0
      Pods/DBDebugToolkit/DBDebugToolkit/Classes/Location/CLLocationManager+DBLocationToolkit.m
  67. 66 0
      Pods/DBDebugToolkit/DBDebugToolkit/Classes/Location/DBCustomLocationViewController.h
  68. 85 0
      Pods/DBDebugToolkit/DBDebugToolkit/Classes/Location/DBCustomLocationViewController.m
  69. 36 0
      Pods/DBDebugToolkit/DBDebugToolkit/Classes/Location/DBLocationTableViewController.h
  70. 181 0
      Pods/DBDebugToolkit/DBDebugToolkit/Classes/Location/DBLocationTableViewController.m
  71. 47 0
      Pods/DBDebugToolkit/DBDebugToolkit/Classes/Location/DBLocationToolkit.h
  72. 125 0
      Pods/DBDebugToolkit/DBDebugToolkit/Classes/Location/DBLocationToolkit.m
  73. 54 0
      Pods/DBDebugToolkit/DBDebugToolkit/Classes/Location/DBPresetLocation.h
  74. 44 0
      Pods/DBDebugToolkit/DBDebugToolkit/Classes/Location/DBPresetLocation.m
  75. 125 0
      Pods/DBDebugToolkit/DBDebugToolkit/Classes/Menu/DBMenuTableViewController.h
  76. 134 0
      Pods/DBDebugToolkit/DBDebugToolkit/Classes/Menu/DBMenuTableViewController.m
  77. 48 0
      Pods/DBDebugToolkit/DBDebugToolkit/Classes/Network/DBBodyPreviewViewController.h
  78. 99 0
      Pods/DBDebugToolkit/DBDebugToolkit/Classes/Network/DBBodyPreviewViewController.m
  79. 36 0
      Pods/DBDebugToolkit/DBDebugToolkit/Classes/Network/DBNetworkSettingsTableViewController.h
  80. 71 0
      Pods/DBDebugToolkit/DBDebugToolkit/Classes/Network/DBNetworkSettingsTableViewController.m
  81. 103 0
      Pods/DBDebugToolkit/DBDebugToolkit/Classes/Network/DBNetworkToolkit.h
  82. 142 0
      Pods/DBDebugToolkit/DBDebugToolkit/Classes/Network/DBNetworkToolkit.m
  83. 37 0
      Pods/DBDebugToolkit/DBDebugToolkit/Classes/Network/DBNetworkViewController.h
  84. 231 0
      Pods/DBDebugToolkit/DBDebugToolkit/Classes/Network/DBNetworkViewController.m
  85. 59 0
      Pods/DBDebugToolkit/DBDebugToolkit/Classes/Network/DBRequestDetailsViewController.h
  86. 411 0
      Pods/DBDebugToolkit/DBDebugToolkit/Classes/Network/DBRequestDetailsViewController.m
  87. 46 0
      Pods/DBDebugToolkit/DBDebugToolkit/Classes/Network/MainQueueOperation/DBMainQueueOperation.h
  88. 97 0
      Pods/DBDebugToolkit/DBDebugToolkit/Classes/Network/MainQueueOperation/DBMainQueueOperation.m
  89. 38 0
      Pods/DBDebugToolkit/DBDebugToolkit/Classes/Network/MainQueueOperation/NSOperationQueue+DBMainQueueOperation.h
  90. 32 0
      Pods/DBDebugToolkit/DBDebugToolkit/Classes/Network/MainQueueOperation/NSOperationQueue+DBMainQueueOperation.m
  91. 88 0
      Pods/DBDebugToolkit/DBDebugToolkit/Classes/Network/RequestModel/DBRequestDataHandler.h
  92. 162 0
      Pods/DBDebugToolkit/DBDebugToolkit/Classes/Network/RequestModel/DBRequestDataHandler.m
  93. 239 0
      Pods/DBDebugToolkit/DBDebugToolkit/Classes/Network/RequestModel/DBRequestModel.h
  94. 235 0
      Pods/DBDebugToolkit/DBDebugToolkit/Classes/Network/RequestModel/DBRequestModel.m
  95. 60 0
      Pods/DBDebugToolkit/DBDebugToolkit/Classes/Network/RequestModel/DBRequestOutcome.h
  96. 64 0
      Pods/DBDebugToolkit/DBDebugToolkit/Classes/Network/RequestModel/DBRequestOutcome.m
  97. 38 0
      Pods/DBDebugToolkit/DBDebugToolkit/Classes/Network/URLProtocol/DBAuthenticationChallengeSender.h
  98. 81 0
      Pods/DBDebugToolkit/DBDebugToolkit/Classes/Network/URLProtocol/DBAuthenticationChallengeSender.m
  99. 30 0
      Pods/DBDebugToolkit/DBDebugToolkit/Classes/Network/URLProtocol/DBURLProtocol.h
  100. 112 0
      Pods/DBDebugToolkit/DBDebugToolkit/Classes/Network/URLProtocol/DBURLProtocol.m

+ 2 - 0
Podfile

@@ -4,6 +4,8 @@ target 'deltachat-ios' do
   pod 'ReachabilitySwift'
   pod 'QuickTableViewController'
   pod 'JGProgressHUD'
+  pod 'SwiftyBeaver'
+  pod 'DBDebugToolkit'
   pod 'MessageKit', '2.0.0'
   post_install do |installer|
       installer.pods_project.targets.each do |target|

+ 9 - 1
Podfile.lock

@@ -1,4 +1,5 @@
 PODS:
+  - DBDebugToolkit (0.5.0)
   - JGProgressHUD (2.0.3)
   - MessageInputBar/Core (0.4.1)
   - MessageKit (2.0.0):
@@ -6,31 +7,38 @@ PODS:
   - openssl-ios-bitcode (1.0.210)
   - QuickTableViewController (1.0.0)
   - ReachabilitySwift (4.3.0)
+  - SwiftyBeaver (1.6.1)
 
 DEPENDENCIES:
+  - DBDebugToolkit
   - JGProgressHUD
   - MessageKit (= 2.0.0)
   - openssl-ios-bitcode (= 1.0.210)
   - QuickTableViewController
   - ReachabilitySwift
+  - SwiftyBeaver
 
 SPEC REPOS:
   https://github.com/cocoapods/specs.git:
+    - DBDebugToolkit
     - JGProgressHUD
     - MessageInputBar
     - MessageKit
     - openssl-ios-bitcode
     - QuickTableViewController
     - ReachabilitySwift
+    - SwiftyBeaver
 
 SPEC CHECKSUMS:
+  DBDebugToolkit: c04bb6f618051d3de447a4b4323f37826116cfed
   JGProgressHUD: 12b20a8f4ffe05258f8635c1ab92816e451f904d
   MessageInputBar: e81c7535347f1f7b923de7080409a535a004b6e4
   MessageKit: 29c1c87e5a396d2ca7a3f712e5171dc9aba42a1e
   openssl-ios-bitcode: c833701a4488bd43de0051db41cfa75f6fef8109
   QuickTableViewController: a49fb6dc5623b9dbe301a03ac5c563099e234fbc
   ReachabilitySwift: 408477d1b6ed9779dba301953171e017c31241f3
+  SwiftyBeaver: ccfcdf85a04d429f1633f668650b0ce8020bda3a
 
-PODFILE CHECKSUM: 4cc25fb37ef9036decc6e11a25a0ea70c96e2149
+PODFILE CHECKSUM: 68c148b02e1dc4050bbbf02257c1216b82641b16
 
 COCOAPODS: 1.5.3

+ 50 - 0
Pods/DBDebugToolkit/DBDebugToolkit/Classes/BuildInfo/DBBuildInfoProvider.h

@@ -0,0 +1,50 @@
+// The MIT License
+//
+// Copyright (c) 2016 Dariusz Bukowski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#import <Foundation/Foundation.h>
+
+/**
+ `DBBuildInfoProvider` provides helper methods returning information about the running build.
+ */
+@interface DBBuildInfoProvider : NSObject
+
+/**
+ Returns the application name string.
+ */
+- (NSString *)applicationName;
+
+/**
+ Returns the build version string.
+ */
+- (NSString *)buildVersion;
+
+/**
+ Returns the build number string.
+ */
+- (NSString *)buildNumber;
+
+/**
+ Returns build information in format: "<application name>, v. <build version> (<build number>)"
+ */
+- (NSString *)buildInfoString;
+
+@end

+ 51 - 0
Pods/DBDebugToolkit/DBDebugToolkit/Classes/BuildInfo/DBBuildInfoProvider.m

@@ -0,0 +1,51 @@
+// The MIT License
+//
+// Copyright (c) 2016 Dariusz Bukowski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#import "DBBuildInfoProvider.h"
+
+@implementation DBBuildInfoProvider
+
+- (NSString *)applicationName {
+    return [self infoDictionaryObjectForKey:(NSString *)kCFBundleNameKey];
+}
+
+- (NSString *)buildVersion {
+    return [self infoDictionaryObjectForKey:@"CFBundleShortVersionString"];
+}
+
+- (NSString *)buildNumber {
+    return [self infoDictionaryObjectForKey:@"CFBundleVersion"];
+}
+
+- (NSString *)buildInfoString {
+    NSString *buildInfoStringFormat = @"%@, v. %@ (%@)";
+    return [NSString stringWithFormat:buildInfoStringFormat, [self applicationName], [self buildVersion], [self buildNumber]];
+}
+
+#pragma mark - Private methods
+
+- (NSString *)infoDictionaryObjectForKey:(NSString *)key {
+    NSDictionary *infoDictionary = [[NSBundle mainBundle] infoDictionary];
+    return [infoDictionary objectForKey:key];
+}
+
+@end

+ 35 - 0
Pods/DBDebugToolkit/DBDebugToolkit/Classes/Categories/NSBundle+DBDebugToolkit.h

@@ -0,0 +1,35 @@
+// The MIT License
+//
+// Copyright (c) 2016 Dariusz Bukowski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#import <Foundation/Foundation.h>
+
+/**
+ `NSBundle` category providing methods for easier access to the library bundle.
+ */
+@interface NSBundle (DBDebugToolkit)
+
+/**
+ Returns `DBDebugToolkit` bundle.
+ */
++ (instancetype)debugToolkitBundle;
+
+@end

+ 34 - 0
Pods/DBDebugToolkit/DBDebugToolkit/Classes/Categories/NSBundle+DBDebugToolkit.m

@@ -0,0 +1,34 @@
+// The MIT License
+//
+// Copyright (c) 2016 Dariusz Bukowski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#import "NSBundle+DBDebugToolkit.h"
+#import "DBDebugToolkit.h"
+
+@implementation NSBundle (DBDebugToolkit)
+
++ (instancetype)debugToolkitBundle {
+    NSBundle *podBundle = [NSBundle bundleForClass:[DBDebugToolkit class]];
+    NSURL *bundleURL = [podBundle URLForResource:@"DBDebugToolkit" withExtension:@"bundle"];
+    return [NSBundle bundleWithURL:bundleURL];
+}
+
+@end

+ 54 - 0
Pods/DBDebugToolkit/DBDebugToolkit/Classes/Categories/NSObject+DBDebugToolkit.h

@@ -0,0 +1,54 @@
+// The MIT License
+//
+// Copyright (c) 2016 Dariusz Bukowski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#import <Foundation/Foundation.h>
+
+/**
+ `NSObject` category adding helper methods for swizzling.
+ */
+@interface NSObject (DBDebugToolkit)
+
+/**
+ Exchanges class methods between two selectors using method swizzling.
+ 
+ @param originalSelector The original selector, which is supposed to have its implementation replaced.
+ @param swizzledSelector The swizzled selector, providing the new implementation.
+ */
++ (void)exchangeClassMethodsWithOriginalSelector:(SEL)originalSelector andSwizzledSelector:(SEL)swizzledSelector;
+
+/**
+ Exchanges instance methods between two selectors using method swizzling.
+ 
+ @param originalSelector The original selector, which is supposed to have its implementation replaced.
+ @param swizzledSelector The swizzled selector, providing the new implementation.
+ */
++ (void)exchangeInstanceMethodsWithOriginalSelector:(SEL)originalSelector andSwizzledSelector:(SEL)swizzledSelector;
+
+/**
+ Replaces instance method implementation with a block.
+ 
+ @param originalSelector The original selector, which is supposed to have its implementation replaced.
+ @param block The block containing new implementation.
+ */
++ (IMP)replaceMethodWithSelector:(SEL)originalSelector block:(id)block;
+
+@end

+ 80 - 0
Pods/DBDebugToolkit/DBDebugToolkit/Classes/Categories/NSObject+DBDebugToolkit.m

@@ -0,0 +1,80 @@
+// The MIT License
+//
+// Copyright (c) 2016 Dariusz Bukowski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#import "NSObject+DBDebugToolkit.h"
+#import <objc/runtime.h>
+
+@implementation NSObject (DBDebugToolkit)
+
++ (void)exchangeClassMethodsWithOriginalSelector:(SEL)originalSelector andSwizzledSelector:(SEL)swizzledSelector {
+    [self exchangeMethodsWithOriginalSelector:originalSelector
+                             swizzledSelector:swizzledSelector
+                                 classMethods:YES];
+}
+
++ (void)exchangeInstanceMethodsWithOriginalSelector:(SEL)originalSelector andSwizzledSelector:(SEL)swizzledSelector {
+    [self exchangeMethodsWithOriginalSelector:originalSelector
+                             swizzledSelector:swizzledSelector
+                                 classMethods:NO];
+}
+
++ (IMP)replaceMethodWithSelector:(SEL)originalSelector block:(id)block {
+    NSCParameterAssert(block);
+    
+    Class class = [self class];
+    Method originalMethod = class_getInstanceMethod(class, originalSelector);
+    NSCParameterAssert(originalMethod);
+    
+    IMP newIMP = imp_implementationWithBlock(block);
+    
+    if (!class_addMethod(class, originalSelector, newIMP, method_getTypeEncoding(originalMethod))) {
+        return method_setImplementation(originalMethod, newIMP);
+    } else {
+        return method_getImplementation(originalMethod);
+    }
+}
+
+#pragma mark - Private
+
++ (void)exchangeMethodsWithOriginalSelector:(SEL)originalSelector
+                           swizzledSelector:(SEL)swizzledSelector
+                               classMethods:(BOOL)classMethods {
+    Class class = classMethods ? object_getClass((id)self) : [self class];
+    Method originalMethod = classMethods ? class_getClassMethod(class, originalSelector) : class_getInstanceMethod(class, originalSelector);
+    Method swizzledMethod = classMethods ? class_getClassMethod(class, swizzledSelector) : class_getInstanceMethod(class, swizzledSelector);
+    
+    BOOL didAddMethod = class_addMethod(class,
+                                        originalSelector,
+                                        method_getImplementation(swizzledMethod),
+                                        method_getTypeEncoding(swizzledMethod));
+    
+    if (didAddMethod) {
+        class_replaceMethod(class,
+                            swizzledSelector,
+                            method_getImplementation(originalMethod),
+                            method_getTypeEncoding(originalMethod));
+    } else {
+        method_exchangeImplementations(originalMethod, swizzledMethod);
+    }
+}
+
+@end

+ 35 - 0
Pods/DBDebugToolkit/DBDebugToolkit/Classes/Categories/UIApplication+DBDebugToolkit.h

@@ -0,0 +1,35 @@
+// The MIT License
+//
+// Copyright (c) 2017 Dariusz Bukowski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#import <UIKit/UIKit.h>
+
+/**
+ String containing the type of the `Clear data` shortcut item;
+ */
+extern NSString *const DBClearDataShortcutItemType;
+
+/**
+ `UIApplication` category modifying the delegate behavior.
+ */
+@interface UIApplication (DBDebugToolkit)
+
+@end

+ 87 - 0
Pods/DBDebugToolkit/DBDebugToolkit/Classes/Categories/UIApplication+DBDebugToolkit.m

@@ -0,0 +1,87 @@
+// The MIT License
+//
+// Copyright (c) 2017 Dariusz Bukowski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#import "UIApplication+DBDebugToolkit.h"
+#import "NSObject+DBDebugToolkit.h"
+#import "DBDebugToolkit.h"
+#import <objc/runtime.h>
+
+NSString *const DBClearDataShortcutItemType = @"DBClearDataShortcutItemType";
+
+@implementation UIApplication (DBDebugToolkit)
+
++ (void)load {
+    static dispatch_once_t onceToken;
+    dispatch_once(&onceToken, ^{
+        __block IMP originalIMP = [self replaceMethodWithSelector:@selector(setDelegate:)
+                                                            block:^(UIApplication *blockSelf, id <UIApplicationDelegate>delegate) {
+                                                                Class delegateClass = [delegate class];
+                                                                [self addClearDataShortcutHandlingForClass:delegateClass];
+                                                                ((void (*)(id, SEL, id <UIApplicationDelegate>))originalIMP)(blockSelf, @selector(setDelegate:), delegate);
+                                                            }];
+    });
+}
+
++ (void)addClearDataShortcutHandlingForClass:(Class)class {
+    static dispatch_once_t onceToken;
+    dispatch_once(&onceToken, ^{
+        if (![[NSProcessInfo processInfo] isOperatingSystemAtLeastVersion:(NSOperatingSystemVersion){9, 0, 0}]) {
+            // Shortcut items are not supported on the running iOS version.
+            return;
+        }
+
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wpartial-availability"
+        SEL selector = @selector(application:performActionForShortcutItem:completionHandler:);
+        Method originalMethod = class_getInstanceMethod(class, selector);
+        if (originalMethod) {
+            __block IMP originalIMP = [class replaceMethodWithSelector:selector
+                                                                 block:^(id <UIApplicationDelegate> blockSelf,
+                                                                         UIApplication *application,
+                                                                         UIApplicationShortcutItem *shortcutItem,
+                                                                         void (^completionHandler)(BOOL succeeded)) {
+                                                                     if ([shortcutItem.type isEqualToString:DBClearDataShortcutItemType]) {
+                                                                         [DBDebugToolkit handleClearDataShortcutItemAction];
+                                                                     }
+                                                                     ((void (*)(id, SEL, UIApplication *, UIApplicationShortcutItem *, void (^)(BOOL succeeded)))originalIMP)(blockSelf, @selector(application:performActionForShortcutItem:completionHandler:), application, shortcutItem, completionHandler);
+                                                                 }];
+        } else {
+            void (^block)(id <UIApplicationDelegate> blockSelf,
+                          UIApplication *application,
+                          UIApplicationShortcutItem *shortcutItem,
+                          void (^completionHandler)(BOOL succeeded)) = ^(id <UIApplicationDelegate> blockSelf,
+                                                                         UIApplication *application,
+                                                                         UIApplicationShortcutItem *shortcutItem,
+                                                                         void (^completionHandler)(BOOL succeeded)) {
+                if ([shortcutItem.type isEqualToString:DBClearDataShortcutItemType]) {
+                    [DBDebugToolkit handleClearDataShortcutItemAction];
+                }
+            };
+
+            IMP implementation = imp_implementationWithBlock(block);
+            class_addMethod(class, selector, implementation, "v@:@@@");
+        }
+#pragma clang diagnostic pop
+    });
+}
+
+@end

+ 37 - 0
Pods/DBDebugToolkit/DBDebugToolkit/Classes/Categories/UIColor+DBDebugToolkit.h

@@ -0,0 +1,37 @@
+// The MIT License
+//
+// Copyright (c) 2016 Dariusz Bukowski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#import <UIKit/UIKit.h>
+
+/**
+ `UIColor` category adding helper methods for custom color initialization.
+ */
+@interface UIColor (DBDebugToolkit)
+
+/**
+ Creates and returns `UIColor` instance with provided RGB values.
+ 
+ @param rgbValue Integer with red, green and blue values encoded in format 0xRRGGBB.
+ */
++ (instancetype)colorWithRGBValue:(NSInteger)rgbValue;
+
+@end

+ 34 - 0
Pods/DBDebugToolkit/DBDebugToolkit/Classes/Categories/UIColor+DBDebugToolkit.m

@@ -0,0 +1,34 @@
+// The MIT License
+//
+// Copyright (c) 2016 Dariusz Bukowski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#import "UIColor+DBDebugToolkit.h"
+
+@implementation UIColor (DBDebugToolkit)
+
++ (instancetype)colorWithRGBValue:(NSInteger)rgbValue {
+    return [UIColor colorWithRed:((CGFloat)((rgbValue & 0xFF0000) >> 16)) / 255.0
+                           green:((CGFloat)((rgbValue & 0x00FF00) >>  8)) / 255.0
+                            blue:((CGFloat)((rgbValue & 0x0000FF) >>  0)) / 255.0
+                           alpha:1.0];
+}
+
+@end

+ 35 - 0
Pods/DBDebugToolkit/DBDebugToolkit/Classes/Categories/UILabel+DBDebugToolkit.h

@@ -0,0 +1,35 @@
+// The MIT License
+//
+// Copyright (c) 2016 Dariusz Bukowski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#import <UIKit/UIKit.h>
+
+/**
+ `UILabel` category adding helper method for creating a label that informs about an empty table view.
+ */
+@interface UILabel (DBDebugToolkit)
+
+/**
+ Creates, configures and returns a label that will be used as a background for an empty table view.
+ */
++ (instancetype)tableViewBackgroundLabel;
+
+@end

+ 36 - 0
Pods/DBDebugToolkit/DBDebugToolkit/Classes/Categories/UILabel+DBDebugToolkit.m

@@ -0,0 +1,36 @@
+// The MIT License
+//
+// Copyright (c) 2016 Dariusz Bukowski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#import "UILabel+DBDebugToolkit.h"
+
+@implementation UILabel (DBDebugToolkit)
+
++ (instancetype)tableViewBackgroundLabel {
+    UILabel *label = [[UILabel alloc] init];
+    label.font = [UIFont preferredFontForTextStyle:UIFontTextStyleHeadline];
+    label.textColor = [UIColor darkGrayColor];
+    label.textAlignment = NSTextAlignmentCenter;
+    label.numberOfLines = 0;
+    return label;
+}
+
+@end

+ 33 - 0
Pods/DBDebugToolkit/DBDebugToolkit/Classes/Categories/UIView+Snapshot.h

@@ -0,0 +1,33 @@
+// The MIT License
+//
+// Copyright (c) 2017 Dariusz Bukowski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+/**
+ `UIView` category adding helper method for creating a snaphot image of the view.
+ */
+@interface UIView (Snapshot)
+
+/**
+ Creates and returns an image containing 
+ */
+- (UIImage *)snapshot;
+
+@end

+ 35 - 0
Pods/DBDebugToolkit/DBDebugToolkit/Classes/Categories/UIView+Snapshot.m

@@ -0,0 +1,35 @@
+// The MIT License
+//
+// Copyright (c) 2017 Dariusz Bukowski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#import "UIView+Snapshot.h"
+
+@implementation UIView (Snapshot)
+
+- (UIImage *)snapshot {
+    UIGraphicsBeginImageContextWithOptions(self.bounds.size, NO, [UIScreen mainScreen].scale);
+    [self drawViewHierarchyInRect:self.bounds afterScreenUpdates:NO];
+    UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
+    UIGraphicsEndImageContext();
+    return image;
+}
+
+@end

+ 67 - 0
Pods/DBDebugToolkit/DBDebugToolkit/Classes/Cells/ColorPicker/DBColorCheckbox.h

@@ -0,0 +1,67 @@
+// The MIT License
+//
+// Copyright (c) 2016 Dariusz Bukowski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#import <UIKit/UIKit.h>
+
+@class DBColorCheckbox;
+
+/**
+ A protocol used for informing about the checkbox state.
+ */
+@protocol DBColorCheckboxDelegate
+
+/**
+ Informs the delegate that the checkbox value was changed.
+ 
+ @param colorCheckbox The checkbox that changed its value.
+ @param newValue The new checkbox value.
+ */
+- (void)colorCheckbox:(DBColorCheckbox *)colorCheckbox didChangeValue:(BOOL)newValue;
+
+@end
+
+/**
+ `DBColorCheckbox` is a simple `UIControl` subclass designed for selecting a specified color.
+ */
+@interface DBColorCheckbox : UIControl
+
+/**
+ Delegate that will be informed about the checkbox value. It needs to conform to `DBColorCheckboxDelegate` protocol.
+ */
+@property (nonatomic, weak) id <DBColorCheckboxDelegate> delegate;
+
+/**
+ Current value of the checkbox.
+ */
+@property (nonatomic, assign) BOOL isChecked;
+
+/**
+ Color selected by the checkbox.
+ */
+@property (nonatomic, strong) UIColor *color;
+
+/**
+ Color used for drawing the check mark.
+ */
+@property (nonatomic, strong) UIColor *checkMarkColor;
+
+@end

+ 91 - 0
Pods/DBDebugToolkit/DBDebugToolkit/Classes/Cells/ColorPicker/DBColorCheckbox.m

@@ -0,0 +1,91 @@
+// The MIT License
+//
+// Copyright (c) 2016 Dariusz Bukowski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#import "DBColorCheckbox.h"
+
+@implementation DBColorCheckbox
+
+#pragma mark - Drawing
+
+- (void)layoutSubviews {
+    [super layoutSubviews];
+    self.layer.cornerRadius = self.frame.size.width / 2.0;
+    self.clipsToBounds = YES;
+}
+
+- (void)drawRect:(CGRect)rect {
+    [super drawRect:rect];
+    if (self.isChecked) {
+        [self.checkMarkColor setStroke];
+        // Based on code generated by PaintCode, mentioned here: https://stackoverflow.com/a/19332828
+        CGRect checkMarkRect = CGRectInset(self.bounds, 6.0, 6.0);
+        UIBezierPath *bezierPath = [UIBezierPath bezierPath];
+        [bezierPath moveToPoint:CGPointMake(CGRectGetMinX(checkMarkRect) + 0.27083 * CGRectGetWidth(checkMarkRect),
+                                            CGRectGetMinY(checkMarkRect) + 0.54167 * CGRectGetHeight(checkMarkRect))];
+        [bezierPath addLineToPoint:CGPointMake(CGRectGetMinX(checkMarkRect) + 0.41667 * CGRectGetWidth(checkMarkRect),
+                                               CGRectGetMinY(checkMarkRect) + 0.68750 * CGRectGetHeight(checkMarkRect))];
+        [bezierPath addLineToPoint: CGPointMake(CGRectGetMinX(checkMarkRect) + 0.75000 * CGRectGetWidth(checkMarkRect),
+                                                CGRectGetMinY(checkMarkRect) + 0.35417 * CGRectGetHeight(checkMarkRect))];
+        bezierPath.lineCapStyle = kCGLineCapSquare;
+        bezierPath.lineWidth = 1.3;
+        [bezierPath stroke];
+    }
+}
+
+#pragma mark - Handling touches
+
+- (BOOL)beginTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event {
+    self.isChecked = true;
+    return YES;
+}
+
+#pragma mark - Property setters
+
+- (void)setColor:(UIColor *)color {
+    _color = color;
+    self.backgroundColor = color;
+    [self setupBorderIfNeeded];
+}
+
+- (void)setCheckMarkColor:(UIColor *)checkMarkColor {
+    _checkMarkColor = checkMarkColor;
+    [self setupBorderIfNeeded];
+}
+
+- (void)setIsChecked:(BOOL)isChecked {
+    if (isChecked != _isChecked) {
+        _isChecked = isChecked;
+        [self.delegate colorCheckbox:self didChangeValue:isChecked];
+        [self setNeedsDisplay];
+    }
+}
+
+#pragma mark - Private methods
+
+- (void)setupBorderIfNeeded {
+    if (self.color == [UIColor whiteColor]) {
+        self.layer.borderWidth = 1.0;
+        self.layer.borderColor = self.checkMarkColor.CGColor;
+    }
+}
+
+@end

+ 68 - 0
Pods/DBDebugToolkit/DBDebugToolkit/Classes/Cells/ColorPicker/DBColorPickerTableViewCell.h

@@ -0,0 +1,68 @@
+// The MIT License
+//
+// Copyright (c) 2016 Dariusz Bukowski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#import <UIKit/UIKit.h>
+
+@class DBColorPickerTableViewCell;
+
+/**
+ A protocol used for informing about the selected color.
+ */
+@protocol DBColorPickerTableViewCellDelegate
+
+/**
+ Informs the delegate about the selected color index.
+ 
+ @param colorPickerCell The cell with a color picker that changed the selected color.
+ @param index The index of the selected color.
+ */
+- (void)colorPickerCell:(DBColorPickerTableViewCell *)colorPickerCell didSelectColorAtIndex:(NSInteger)index;
+
+@end
+
+/**
+ `DBColorPickerTableViewCell` is a table view cell subclass with a title label, and multiple color checkboxes.
+ */
+@interface DBColorPickerTableViewCell : UITableViewCell
+
+/**
+ An outlet to `UILabel` instance displaying the title of the value changed with the color picker.
+ */
+@property (nonatomic, weak) IBOutlet UILabel *titleLabel;
+
+/**
+ Delegate that will be informed about the selected color index. It needs to conform to `DBColorPickerTableViewCellDelegate` protocol.
+ */
+@property (nonatomic, weak) id <DBColorPickerTableViewCellDelegate> delegate;
+
+/**
+ Configures the cell with specified colors and the index of the selected color.
+ 
+ @param primaryColors An array of colors that can be selected in the cell.
+ @param secondaryColors An array of colors used for drawing the checkmarks on the the checkboxes.
+ @param selectedIndex The index of the currently selected color.
+ */
+- (void)configureWithPrimaryColors:(NSArray <UIColor *> *)primaryColors
+                   secondaryColors:(NSArray <UIColor *> *)secondaryColors
+                     selectedIndex:(NSInteger)selectedIndex;
+
+@end

+ 88 - 0
Pods/DBDebugToolkit/DBDebugToolkit/Classes/Cells/ColorPicker/DBColorPickerTableViewCell.m

@@ -0,0 +1,88 @@
+// The MIT License
+//
+// Copyright (c) 2016 Dariusz Bukowski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#import "DBColorPickerTableViewCell.h"
+#import "DBColorCheckbox.h"
+
+@interface DBColorPickerTableViewCell () <DBColorCheckboxDelegate>
+
+@property (nonatomic, weak) IBOutlet DBColorCheckbox *colorCheckbox1;
+@property (nonatomic, weak) IBOutlet DBColorCheckbox *colorCheckbox2;
+@property (nonatomic, weak) IBOutlet DBColorCheckbox *colorCheckbox3;
+@property (nonatomic, weak) IBOutlet DBColorCheckbox *colorCheckbox4;
+@property (nonatomic, weak) IBOutlet DBColorCheckbox *colorCheckbox5;
+@property (nonatomic, weak) IBOutlet DBColorCheckbox *colorCheckbox6;
+@property (nonatomic, weak) IBOutlet DBColorCheckbox *colorCheckbox7;
+@property (nonatomic, weak) IBOutlet DBColorCheckbox *colorCheckbox8;
+@property (nonatomic, weak) IBOutlet DBColorCheckbox *colorCheckbox9;
+@property (nonatomic, weak) IBOutlet DBColorCheckbox *colorCheckbox10;
+@property (nonatomic, assign) NSInteger selectedIndex;
+
+@end
+
+@implementation DBColorPickerTableViewCell
+
+- (void)configureWithPrimaryColors:(NSArray<UIColor *> *)primaryColors
+                   secondaryColors:(NSArray<UIColor *> *)secondaryColors
+                     selectedIndex:(NSInteger)selectedIndex {
+    NSArray <DBColorCheckbox *> *colorCheckboxes = [self colorCheckboxes];
+    [colorCheckboxes enumerateObjectsUsingBlock:^(DBColorCheckbox * _Nonnull checkbox, NSUInteger index, BOOL * _Nonnull stop) {
+        checkbox.color = primaryColors[index];
+        checkbox.checkMarkColor = secondaryColors[index];
+        checkbox.delegate = self;
+    }];
+    self.selectedIndex = selectedIndex;
+}
+
+#pragma mark - Private methods
+
+- (NSArray <DBColorCheckbox *> *)colorCheckboxes {
+    return @[self.colorCheckbox1,
+             self.colorCheckbox2,
+             self.colorCheckbox3,
+             self.colorCheckbox4,
+             self.colorCheckbox5,
+             self.colorCheckbox6,
+             self.colorCheckbox7,
+             self.colorCheckbox8,
+             self.colorCheckbox9,
+             self.colorCheckbox10];
+}
+
+- (void)setSelectedIndex:(NSInteger)selectedIndex {
+    _selectedIndex = selectedIndex;
+    NSArray <DBColorCheckbox *> *colorCheckboxes = [self colorCheckboxes];
+    [colorCheckboxes enumerateObjectsUsingBlock:^(DBColorCheckbox * _Nonnull checkbox, NSUInteger index, BOOL * _Nonnull stop) {
+        checkbox.isChecked = index == selectedIndex;
+    }];
+    [self.delegate colorPickerCell:self didSelectColorAtIndex:selectedIndex];
+}
+
+#pragma mark - DBColorCheckboxDelegate
+
+- (void)colorCheckbox:(DBColorCheckbox *)colorCheckbox didChangeValue:(BOOL)newValue {
+    if (newValue == YES) {
+        self.selectedIndex = [[self colorCheckboxes] indexOfObject:colorCheckbox];
+    }
+}
+
+@end

+ 36 - 0
Pods/DBDebugToolkit/DBDebugToolkit/Classes/Cells/DBMenuChartTableViewCell.h

@@ -0,0 +1,36 @@
+// The MIT License
+//
+// Copyright (c) 2016 Dariusz Bukowski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#import <UIKit/UIKit.h>
+#import "DBChartView.h"
+
+/**
+ `DBMenuChartTableViewCell` is a simple table view cell with a `DBChartView` instance as its only subview.
+ */
+@interface DBMenuChartTableViewCell : UITableViewCell
+
+/**
+ An outlet to the `DBChartView` instance.
+ */
+@property (nonatomic, strong) IBOutlet DBChartView *chartView;
+
+@end

+ 32 - 0
Pods/DBDebugToolkit/DBDebugToolkit/Classes/Cells/DBMenuChartTableViewCell.m

@@ -0,0 +1,32 @@
+// The MIT License
+//
+// Copyright (c) 2016 Dariusz Bukowski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#import "DBMenuChartTableViewCell.h"
+
+@implementation DBMenuChartTableViewCell
+
+- (void)awakeFromNib {
+    [super awakeFromNib];
+    self.selectionStyle = UITableViewCellSelectionStyleNone;
+}
+
+@end

+ 65 - 0
Pods/DBDebugToolkit/DBDebugToolkit/Classes/Cells/DBMenuSegmentedControlTableViewCell.h

@@ -0,0 +1,65 @@
+// The MIT License
+//
+// Copyright (c) 2016 Dariusz Bukowski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#import <UIKit/UIKit.h>
+
+@class DBMenuSegmentedControlTableViewCell;
+
+/**
+ A protocol used for informing about changes in cell's segmented control.
+ */
+@protocol DBMenuSegmentedControlTableViewCellDelegate <NSObject>
+
+/**
+ Informs the delegate that the selected segment was changed in the cell.
+ 
+ @param menuSegmentedControlTableViewCell The cell with a segmented control that changed selected segment.
+ @param index The selected segment index.
+ */
+- (void)menuSegmentedControlTableViewCell:(DBMenuSegmentedControlTableViewCell *)menuSegmentedControlTableViewCell didSelectSegmentAtIndex:(NSUInteger)index;
+
+@end
+
+/**
+ `DBMenuSegmentedControlTableViewCell` is a simple table view cell subclass with a centered `UISegmentedControl`.
+ */
+@interface DBMenuSegmentedControlTableViewCell : UITableViewCell
+
+/**
+ Delegate that will be informed about changes in segmented control. It needs to conform to `DBMenuSegmentedControlTableViewCellDelegate` protocol.
+ */
+@property (nonatomic, weak) id <DBMenuSegmentedControlTableViewCellDelegate> delegate;
+
+/**
+ An outlet to `UISegmentedControl` instance.
+ */
+@property (nonatomic, strong) IBOutlet UISegmentedControl *segmentedControl;
+
+/**
+ Configures the cell with segment titles and the selected segment index.
+ 
+ @param titles Titles for segments.
+ @param selectedIndex The initial selected segment index.
+ */
+- (void)configureWithTitles:(NSArray <NSString *> *)titles selectedIndex:(NSUInteger)selectedIndex;
+
+@end

+ 50 - 0
Pods/DBDebugToolkit/DBDebugToolkit/Classes/Cells/DBMenuSegmentedControlTableViewCell.m

@@ -0,0 +1,50 @@
+// The MIT License
+//
+// Copyright (c) 2016 Dariusz Bukowski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#import "DBMenuSegmentedControlTableViewCell.h"
+
+@implementation DBMenuSegmentedControlTableViewCell
+
+#pragma mark - Initialization
+
+- (void)awakeFromNib {
+    [super awakeFromNib];
+    self.selectionStyle = UITableViewCellSelectionStyleNone;
+    [self.segmentedControl addTarget:self action:@selector(segmentedControlValueChanged:) forControlEvents:UIControlEventValueChanged];
+}
+
+#pragma mark - Segmented control
+
+- (void)segmentedControlValueChanged:(UISegmentedControl *)segmentedControl {
+    NSUInteger selectedIndex = segmentedControl.selectedSegmentIndex;
+    [self.delegate menuSegmentedControlTableViewCell:self didSelectSegmentAtIndex:selectedIndex];
+}
+
+- (void)configureWithTitles:(NSArray<NSString *> *)titles selectedIndex:(NSUInteger)selectedIndex {
+    [self.segmentedControl removeAllSegments];
+    [titles enumerateObjectsUsingBlock:^(NSString *title, NSUInteger idx, BOOL *stop) {
+        [self.segmentedControl insertSegmentWithTitle:title atIndex:idx animated:false];
+    }];
+    [self.segmentedControl setSelectedSegmentIndex:selectedIndex];
+}
+
+@end

+ 62 - 0
Pods/DBDebugToolkit/DBDebugToolkit/Classes/Cells/DBMenuSwitchTableViewCell.h

@@ -0,0 +1,62 @@
+// The MIT License
+//
+// Copyright (c) 2016 Dariusz Bukowski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#import <UIKit/UIKit.h>
+
+@class DBMenuSwitchTableViewCell;
+
+/**
+ A protocol used for informing about changes in cell's switch.
+ */
+@protocol DBMenuSwitchTableViewCellDelegate <NSObject>
+
+/**
+ Informs the delegate that the switch value was changed in the cell.
+ 
+ @param menuSwitchTableViewCell The cell with a switch that changed its value.
+ @param isOn The new switch value.
+ */
+- (void)menuSwitchTableViewCell:(DBMenuSwitchTableViewCell *)menuSwitchTableViewCell didSetOn:(BOOL)isOn;
+
+@end
+
+/**
+ `DBMenuSwitchTableViewCell` is a simple table view cell subclass with a title label and a `UISwitch` instance.
+ */
+@interface DBMenuSwitchTableViewCell : UITableViewCell
+
+/**
+ Delegate that will be informed about switch value changes. It needs to conform to `DBMenuSwitchTableViewCellDelegate` protocol.
+ */
+@property (nonatomic, weak) id <DBMenuSwitchTableViewCellDelegate> delegate;
+
+/**
+ An outlet to `UILabel` instance displaying the title of the value changed with the switch.
+ */
+@property (nonatomic, strong) IBOutlet UILabel *titleLabel;
+
+/**
+ An outlet to `UISwitch` instance.
+ */
+@property (nonatomic, strong) IBOutlet UISwitch *valueSwitch;
+
+@end

+ 42 - 0
Pods/DBDebugToolkit/DBDebugToolkit/Classes/Cells/DBMenuSwitchTableViewCell.m

@@ -0,0 +1,42 @@
+// The MIT License
+//
+// Copyright (c) 2016 Dariusz Bukowski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#import "DBMenuSwitchTableViewCell.h"
+
+@implementation DBMenuSwitchTableViewCell
+
+#pragma mark - Initialization
+
+- (void)awakeFromNib {
+    [super awakeFromNib];
+    self.selectionStyle = UITableViewCellSelectionStyleNone;
+    [self.valueSwitch addTarget:self action:@selector(switchValueChanged:) forControlEvents:UIControlEventValueChanged];
+}
+
+#pragma mark - Switch
+
+- (void)switchValueChanged:(UISwitch *)sender {
+    BOOL newValue = sender.isOn;
+    [self.delegate menuSwitchTableViewCell:self didSetOn:newValue];
+}
+
+@end

+ 38 - 0
Pods/DBDebugToolkit/DBDebugToolkit/Classes/Cells/DBRequestTableViewCell.h

@@ -0,0 +1,38 @@
+// The MIT License
+//
+// Copyright (c) 2016 Dariusz Bukowski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#import <UIKit/UIKit.h>
+#import "DBRequestModel.h"
+
+/**
+ `DBRequestTableViewCell` is a table view cell displaying information about a logged request.
+ */
+@interface DBRequestTableViewCell : UITableViewCell
+
+/**
+ Configures the cell with a `DBRequestModel` instance.
+ 
+ @param requestModel `DBRequestModel` instance containing all the information about the logged request.
+ */
+- (void)configureWithRequestModel:(DBRequestModel *)requestModel;
+
+@end

+ 102 - 0
Pods/DBDebugToolkit/DBDebugToolkit/Classes/Cells/DBRequestTableViewCell.m

@@ -0,0 +1,102 @@
+// The MIT License
+//
+// Copyright (c) 2016 Dariusz Bukowski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#import "DBRequestTableViewCell.h"
+
+@interface DBRequestTableViewCell ()
+
+@property (weak, nonatomic) IBOutlet UIImageView *thumbnailImageView;
+@property (weak, nonatomic) IBOutlet UILabel *responseTypeLabel;
+@property (weak, nonatomic) IBOutlet UIActivityIndicatorView *activityIndicator;
+@property (weak, nonatomic) IBOutlet UILabel *httpMethodLabel;
+@property (weak, nonatomic) IBOutlet UILabel *resourcePathLabel;
+@property (weak, nonatomic) IBOutlet NSLayoutConstraint *resourcePathLabelLeadingConstraint;
+@property (weak, nonatomic) IBOutlet UILabel *hostLabel;
+@property (weak, nonatomic) IBOutlet UILabel *responseDetailsLabel;
+
+@end
+
+@implementation DBRequestTableViewCell
+
+- (void)configureWithRequestModel:(DBRequestModel *)requestModel {
+    [self configureThumbnailViewWithRequestModel:requestModel];
+    [self configureRequestLabelsWithRequestModel:requestModel];
+    [self configureResponseLabelWithRequestModel:requestModel];
+}
+
+#pragma mark - Private methods
+
+- (void)configureThumbnailViewWithRequestModel:(DBRequestModel *)requestModel {
+    [self.activityIndicator stopAnimating];
+    self.activityIndicator.alpha = 0.0;
+    self.responseTypeLabel.alpha = 0.0;
+    self.thumbnailImageView.alpha = 0.0;
+    if (requestModel.didFinishWithError) {
+        self.responseTypeLabel.text = @"ERROR";
+        self.responseTypeLabel.alpha = 1.0;
+    } else if (requestModel.responseBodySynchronizationStatus != DBRequestModelBodySynchronizationStatusFinished) {
+        [self.activityIndicator startAnimating];
+        self.activityIndicator.alpha = 1.0;
+    } else {
+        if (requestModel.responseBodyType == DBRequestModelBodyTypeImage) {
+            self.thumbnailImageView.image = requestModel.thumbnail;
+            self.thumbnailImageView.alpha = 1.0;
+        } else {
+            self.responseTypeLabel.text = requestModel.responseBodyType == DBRequestModelBodyTypeJSON ? @"JSON" : @"TEXT";
+            self.responseTypeLabel.alpha = 1.0;
+        }
+    }
+}
+
+- (void)configureRequestLabelsWithRequestModel:(DBRequestModel *)requestModel {
+    if (requestModel.httpMethod.length > 0) {
+        self.httpMethodLabel.text = requestModel.httpMethod.uppercaseString;
+        self.resourcePathLabelLeadingConstraint.constant = 4;
+    } else {
+        self.httpMethodLabel.text = nil;
+        self.resourcePathLabelLeadingConstraint.constant = 0;
+    }
+    self.resourcePathLabel.text = requestModel.url.relativePath;
+    self.hostLabel.text = requestModel.url.host;
+}
+
+- (void)configureResponseLabelWithRequestModel:(DBRequestModel *)requestModel {
+    if (!requestModel.finished) {
+        NSString *dateString = [NSDateFormatter localizedStringFromDate:requestModel.sendingDate
+                                                              dateStyle:NSDateFormatterMediumStyle
+                                                              timeStyle:NSDateFormatterMediumStyle];
+        self.responseDetailsLabel.text = [NSString stringWithFormat:@"Sent at %@...", dateString];
+    } else if (requestModel.didFinishWithError) {
+        self.responseDetailsLabel.text = [NSString stringWithFormat:@"Error %ld: %@", (long)requestModel.errorCode, requestModel.localizedErrorDescription];
+    } else {
+        NSMutableString *responseString = [NSMutableString stringWithFormat:@"%.2lfs", requestModel.duration];
+        if (requestModel.statusCode) {
+            [responseString appendFormat:@", HTTP %@", requestModel.statusCode];
+            if (requestModel.localizedStatusCodeString) {
+                [responseString appendFormat:@" - %@", requestModel.localizedStatusCodeString];
+            }
+        }
+        self.responseDetailsLabel.text = responseString;
+    }
+}
+
+@end

+ 92 - 0
Pods/DBDebugToolkit/DBDebugToolkit/Classes/Cells/DBSliderTableViewCell.h

@@ -0,0 +1,92 @@
+// The MIT License
+//
+// Copyright (c) 2016 Dariusz Bukowski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#import <UIKit/UIKit.h>
+
+@class DBSliderTableViewCell;
+
+/**
+ A protocol used for informing about cell's slider events.
+ */
+@protocol DBSliderTableViewCellDelegate
+
+/**
+ Informs the delegate that the slider value was changed in the cell.
+ 
+ @param sliderCell The cell with a slider that changed its value.
+ @param value The new slider value.
+ */
+- (void)sliderCell:(DBSliderTableViewCell *)sliderCell didSelectValue:(NSInteger)value;
+
+/**
+ Informs the delegate that the slider did start editing.
+ 
+ @param sliderCell The cell with a slider that started editing.
+ */
+- (void)sliderCellDidStartEditingValue:(DBSliderTableViewCell *)sliderCell;
+
+/**
+ Informs the delegate that the slider did end editing.
+ 
+ @param sliderCell The cell with a slider that ended editing.
+ */
+- (void)sliderCellDidEndEditingValue:(DBSliderTableViewCell *)sliderCell;
+
+@end
+
+/**
+ `DBSliderTableViewCell` is a simple table view cell subclass with a title label, a `UISlider` instance and a value label.
+ */
+@interface DBSliderTableViewCell : UITableViewCell
+
+/**
+ Delegate that will be informed about slider events. It needs to conform to `DBSliderTableViewCellDelegate` protocol.
+ */
+@property (nonatomic, weak) id <DBSliderTableViewCellDelegate> delegate;
+
+/**
+ An outlet to `UILabel` instance displaying the title of the value changed with the slider.
+ */
+@property (nonatomic, weak) IBOutlet UILabel *titleLabel;
+
+/**
+ Sets a specified value on the slider.
+ 
+ @param value Selected value.
+ */
+- (void)setValue:(NSInteger)value;
+
+/**
+ Sets a minimum value of the slider.
+ 
+ @param minValue Minimum value of the slider.
+ */
+- (void)setMinValue:(NSInteger)minValue;
+
+/**
+ Sets a maximum value of the slider.
+ 
+ @param maxValue Maximum value of the slider.
+ */
+- (void)setMaxValue:(NSInteger)maxValue;
+
+@end

+ 73 - 0
Pods/DBDebugToolkit/DBDebugToolkit/Classes/Cells/DBSliderTableViewCell.m

@@ -0,0 +1,73 @@
+// The MIT License
+//
+// Copyright (c) 2016 Dariusz Bukowski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#import "DBSliderTableViewCell.h"
+
+@interface DBSliderTableViewCell ()
+
+@property (nonatomic, weak) IBOutlet UILabel *valueLabel;
+@property (nonatomic, weak) IBOutlet UISlider *slider;
+
+@end
+
+@implementation DBSliderTableViewCell
+
+- (void)awakeFromNib {
+    [super awakeFromNib];
+    [self.slider addTarget:self action:@selector(sliderValueChanged:event:) forControlEvents:UIControlEventValueChanged];
+}
+
+#pragma mark - UISlider properties
+
+- (void)setValue:(NSInteger)value {
+    self.slider.value = (NSInteger)value;
+    self.valueLabel.text = [NSString stringWithFormat:@"%ld", (long)value];
+}
+
+- (void)setMinValue:(NSInteger)minValue {
+    self.slider.minimumValue = (NSInteger)minValue;
+}
+
+- (void)setMaxValue:(NSInteger)maxValue {
+    self.slider.maximumValue = (NSInteger)maxValue;
+}
+
+#pragma mark - UISlider callbacks
+
+- (void)sliderValueChanged:(id)sender event:(UIEvent*)event {
+    UITouch *touchEvent = event.allTouches.anyObject;
+    switch (touchEvent.phase) {
+        case UITouchPhaseBegan:
+            [self.delegate sliderCellDidStartEditingValue:self];
+            break;
+        case UITouchPhaseEnded:
+            [self.delegate sliderCellDidEndEditingValue:self];
+            break;
+        default:
+            break;
+    }
+    NSInteger value = (NSInteger)self.slider.value;
+    self.valueLabel.text = [NSString stringWithFormat:@"%ld", (long)value];
+    [self.delegate sliderCell:self didSelectValue:value];
+}
+
+@end

+ 69 - 0
Pods/DBDebugToolkit/DBDebugToolkit/Classes/Cells/DBTextViewTableViewCell.h

@@ -0,0 +1,69 @@
+// The MIT License
+//
+// Copyright (c) 2016 Dariusz Bukowski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#import <UIKit/UIKit.h>
+
+@class DBTextViewTableViewCell;
+
+/**
+ A protocol used for passing the text view delegate methods.
+ */
+@protocol DBTextViewTableViewCellDelegate <NSObject>
+
+/**
+ Informs the delegate that the text value was changed in the cell.
+ 
+ @param textViewCell The cell with a text view that changed its value.
+ */
+- (void)textViewTableViewCellDidChangeText:(DBTextViewTableViewCell *)textViewCell;
+
+/**
+ Asks the delegate if the text can be changed to the given value in the text view.
+ 
+ @param textViewCell The cell with a text view that requires new value validation.
+ @param text The new text value that requires validation.
+ */
+- (BOOL)textViewTableViewCell:(DBTextViewTableViewCell *)textViewCell shouldChangeTextTo:(NSString *)text;
+
+@end
+
+/**
+ `DBTextViewTableViewCell` is a table view cell displaying a title and a text view allowing the user to input a multiline content.
+ */
+@interface DBTextViewTableViewCell : UITableViewCell
+
+/**
+ An outlet to `UILabel` instance displaying the title of the value contained in the text view.
+ */
+@property (nonatomic, weak) IBOutlet UILabel *titleLabel;
+
+/**
+ An outlet to `UITextView` instance allowing the user to provide a multiline content.
+ */
+@property (nonatomic, weak) IBOutlet UITextView *textView;
+
+/**
+ Delegate that will be responsible for handling some of the `UITextViewDelegate` methods. It needs to conform to `DBTextViewTableViewCellDelegate` protocol.
+ */
+@property (nonatomic, weak) id <DBTextViewTableViewCellDelegate> delegate;
+
+@end

+ 49 - 0
Pods/DBDebugToolkit/DBDebugToolkit/Classes/Cells/DBTextViewTableViewCell.m

@@ -0,0 +1,49 @@
+// The MIT License
+//
+// Copyright (c) 2016 Dariusz Bukowski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#import "DBTextViewTableViewCell.h"
+
+@interface DBTextViewTableViewCell () <UITextViewDelegate>
+
+@end
+
+@implementation DBTextViewTableViewCell
+
+- (void)awakeFromNib {
+    [super awakeFromNib];
+    self.textView.textContainer.lineFragmentPadding = 0;
+    self.textView.textContainerInset = UIEdgeInsetsZero;
+    self.textView.delegate = self;
+}
+
+#pragma mark - UITextViewDelegate
+
+- (void)textViewDidChange:(UITextView *)textView {
+    [self.delegate textViewTableViewCellDidChangeText:self];
+}
+
+- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text {
+    NSString *resultText = [textView.text stringByReplacingCharactersInRange:range withString:text];
+    return [self.delegate textViewTableViewCell:self shouldChangeTextTo:resultText];
+}
+
+@end

+ 39 - 0
Pods/DBDebugToolkit/DBDebugToolkit/Classes/Cells/TitleValue/DBTitleValueTableViewCell.h

@@ -0,0 +1,39 @@
+// The MIT License
+//
+// Copyright (c) 2016 Dariusz Bukowski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#import <UIKit/UIKit.h>
+#import "DBTitleValueTableViewCellDataSource.h"
+
+/**
+ `DBTitleValueTableViewCell` is a table view cell displaying two labels for a title and a value. 
+ It is designed for handling a multiline value string.
+ */
+@interface DBTitleValueTableViewCell : UITableViewCell
+
+/**
+ Configures the cell with a `DBTitleValueTableViewCellDataSource` instance.
+ 
+ @param dataSource `DBTitleValueTableViewCellDataSource` instance containing title and value.
+ */
+- (void)configureWithDataSource:(DBTitleValueTableViewCellDataSource *)dataSource;
+
+@end

+ 45 - 0
Pods/DBDebugToolkit/DBDebugToolkit/Classes/Cells/TitleValue/DBTitleValueTableViewCell.m

@@ -0,0 +1,45 @@
+// The MIT License
+//
+// Copyright (c) 2016 Dariusz Bukowski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#import "DBTitleValueTableViewCell.h"
+
+@interface DBTitleValueTableViewCell ()
+
+@property (weak, nonatomic) IBOutlet UILabel *titleLabel;
+@property (weak, nonatomic) IBOutlet UITextView *valueTextView;
+
+@end
+
+@implementation DBTitleValueTableViewCell
+
+- (void)awakeFromNib {
+    [super awakeFromNib];
+    self.valueTextView.textContainer.lineFragmentPadding = 0;
+    self.valueTextView.textContainerInset = UIEdgeInsetsZero;
+}
+
+- (void)configureWithDataSource:(DBTitleValueTableViewCellDataSource *)dataSource {
+    self.titleLabel.text = dataSource.title;
+    self.valueTextView.text = dataSource.value;
+}
+
+@end

+ 48 - 0
Pods/DBDebugToolkit/DBDebugToolkit/Classes/Cells/TitleValue/DBTitleValueTableViewCellDataSource.h

@@ -0,0 +1,48 @@
+// The MIT License
+//
+// Copyright (c) 2016 Dariusz Bukowski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#import <Foundation/Foundation.h>
+
+/**
+ `DBTitleValueTableViewCellDataSource` is a simple object containing all the data needed for `DBTitleValueTableViewCell` instance configuration.
+ */
+@interface DBTitleValueTableViewCellDataSource : NSObject
+
+/**
+ Creates and returns a new `DBTitleValueTableViewCellDataSource` instance with provided title and value.
+ 
+ @param title Title string.
+ @param value Value string.
+ */
++ (instancetype)dataSourceWithTitle:(NSString *)title value:(NSString *)value;
+
+/**
+ `NSString` object containing the title.
+ */
+@property (nonatomic, readonly) NSString *title;
+
+/**
+ `NSString` object containing the value.
+ */
+@property (nonatomic, readonly) NSString *value;
+
+@end

+ 50 - 0
Pods/DBDebugToolkit/DBDebugToolkit/Classes/Cells/TitleValue/DBTitleValueTableViewCellDataSource.m

@@ -0,0 +1,50 @@
+// The MIT License
+//
+// Copyright (c) 2016 Dariusz Bukowski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#import "DBTitleValueTableViewCellDataSource.h"
+
+@interface DBTitleValueTableViewCellDataSource ()
+
+@property (nonatomic, copy) NSString *title;
+@property (nonatomic, copy) NSString *value;
+
+@end
+
+@implementation DBTitleValueTableViewCellDataSource
+
+#pragma mark - Initialization
+
+- (instancetype)initWithTitle:(NSString *)title value:(NSString *)value {
+    self = [super init];
+    if (self) {
+        self.title = title;
+        self.value = value;
+    }
+    
+    return self;
+}
+
++ (instancetype)dataSourceWithTitle:(NSString *)title value:(NSString *)value {
+    return [[self alloc] initWithTitle:title value:value];
+}
+
+@end

+ 82 - 0
Pods/DBDebugToolkit/DBDebugToolkit/Classes/Chart/DBChartView.h

@@ -0,0 +1,82 @@
+// The MIT License
+//
+// Copyright (c) 2016 Dariusz Bukowski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#import <UIKit/UIKit.h>
+
+/**
+ `DBChartView` is a `UIView` subclass that draws a highly customizable chart.
+ */
+IB_DESIGNABLE
+@interface DBChartView : UIView
+
+/**
+ An array of values that should be displayed on the chart.
+ */
+@property (nonatomic, strong) NSArray *measurements;
+
+/**
+ Time in seconds between measurements.
+ */
+@property (nonatomic, assign) NSTimeInterval measurementInterval;
+
+/**
+ Time in seconds between values marked on the x-axis.
+ */
+@property (nonatomic, assign) NSTimeInterval markedTimesInterval;
+
+/**
+ A number specifying the limit of values presented on the chart. 
+ Useful for presenting low numbers of measurements with proper scale.
+ */
+@property (nonatomic, assign) NSInteger measurementsLimit;
+
+/**
+ Maximal value of measurements. It's used to present values with proper scale.
+ */
+@property (nonatomic, assign) CGFloat maxValue;
+
+/**
+ A single value that will be marked on the y-axis.
+ */
+@property (nonatomic, assign) CGFloat markedValue;
+
+/**
+ A string format for displaying the marked value next to the y-axis.
+ */
+@property (nonatomic, strong) NSString *markedValueFormat;
+
+/**
+ The color of the chart.
+ */
+@property (nonatomic, strong) IBInspectable UIColor *chartColor;
+
+/**
+ The color of the drawn axes and the marked value.
+ */
+@property (nonatomic, strong) IBInspectable UIColor *axisColor;
+
+/**
+ Boolean property that determines whether the area beneath the plot should be filled.
+ */
+@property (nonatomic, assign) IBInspectable BOOL filled;
+
+@end

+ 147 - 0
Pods/DBDebugToolkit/DBDebugToolkit/Classes/Chart/DBChartView.m

@@ -0,0 +1,147 @@
+// The MIT License
+//
+// Copyright (c) 2016 Dariusz Bukowski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#import "DBChartView.h"
+
+static const CGFloat DBChartViewSpaceForMarkedValue = 30;
+static const CGFloat DBChartViewAdditionalLinesDistanceFromAxis = 4;
+static const CGFloat DBChartViewMarkedValueDistanceFromAxis = 4;
+static const CGFloat DBChartViewOffsetFromArrows = 8;
+static const CGFloat DBChartViewArrowWidth = 2;
+static const CGFloat DBChartViewArrowHeight = 4;
+
+@implementation DBChartView
+
+- (void)setMeasurements:(NSArray *)measurements {
+    _measurements = measurements;
+    [self setNeedsDisplay];
+}
+
+- (void)prepareForInterfaceBuilder {
+    [super prepareForInterfaceBuilder];
+    // Simple data to present the chart nicely in Interface Builder.
+    self.measurements = @[ @10, @20, @30, @25, @20, @15, @16, @10];
+    self.maxValue = 35.0;
+}
+
+- (void)drawRect:(CGRect)rect {
+    CGContextRef context = UIGraphicsGetCurrentContext();
+    CGFloat bottomOffset = rect.size.height - rect.size.width;
+    CGPoint chartStartPoint = CGPointMake(DBChartViewSpaceForMarkedValue + DBChartViewAdditionalLinesDistanceFromAxis + DBChartViewMarkedValueDistanceFromAxis,
+                                          rect.size.height - DBChartViewAdditionalLinesDistanceFromAxis - bottomOffset);
+    CGPoint topRightChartCorner = CGPointMake(rect.size.width - DBChartViewOffsetFromArrows - DBChartViewAdditionalLinesDistanceFromAxis,
+                                              DBChartViewOffsetFromArrows + DBChartViewAdditionalLinesDistanceFromAxis);
+    CGFloat oneMeasurementWidth = (topRightChartCorner.x - chartStartPoint.x) / (self.measurementsLimit - 1);
+    
+    if (self.measurements.count > 1) {
+        // Drawing chart.
+        CGContextSetStrokeColorWithColor(context, self.chartColor.CGColor);
+        CGContextSetLineJoin(context, kCGLineJoinMiter);
+        CGContextMoveToPoint(context, chartStartPoint.x, chartStartPoint.y);
+        for (int index = 0; index < self.measurements.count; index++) {
+            CGFloat measurement = [self.measurements[index] doubleValue];
+            CGFloat measurementY = chartStartPoint.y - measurement * (chartStartPoint.y - topRightChartCorner.y) / self.maxValue;
+            CGContextAddLineToPoint(context, chartStartPoint.x + oneMeasurementWidth * index, measurementY);
+        }
+        if (self.filled) {
+            CGContextSetFillColorWithColor(context, self.chartColor.CGColor);
+            CGContextAddLineToPoint(context, chartStartPoint.x + oneMeasurementWidth * (self.measurements.count - 1), chartStartPoint.y);
+            CGContextAddLineToPoint(context, chartStartPoint.x, chartStartPoint.y);
+            CGContextFillPath(context);
+        }
+        CGContextStrokePath(context);
+    }
+    
+    // Drawing axes on top of the chart.
+    CGContextSetStrokeColorWithColor(context, self.axisColor.CGColor);
+    
+    // Drawing x-axis.
+    CGContextMoveToPoint(context, chartStartPoint.x, chartStartPoint.y);
+    CGContextAddLineToPoint(context, rect.size.width, chartStartPoint.y);
+    CGContextAddLineToPoint(context, rect.size.width - DBChartViewArrowHeight,
+                            chartStartPoint.y + DBChartViewArrowWidth);
+    CGContextAddLineToPoint(context, rect.size.width, chartStartPoint.y);
+    CGContextAddLineToPoint(context, rect.size.width - DBChartViewArrowHeight,
+                            chartStartPoint.y - DBChartViewArrowWidth);
+    CGContextStrokePath(context);
+    
+    // Drawing y-axis.
+    CGContextMoveToPoint(context, chartStartPoint.x, chartStartPoint.y);
+    CGContextAddLineToPoint(context, chartStartPoint.x, 0);
+    CGContextAddLineToPoint(context, chartStartPoint.x - DBChartViewArrowWidth, DBChartViewArrowHeight);
+    CGContextAddLineToPoint(context, chartStartPoint.x, 0);
+    CGContextAddLineToPoint(context, chartStartPoint.x + DBChartViewArrowWidth, DBChartViewArrowHeight);
+    CGContextStrokePath(context);
+    
+    // Drawing marked value line.
+    CGFloat markedValueY = chartStartPoint.y - self.markedValue * (chartStartPoint.y - topRightChartCorner.y) / self.maxValue;
+    CGContextMoveToPoint(context, chartStartPoint.x, markedValueY);
+    CGContextAddLineToPoint(context, chartStartPoint.x + DBChartViewAdditionalLinesDistanceFromAxis, markedValueY);
+    CGContextAddLineToPoint(context, chartStartPoint.x - DBChartViewAdditionalLinesDistanceFromAxis, markedValueY);
+    CGContextStrokePath(context);
+    
+    // Drawing marked value.
+    UIFont *font = [UIFont systemFontOfSize:8];
+    NSDictionary *stringAttributes = @{ NSFontAttributeName : font, NSForegroundColorAttributeName : self.axisColor };
+    NSString *markedValueString = [NSString stringWithFormat:self.markedValueFormat, self.markedValue];
+    NSAttributedString *attributedString = [[NSAttributedString alloc] initWithString:markedValueString attributes:stringAttributes];
+    CGSize attributedStringSize = attributedString.size;
+    [attributedString drawInRect:CGRectMake(chartStartPoint.x - DBChartViewAdditionalLinesDistanceFromAxis - DBChartViewMarkedValueDistanceFromAxis - attributedStringSize.width,
+                                            markedValueY - attributedStringSize.height / 2,
+                                            attributedStringSize.width,
+                                            attributedStringSize.height)];
+    
+    // Drawing marked times.
+    for (NSTimeInterval markedTime = self.markedTimesInterval; markedTime < self.measurements.count * self.measurementInterval; markedTime += self.markedTimesInterval) {
+        CGFloat markedTimeX = chartStartPoint.x + oneMeasurementWidth * self.measurements.count * (1.0 - markedTime / (self.measurements.count * self.measurementInterval));
+        
+        // Drawing marked time line
+        CGContextMoveToPoint(context, markedTimeX, chartStartPoint.y);
+        CGContextAddLineToPoint(context, markedTimeX, chartStartPoint.y + DBChartViewAdditionalLinesDistanceFromAxis);
+        CGContextAddLineToPoint(context, markedTimeX, chartStartPoint.y - DBChartViewAdditionalLinesDistanceFromAxis);
+        CGContextStrokePath(context);
+        
+        // Drawing marked time
+        NSString *markedTimeString = [self markedTimeTextWithTimeInterval:markedTime];
+        NSAttributedString *attributedString = [[NSAttributedString alloc] initWithString:markedTimeString attributes:stringAttributes];
+        CGSize attributedStringSize = attributedString.size;
+        [attributedString drawInRect:CGRectMake(markedTimeX - attributedStringSize.width / 2,
+                                                chartStartPoint.y + DBChartViewAdditionalLinesDistanceFromAxis + DBChartViewMarkedValueDistanceFromAxis,
+                                                attributedStringSize.width,
+                                                attributedStringSize.height)];
+    }
+}
+
+- (NSString *)markedTimeTextWithTimeInterval:(NSTimeInterval)timeInterval {
+    NSInteger minutes = (NSInteger)timeInterval / 60;
+    NSInteger seconds = (NSInteger)fmod(timeInterval, 60.0);
+    NSMutableArray *components = [NSMutableArray array];
+    if (minutes > 0) {
+        [components addObject:[NSString stringWithFormat:@"%ldm", (long)minutes]];
+    }
+    if (seconds > 0) {
+        [components addObject:[NSString stringWithFormat:@"%lds", (long)seconds]];
+    }
+    return [components componentsJoinedByString:@" "];
+}
+
+@end

+ 79 - 0
Pods/DBDebugToolkit/DBDebugToolkit/Classes/Console/DBConsoleOutputCaptor.h

@@ -0,0 +1,79 @@
+// The MIT License
+//
+// Copyright (c) 2016 Dariusz Bukowski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#import <Foundation/Foundation.h>
+
+@class DBConsoleOutputCaptor;
+
+/**
+ A protocol used for informing about changes in the captured output or in the captor state.
+ */
+@protocol DBConsoleOutputCaptorDelegate <NSObject>
+
+/**
+ Informs the delegate that new console output is available.
+ 
+ @param consoleOutputCaptor The captor that updated its console output.
+ */
+- (void)consoleOutputCaptorDidUpdateOutput:(DBConsoleOutputCaptor *)consoleOutputCaptor;
+
+/**
+ Informs the delegate that the captor changed its enabled flag.
+ 
+ @param consoleOutputCaptor The captor that changed its enabled flag.
+ @param enabled The new value of enabled flag.
+ */
+- (void)consoleOutputCaptor:(DBConsoleOutputCaptor *)consoleOutputCaptor didSetEnabled:(BOOL)enabled;
+
+@end
+
+/**
+ `DBConsoleOutputCaptor` is a singleton responsible for capturing data from stdout and stderr.
+ */
+@interface DBConsoleOutputCaptor : NSObject
+
+/**
+ Returns the singleton instance.
+ */
++ (instancetype)sharedInstance;
+
+/**
+ Delegate that will be informed about changes in the available data or in the enabled flag value. It needs to conform to `DBConsoleOutputCaptorDelegate` protocol.
+ */
+@property (nonatomic, weak) id <DBConsoleOutputCaptorDelegate> delegate;
+
+/**
+ `NSString` object containing the captured console output.
+ */
+@property (nonatomic, readonly) NSString *consoleOutput;
+
+/**
+ Boolean variable determining whether the console output capturing is enabled or not. It is true by default.
+ */
+@property (nonatomic, assign) BOOL enabled;
+
+/**
+ Clears the captured console output.
+ */
+- (void)clearConsoleOutput;
+
+@end

+ 171 - 0
Pods/DBDebugToolkit/DBDebugToolkit/Classes/Console/DBConsoleOutputCaptor.m

@@ -0,0 +1,171 @@
+// The MIT License
+//
+// Copyright (c) 2016 Dariusz Bukowski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#import "DBConsoleOutputCaptor.h"
+
+__strong static NSThread *stderrReadingThread = nil;
+
+typedef int File_Writer_t(void *, const char *, int);
+
+@interface DBConsoleOutputCaptor ()
+
+@property (nonatomic, strong) NSString *consoleOutput;
+
+@property (nonatomic, assign) File_Writer_t *originalStdoutWriter;
+
+@property (nonatomic, assign) int originalStderrFileDescriptor;
+@property (nonatomic, strong) NSPipe *stderrPipe;
+@property (nonatomic, strong) NSFileHandle *stderrPipeReadHandle;
+
+@end
+
+@implementation DBConsoleOutputCaptor
+
+#pragma mark - Initialization
+
+- (instancetype)init {
+    self = [super init];
+    if (self) {
+        _consoleOutput = [NSString string];
+    }
+    
+    return self;
+}
+
++ (instancetype)sharedInstance {
+    static DBConsoleOutputCaptor *sharedInstance = nil;
+    static dispatch_once_t onceToken;
+    dispatch_once(&onceToken, ^{
+        sharedInstance = [[DBConsoleOutputCaptor alloc] init];
+    });
+    return sharedInstance;
+}
+
+- (void)dealloc {
+    [self stopCapturingConsoleOutput];
+}
+
+#pragma mark - Capturing console output
+
+- (void)startCapturingConsoleOutput {
+    [self performSelector:@selector(startCapturingStderr) onThread:stderrReadingThread withObject:nil waitUntilDone:NO];
+    [self startCapturingStdout];
+}
+
+- (void)stopCapturingConsoleOutput {
+    [self stopCapturingStderr];
+    [self stopCapturingStdout];
+}
+
+- (void)appendConsoleOutput:(NSString *)consoleOutput {
+    dispatch_async(dispatch_get_main_queue(), ^{
+        self.consoleOutput = [self.consoleOutput stringByAppendingString:(consoleOutput ?: @"")];
+        [self.delegate consoleOutputCaptorDidUpdateOutput:self];
+    });
+}
+
+- (void)setEnabled:(BOOL)enabled {
+    if (_enabled != enabled) {
+        _enabled = enabled;
+        if (enabled) {
+            [self startCapturingConsoleOutput];
+        } else {
+            [self stopCapturingConsoleOutput];
+            self.consoleOutput = [NSString string];
+            [self.delegate consoleOutputCaptorDidUpdateOutput:self];
+        }
+        [self.delegate consoleOutputCaptor:self didSetEnabled:enabled];
+    }
+}
+
+#pragma mark - Stderr reading thread
+
++ (void)load {
+    stderrReadingThread = [[NSThread alloc] initWithTarget:self selector:@selector(runStderrReadingThread) object:nil];
+    [stderrReadingThread start];
+}
+
++ (void)runStderrReadingThread {
+    @autoreleasepool {
+        [[NSThread currentThread] setName:@"DBDebugToolkit stderr reading thread"];
+        
+        NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
+        [runLoop addPort:[NSPort port] forMode:NSDefaultRunLoopMode];
+        [runLoop run];
+    }
+}
+
+#pragma mark - Stderr
+
+- (void)startCapturingStderr {
+    self.originalStderrFileDescriptor = dup(fileno(stderr));
+    self.stderrPipe = [NSPipe pipe];
+    self.stderrPipeReadHandle = self.stderrPipe.fileHandleForReading;
+    dup2(self.stderrPipe.fileHandleForWriting.fileDescriptor, fileno(stderr));
+    close(self.stderrPipe.fileHandleForWriting.fileDescriptor); // Close unused file handle.
+    [[NSNotificationCenter defaultCenter] addObserver:self
+                                             selector:@selector(stderrCaptureNotification:)
+                                                 name:NSFileHandleReadCompletionNotification
+                                               object:self.stderrPipeReadHandle];
+    [self.stderrPipeReadHandle readInBackgroundAndNotify];
+}
+
+- (void)stopCapturingStderr {
+    [[NSNotificationCenter defaultCenter] removeObserver:self];
+    dup2(self.originalStderrFileDescriptor, fileno(stderr));
+}
+
+- (void)stderrCaptureNotification:(NSNotification *)notification {
+    NSData *writtenData = [[notification userInfo] objectForKey:NSFileHandleNotificationDataItem];
+    NSString *writtenString = [[NSString alloc] initWithData:writtenData encoding:NSASCIIStringEncoding];
+    write(self.originalStderrFileDescriptor, writtenData.bytes, writtenData.length);
+    [self appendConsoleOutput:writtenString];
+    [self.stderrPipeReadHandle readInBackgroundAndNotify]; // Continue reading.
+}
+
+#pragma mark - Stdout
+
+- (void)startCapturingStdout {
+    self.originalStdoutWriter = stdout->_write;
+    stdout->_write = &capturingStdoutWriter;
+}
+
+- (void)stopCapturingStdout {
+    stdout->_write = self.originalStdoutWriter;
+}
+
+static int capturingStdoutWriter(void *inFD, const char *buffer, int size) {
+    DBConsoleOutputCaptor *consoleOutputCaptor = [DBConsoleOutputCaptor sharedInstance];
+    consoleOutputCaptor.originalStdoutWriter(inFD, buffer, size); // Run original implementation.
+    NSString *writtenString = [[NSString alloc] initWithBytes:buffer length:size encoding:NSUTF8StringEncoding];
+    [consoleOutputCaptor appendConsoleOutput:writtenString];
+    return size;
+}
+
+#pragma mark - Clearing console output
+
+- (void)clearConsoleOutput {
+    self.consoleOutput = [NSString string];
+    [self.delegate consoleOutputCaptorDidUpdateOutput:self];
+}
+
+@end

+ 49 - 0
Pods/DBDebugToolkit/DBDebugToolkit/Classes/Console/DBConsoleViewController.h

@@ -0,0 +1,49 @@
+// The MIT License
+//
+// Copyright (c) 2016 Dariusz Bukowski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#import <UIKit/UIKit.h>
+#import "DBConsoleOutputCaptor.h"
+#import "DBBuildInfoProvider.h"
+#import "DBDeviceInfoProvider.h"
+
+/**
+ `DBConsoleViewController` is a view controller that presents the captured console output in a text view.
+ It also has two buttons on the navigation bar. One clears the captured output and the other send the output by email  along with the device information.
+ */
+@interface DBConsoleViewController : UIViewController
+
+/**
+ `DBConsoleOutputCaptor` instance that will provide data displayed in a text view.
+ */
+@property (nonatomic, strong) DBConsoleOutputCaptor *consoleOutputCaptor;
+
+/**
+ `DBBuildInfoProvider` instance providing build information displayed in the email subject.
+ */
+@property (nonatomic, strong) DBBuildInfoProvider *buildInfoProvider;
+
+/**
+ `DBDeviceInfoProvider` instance providing device information displayed in the email body.
+ */
+@property (nonatomic, strong) DBDeviceInfoProvider *deviceInfoProvider;
+
+@end

+ 127 - 0
Pods/DBDebugToolkit/DBDebugToolkit/Classes/Console/DBConsoleViewController.m

@@ -0,0 +1,127 @@
+// The MIT License
+//
+// Copyright (c) 2016 Dariusz Bukowski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#import "DBConsoleViewController.h"
+#import <MessageUI/MessageUI.h>
+
+@interface DBConsoleViewController () <DBConsoleOutputCaptorDelegate, MFMailComposeViewControllerDelegate>
+
+@property (nonatomic, strong) IBOutlet UITextView *textView;
+@property (nonatomic, strong) IBOutlet UIBarButtonItem *clearBarButtonItem;
+@property (nonatomic, strong) IBOutlet UIBarButtonItem *sendBarButtonItem;
+@property (nonatomic, strong) MFMailComposeViewController *mailComposeViewController;
+
+@end
+
+@implementation DBConsoleViewController
+
+- (void)viewDidLoad {
+    [super viewDidLoad];
+    self.consoleOutputCaptor.delegate = self;
+    [self reloadConsole];
+    [self updateShowingConsole];
+}
+
+- (void)viewDidLayoutSubviews {
+    [super viewDidLayoutSubviews];
+    [self scrollToBottom];
+}
+
+- (void)dealloc {
+    [self.mailComposeViewController dismissViewControllerAnimated:YES completion:nil];
+}
+
+- (void)reloadConsole {
+    CGSize contentSize = [self.textView sizeThatFits:CGSizeMake(self.textView.frame.size.width, DBL_MAX)];
+    BOOL shouldScrollToBottom = -self.textView.contentOffset.y + contentSize.height <= self.textView.frame.size.height + DBL_EPSILON;
+    self.textView.text = self.consoleOutputCaptor.consoleOutput;
+    if (shouldScrollToBottom) {
+        [self scrollToBottom];
+    }
+}
+
+- (void)scrollToBottom {
+    CGSize contentSize = [self.textView sizeThatFits:CGSizeMake(self.textView.frame.size.width, DBL_MAX)];
+    self.textView.contentOffset = CGPointMake(0, MAX(contentSize.height - self.textView.frame.size.height, 0));
+}
+
+- (void)updateShowingConsole {
+    self.clearBarButtonItem.enabled = self.consoleOutputCaptor.enabled;
+    self.sendBarButtonItem.enabled = self.consoleOutputCaptor.enabled && [MFMailComposeViewController canSendMail];
+    self.textView.hidden = !self.consoleOutputCaptor.enabled;
+}
+
+#pragma mark - Sending logs
+
+- (IBAction)sendButtonAction:(id)sender {
+    self.mailComposeViewController = [[MFMailComposeViewController alloc] init];
+    self.mailComposeViewController.mailComposeDelegate = self;
+    [self.mailComposeViewController setSubject:[self consoleOutputMailSubject]];
+    [self.mailComposeViewController setMessageBody:[self consoleOutputMailHTMLBody] isHTML:YES];
+    
+    [self presentViewController:self.mailComposeViewController animated:YES completion:NULL];
+}
+
+- (NSString *)consoleOutputMailSubject {
+    return [NSString stringWithFormat:@"%@ - console output", [self.buildInfoProvider buildInfoString]];
+}
+
+- (NSString *)consoleOutputMailHTMLBody {
+    NSMutableString *mailHTMLBody = [NSMutableString string];
+    
+    // Device model.
+    [mailHTMLBody appendFormat:@"<b>Device model:</b> %@<br/>", [self.deviceInfoProvider deviceModel]];
+    
+    // System version.
+    [mailHTMLBody appendFormat:@"<b>System version:</b> %@", [self.deviceInfoProvider systemVersion]];
+    
+    // Console output.
+    NSString *consoleOutputWithIgnoredHTMLTags = [self.textView.text stringByReplacingOccurrencesOfString:@"<" withString:@"&lt"];
+    NSString *consoleOutputWithProperNewlines = [consoleOutputWithIgnoredHTMLTags stringByReplacingOccurrencesOfString:@"\n" withString:@"<br/>"];
+    [mailHTMLBody appendFormat:@"<p><b>Console output:</b><br>%@</p>", consoleOutputWithProperNewlines];
+    
+    return mailHTMLBody;
+}
+
+#pragma mark - Clearing console
+
+- (IBAction)clearButtonAction:(id)sender {
+    [self.consoleOutputCaptor clearConsoleOutput];
+}
+
+#pragma mark - DBConsoleOutputCaptorDelegate 
+
+- (void)consoleOutputCaptorDidUpdateOutput:(DBConsoleOutputCaptor *)consoleOutputCaptor {
+    [self reloadConsole];
+}
+
+- (void)consoleOutputCaptor:(DBConsoleOutputCaptor *)consoleOutputCaptor didSetEnabled:(BOOL)enabled {
+    [self updateShowingConsole];
+}
+
+#pragma mark - MFMailComposeViewControllerDelegate
+
+- (void)mailComposeController:(MFMailComposeViewController *)controller didFinishWithResult:(MFMailComposeResult)result error:(NSError *)error {
+    [self dismissViewControllerAnimated:YES completion:nil];
+}
+
+@end

+ 115 - 0
Pods/DBDebugToolkit/DBDebugToolkit/Classes/CrashReports/DBCrashReport.h

@@ -0,0 +1,115 @@
+// The MIT License
+//
+// Copyright (c) 2017 Dariusz Bukowski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#import <Foundation/Foundation.h>
+
+/**
+ `DBCrashReport` is an object containing all the information about a collected crash.
+ */
+@interface DBCrashReport : NSObject
+
+/**
+ Initializes `DBCrashReport` object with the data contained in a dictionary.
+
+ @param dictionary Dictionary containing all the information about a collected crash.
+ */
+- (instancetype)initWithDictionary:(NSDictionary *)dictionary;
+
+/**
+ Initializes `DBCrashReport` object with the provided data.
+
+ @param name Name of the crash.
+ @param reason Reason of the crash.
+ @param userInfo User info attached to the crash.
+ @param callStackSymbols An array of strings describing the call stack backtrace at the moment of the crash.
+ @param date Date of the crash occurence.
+ @param consoleOutput String containing the console output at the moment of the crash.
+ @param screenshot The screenshot image taken at the moment of the crash.
+ @param systemVersion Version of the system installed when the crash occured.
+ @param appVersion Version of the application installed when the crash occured.
+ */
+- (instancetype)initWithName:(NSString *)name
+                      reason:(NSString *)reason
+                    userInfo:(NSDictionary *)userInfo
+            callStackSymbols:(NSArray<NSString *> *)callStackSymbols
+                        date:(NSDate *)date
+               consoleOutput:(NSString *)consoleOutput
+                  screenshot:(UIImage *)screenshot
+               systemVersion:(NSString *)systemVersion
+                  appVersion:(NSString *)appVersion;
+
+/**
+ Returns a dictionary containing all the information about a collected crash.
+ */
+- (NSDictionary *)dictionaryRepresentation;
+
+/**
+ Returns a string containing the date of the crash occurence.
+ */
+- (NSString *)dateString;
+
+/**
+ Name of the crash. Read-only.
+ */
+@property (nonatomic, readonly) NSString *name;
+
+/**
+ Reason of the crash. Read-only.
+ */
+@property (nonatomic, readonly) NSString *reason;
+
+/**
+ User info attached to the crash. Read-only.
+ */
+@property (nonatomic, readonly) NSDictionary *userInfo;
+
+/**
+ An array of strings describing the call stack backtrace at the moment of the crash. Read-only.
+ */
+@property (nonatomic, readonly) NSArray<NSString *> *callStackSymbols;
+
+/**
+ Date of the crash occurence. Read-only.
+ */
+@property (nonatomic, readonly) NSDate *date;
+
+/**
+ String containing the console output at the moment of the crash. Read-only.
+ */
+@property (nonatomic, readonly) NSString *consoleOutput;
+
+/**
+ The screenshot image taken at the moment of the crash. Read-only.
+ */
+@property (nonatomic, readonly) UIImage *screenshot;
+
+/**
+ Version of the system installed when the crash occured. Read-only.
+ */
+@property (nonatomic, readonly) NSString *systemVersion;
+
+/**
+ Version of the application installed when the crash occured. Read-only.
+ */
+@property (nonatomic, readonly) NSString *appVersion;
+
+@end

+ 141 - 0
Pods/DBDebugToolkit/DBDebugToolkit/Classes/CrashReports/DBCrashReport.m

@@ -0,0 +1,141 @@
+// The MIT License
+//
+// Copyright (c) 2017 Dariusz Bukowski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#import "DBCrashReport.h"
+
+static NSString *const DBCrashReportNameKey = @"name";
+static NSString *const DBCrashReportReasonKey = @"reason";
+static NSString *const DBCrashReportUserInfoKey = @"userInfo";
+static NSString *const DBCrashReportCallStackSymbolsKey = @"callStackSymbols";
+static NSString *const DBCrashReportDateKey = @"date";
+static NSString *const DBCrashReportConsoleOutputKey = @"consoleOutput";
+static NSString *const DBCrashReportScreenshotKey = @"screenshot";
+static NSString *const DBCrashReportSystemVersionKey = @"systemVersion";
+static NSString *const DBCrashReportAppVersionKey = @"appVersion";
+
+@interface DBCrashReport ()
+
+@property (nonatomic, copy) NSString *name;
+@property (nonatomic, copy) NSString *reason;
+@property (nonatomic, copy) NSDictionary *userInfo;
+@property (nonatomic, copy) NSArray<NSString *> *callStackSymbols;
+@property (nonatomic, strong) NSDate *date;
+@property (nonatomic, copy) NSString *consoleOutput;
+@property (nonatomic, strong) UIImage *screenshot;
+@property (nonatomic, copy) NSString *systemVersion;
+@property (nonatomic, copy) NSString *appVersion;
+
+@end
+
+@implementation DBCrashReport
+
+#pragma mark - Initialization
+
+- (instancetype)initWithDictionary:(NSDictionary *)dictionary {
+    self = [super init];
+    if (self) {
+        self.name = dictionary[DBCrashReportNameKey];
+        self.reason = dictionary[DBCrashReportReasonKey];
+        self.userInfo = dictionary[DBCrashReportUserInfoKey];
+        self.callStackSymbols = dictionary[DBCrashReportCallStackSymbolsKey];
+        self.date = dictionary[DBCrashReportDateKey];
+        self.consoleOutput = dictionary[DBCrashReportConsoleOutputKey];
+        self.systemVersion = dictionary[DBCrashReportSystemVersionKey];
+        self.appVersion = dictionary[DBCrashReportAppVersionKey];
+        if (dictionary[DBCrashReportScreenshotKey]) {
+            NSData *screenshotData = [[NSData alloc] initWithBase64EncodedString:dictionary[DBCrashReportScreenshotKey]
+                                                                         options:NSDataBase64DecodingIgnoreUnknownCharacters];
+            self.screenshot = [UIImage imageWithData:screenshotData];
+        }
+    }
+
+    return self;
+}
+
+- (instancetype)initWithName:(NSString *)name
+                      reason:(NSString *)reason
+                    userInfo:(NSDictionary *)userInfo
+            callStackSymbols:(NSArray<NSString *> *)callStackSymbols
+                        date:(NSDate *)date
+               consoleOutput:(NSString *)consoleOutput
+                  screenshot:(UIImage *)screenshot
+               systemVersion:(NSString *)systemVersion
+                  appVersion:(NSString *)appVersion {
+    self = [super init];
+    if (self) {
+        self.name = name;
+        self.reason = reason;
+        self.userInfo = userInfo;
+        self.callStackSymbols = callStackSymbols;
+        self.date = date;
+        self.consoleOutput = consoleOutput;
+        self.screenshot = screenshot;
+        self.systemVersion = systemVersion;
+        self.appVersion = appVersion;
+    }
+
+    return self;
+}
+
+#pragma mark - Dictionary representation
+
+- (NSDictionary *)dictionaryRepresentation {
+    NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];
+    if (self.name) {
+        dictionary[DBCrashReportNameKey] = self.name;
+    }
+    if (self.reason) {
+        dictionary[DBCrashReportReasonKey] = self.reason;
+    }
+    if (self.userInfo) {
+        dictionary[DBCrashReportUserInfoKey] = self.userInfo;
+    }
+    if (self.callStackSymbols) {
+        dictionary[DBCrashReportCallStackSymbolsKey] = self.callStackSymbols;
+    }
+    if (self.date) {
+        dictionary[DBCrashReportDateKey] = self.date;
+    }
+    if (self.consoleOutput) {
+        dictionary[DBCrashReportConsoleOutputKey] = self.consoleOutput;
+    }
+    if (self.screenshot) {
+        NSData *imageData = UIImagePNGRepresentation(self.screenshot);
+        dictionary[DBCrashReportScreenshotKey] = [imageData base64EncodedStringWithOptions:NSDataBase64Encoding64CharacterLineLength];
+    }
+    if (self.systemVersion) {
+        dictionary[DBCrashReportSystemVersionKey] = self.systemVersion;
+    }
+    if (self.appVersion) {
+        dictionary[DBCrashReportAppVersionKey] = self.appVersion;
+    }
+
+    return [dictionary copy];
+}
+
+- (NSString *)dateString {
+    return [NSDateFormatter localizedStringFromDate:self.date
+                                          dateStyle:NSDateFormatterMediumStyle
+                                          timeStyle:NSDateFormatterMediumStyle];
+}
+
+@end

+ 48 - 0
Pods/DBDebugToolkit/DBDebugToolkit/Classes/CrashReports/DBCrashReportDetailsTableViewController.h

@@ -0,0 +1,48 @@
+// The MIT License
+//
+// Copyright (c) 2017 Dariusz Bukowski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#import <UIKit/UIKit.h>
+#import "DBCrashReport.h"
+#import "DBBuildInfoProvider.h"
+#import "DBDeviceInfoProvider.h"
+
+/**
+ `DBCrashReportDetailsTableViewController` is a view controller displaying the details of a crash report.
+ */
+@interface DBCrashReportDetailsTableViewController : UITableViewController
+
+/**
+ `DBCrashReport` instance containing all the details of the application crash.
+ */
+@property (nonatomic, strong) DBCrashReport *crashReport;
+
+/**
+ `DBBuildInfoProvider` instance providing build information displayed in the email subject.
+ */
+@property (nonatomic, strong) DBBuildInfoProvider *buildInfoProvider;
+
+/**
+ `DBDeviceInfoProvider` instance providing device information displayed in the email body.
+ */
+@property (nonatomic, strong) DBDeviceInfoProvider *deviceInfoProvider;
+
+@end

+ 322 - 0
Pods/DBDebugToolkit/DBDebugToolkit/Classes/CrashReports/DBCrashReportDetailsTableViewController.m

@@ -0,0 +1,322 @@
+// The MIT License
+//
+// Copyright (c) 2017 Dariusz Bukowski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#import "DBCrashReportDetailsTableViewController.h"
+#import "NSBundle+DBDebugToolkit.h"
+#import "DBTitleValueTableViewCell.h"
+#import "DBTextViewViewController.h"
+#import "DBImageViewViewController.h"
+#import <MessageUI/MessageUI.h>
+
+typedef NS_ENUM(NSUInteger, DBCrashReportDetailsTableViewControllerSection) {
+    DBCrashReportDetailsTableViewControllerSectionDetails,
+    DBCrashReportDetailsTableViewControllerSectionContext,
+    DBCrashReportDetailsTableViewControllerSectionUserInfo,
+    DBCrashReportDetailsTableViewControllerSectionStackTrace
+};
+
+static NSString *const DBCrashReportDetailsTableViewControllerTitleValueCellIdentifier = @"DBTitleValueTableViewCell";
+static NSString *const DBCrashReportDetailsTableViewControllerContextCellIdentifier = @"ContextCell";
+static NSString *const DBCrashReportDetailsTableViewControllerStackTraceCellIdentifier = @"StackTraceCell";
+
+@interface DBCrashReportDetailsTableViewController () <MFMailComposeViewControllerDelegate>
+
+@property (nonatomic, weak) IBOutlet UIBarButtonItem *shareButton;
+@property (nonatomic, strong) NSArray <DBTitleValueTableViewCellDataSource *> *detailCellDataSources;
+@property (nonatomic, strong) NSArray <DBTitleValueTableViewCellDataSource *> *userInfoCellDataSources;
+@property (nonatomic, strong) MFMailComposeViewController *mailComposeViewController;
+
+@end
+
+@implementation DBCrashReportDetailsTableViewController
+
+- (void)viewDidLoad {
+    [super viewDidLoad];
+    [self setupDetailCellDataSources];
+    [self setupUserInfoCellDataSources];
+    [self setupShareButton];
+    NSBundle *bundle = [NSBundle debugToolkitBundle];
+    [self.tableView registerNib:[UINib nibWithNibName:@"DBTitleValueTableViewCell" bundle:bundle]
+         forCellReuseIdentifier:DBCrashReportDetailsTableViewControllerTitleValueCellIdentifier];
+    self.tableView.rowHeight = UITableViewAutomaticDimension;
+    self.tableView.estimatedRowHeight = 44.0;
+}
+
+- (void)dealloc {
+    [self.mailComposeViewController dismissViewControllerAnimated:YES completion:nil];
+}
+
+#pragma mark - Share button
+
+- (void)setupShareButton {
+    self.shareButton.enabled = [MFMailComposeViewController canSendMail];
+}
+
+- (IBAction)shareButtonAction:(id)sender {
+    self.mailComposeViewController = [[MFMailComposeViewController alloc] init];
+    self.mailComposeViewController.mailComposeDelegate = self;
+    [self.mailComposeViewController setSubject:[self mailSubject]];
+    [self.mailComposeViewController setMessageBody:[self mailHTMLBody] isHTML:YES];
+
+    if (self.crashReport.screenshot) {
+        NSData *screenshotData = UIImageJPEGRepresentation(self.crashReport.screenshot, 1);
+        [self.mailComposeViewController addAttachmentData:screenshotData mimeType:@"image/jpeg" fileName:@"screenshot"];
+    }
+
+    [self presentViewController:self.mailComposeViewController animated:YES completion:NULL];
+}
+
+#pragma mark - UITableViewDataSource
+
+- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
+    return 4;
+}
+
+- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
+    switch (section) {
+        case DBCrashReportDetailsTableViewControllerSectionDetails:
+            return self.detailCellDataSources.count;
+        case DBCrashReportDetailsTableViewControllerSectionContext:
+            return 2;
+        case DBCrashReportDetailsTableViewControllerSectionUserInfo:
+            return self.crashReport.userInfo.count;
+        case DBCrashReportDetailsTableViewControllerSectionStackTrace:
+            return self.crashReport.callStackSymbols.count;
+        default:
+            return 0;
+    }
+}
+
+- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
+    if ([self tableView:tableView numberOfRowsInSection:section] == 0) {
+        return nil;
+    }
+    
+    switch (section) {
+        case DBCrashReportDetailsTableViewControllerSectionDetails:
+            return @"Details";
+        case DBCrashReportDetailsTableViewControllerSectionContext:
+            return @"Context";
+        case DBCrashReportDetailsTableViewControllerSectionUserInfo:
+            return @"User info";
+        case DBCrashReportDetailsTableViewControllerSectionStackTrace:
+            return @"Stack trace";
+        default:
+            return nil;
+    }
+}
+
+- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
+    switch (indexPath.section) {
+        case DBCrashReportDetailsTableViewControllerSectionDetails: {
+            DBTitleValueTableViewCell *titleValueCell = [self.tableView dequeueReusableCellWithIdentifier:DBCrashReportDetailsTableViewControllerTitleValueCellIdentifier];
+            DBTitleValueTableViewCellDataSource *dataSource = self.detailCellDataSources[indexPath.row];
+            [titleValueCell configureWithDataSource:dataSource];
+            [titleValueCell setSeparatorInset:UIEdgeInsetsZero];
+            return titleValueCell;
+        }
+        case DBCrashReportDetailsTableViewControllerSectionContext: {
+            UITableViewCell *contextCell = [tableView dequeueReusableCellWithIdentifier:DBCrashReportDetailsTableViewControllerContextCellIdentifier];
+            if (contextCell == nil) {
+                contextCell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
+                                                     reuseIdentifier:DBCrashReportDetailsTableViewControllerContextCellIdentifier];
+                contextCell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
+            }
+            contextCell.textLabel.text = indexPath.row == 0 ? @"Screenshot" : @"Console output";
+            return contextCell;
+        }
+        case DBCrashReportDetailsTableViewControllerSectionUserInfo: {
+            DBTitleValueTableViewCell *titleValueCell = [self.tableView dequeueReusableCellWithIdentifier:DBCrashReportDetailsTableViewControllerTitleValueCellIdentifier];
+            DBTitleValueTableViewCellDataSource *dataSource = self.userInfoCellDataSources[indexPath.row];
+            [titleValueCell configureWithDataSource:dataSource];
+            [titleValueCell setSeparatorInset:UIEdgeInsetsZero];
+            return titleValueCell;
+        }
+        case DBCrashReportDetailsTableViewControllerSectionStackTrace: {
+            UITableViewCell *stackTraceCell = [tableView dequeueReusableCellWithIdentifier:DBCrashReportDetailsTableViewControllerStackTraceCellIdentifier];
+            if (stackTraceCell == nil) {
+                stackTraceCell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
+                                                        reuseIdentifier:DBCrashReportDetailsTableViewControllerStackTraceCellIdentifier];
+                stackTraceCell.textLabel.numberOfLines = 0;
+                stackTraceCell.textLabel.font = [UIFont systemFontOfSize:12];
+                stackTraceCell.selectionStyle = UITableViewCellSelectionStyleNone;
+            }
+            stackTraceCell.textLabel.text = self.crashReport.callStackSymbols[indexPath.row];
+            return stackTraceCell;
+        }
+    }
+    return nil;
+}
+
+#pragma mark - UITableViewDelegate
+
+- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
+    if (indexPath.section != DBCrashReportDetailsTableViewControllerSectionContext) {
+        return;
+    }
+    
+    if (indexPath.row == 0) {
+        [self openScreenshotPreview];
+    } else {
+        [self openConsoleOutputPreview];
+    }
+}
+
+- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section {
+    return [self heightForFooterAndHeaderInSection:section];
+}
+
+- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section {
+    return [self heightForFooterAndHeaderInSection:section];
+}
+
+#pragma mark - MFMailComposeViewControllerDelegate
+
+- (void)mailComposeController:(MFMailComposeViewController *)controller didFinishWithResult:(MFMailComposeResult)result error:(NSError *)error {
+    [self dismissViewControllerAnimated:YES completion:nil];
+}
+
+#pragma mark - Private methods
+
+#pragma mark - - Detail cells
+
+- (void)setupDetailCellDataSources {
+    NSMutableArray *detailCellDataSources = [NSMutableArray array];
+    [detailCellDataSources addObject:[DBTitleValueTableViewCellDataSource dataSourceWithTitle:@"Name"
+                                                                                        value:self.crashReport.name]];
+    [detailCellDataSources addObject:[DBTitleValueTableViewCellDataSource dataSourceWithTitle:@"Date"
+                                                                                        value:self.crashReport.dateString]];
+    if (self.crashReport.reason) {
+        [detailCellDataSources addObject:[DBTitleValueTableViewCellDataSource dataSourceWithTitle:@"Reason"
+                                                                                            value:self.crashReport.reason]];
+    }
+    [detailCellDataSources addObject:[DBTitleValueTableViewCellDataSource dataSourceWithTitle:@"App version"
+                                                                                        value:self.crashReport.appVersion]];
+    [detailCellDataSources addObject:[DBTitleValueTableViewCellDataSource dataSourceWithTitle:@"System version"
+                                                                                        value:self.crashReport.systemVersion]];
+    self.detailCellDataSources = [detailCellDataSources copy];
+}
+
+#pragma mark - - User info cells
+
+- (void)setupUserInfoCellDataSources {
+    NSMutableArray *userInfoCellDataSources = [NSMutableArray array];
+    for (NSString *key in self.crashReport.userInfo.allKeys) {
+        NSString *value = [NSString stringWithFormat:@"%@", self.crashReport.userInfo[key]];
+        DBTitleValueTableViewCellDataSource *dataSource = [DBTitleValueTableViewCellDataSource dataSourceWithTitle:key
+                                                                                                             value:value];
+        [userInfoCellDataSources addObject:dataSource];
+    }
+    self.userInfoCellDataSources = [userInfoCellDataSources copy];
+}
+
+#pragma mark - - Section heights
+
+- (CGFloat)heightForFooterAndHeaderInSection:(NSInteger)section {
+    return [self tableView:self.tableView numberOfRowsInSection:section] > 0 ? UITableViewAutomaticDimension : CGFLOAT_MIN;
+}
+
+#pragma mark - - Opening context screens
+
+- (void)openScreenshotPreview {
+    NSBundle *bundle = [NSBundle debugToolkitBundle];
+    UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"DBImageViewViewController" bundle:bundle];
+    DBImageViewViewController *imageViewViewController = [storyboard instantiateInitialViewController];
+    [imageViewViewController configureWithTitle:@"Screenshot"
+                                          image:self.crashReport.screenshot
+                                placeholderText:@"No screenshot available."];
+    [self.navigationController pushViewController:imageViewViewController animated:YES];
+}
+
+- (void)openConsoleOutputPreview {
+    NSBundle *bundle = [NSBundle debugToolkitBundle];
+    UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"DBTextViewViewController" bundle:bundle];
+    DBTextViewViewController *textViewViewController = [storyboard instantiateInitialViewController];
+    [textViewViewController configureWithTitle:@"Console output" text:self.crashReport.consoleOutput isInConsoleMode:YES];
+    [self.navigationController pushViewController:textViewViewController animated:YES];
+}
+
+#pragma mark - - Email content
+
+
+- (NSString *)mailSubject {
+    return [NSString stringWithFormat:@"%@ - Crash report: %@, %@", [self.buildInfoProvider buildInfoString],
+                                                                    self.crashReport.name,
+                                                                    self.crashReport.dateString];
+}
+
+- (NSString *)mailHTMLBody {
+    NSMutableString *mailHTMLBody = [NSMutableString string];
+
+    // Environment.
+    [mailHTMLBody appendString:@"<b><u>Environment:</u></b><br/>"];
+
+    // App version.
+    [mailHTMLBody appendFormat:@"<b>App version:</b> %@<br/>", [self.buildInfoProvider buildInfoString]];
+
+    // System version.
+    [mailHTMLBody appendFormat:@"<b>System version:</b> %@<br/>", [self.deviceInfoProvider systemVersion]];
+
+    // Device model.
+    [mailHTMLBody appendFormat:@"<b>Device model:</b> %@<br/><br/>", [self.deviceInfoProvider deviceModel]];
+
+
+    // Details.
+    [mailHTMLBody appendString:@"<b><u>Details:</u></b><br/>"];
+
+    // Name.
+    [mailHTMLBody appendFormat:@"<b>Name:</b> %@<br/>", self.crashReport.name];
+
+    // Date.
+    [mailHTMLBody appendFormat:@"<b>Date:</b> %@<br/>", self.crashReport.dateString];
+
+    // Reason.
+    if (self.crashReport.reason) {
+        [mailHTMLBody appendFormat:@"<b>Reason:</b> %@<br/>", self.crashReport.reason];
+    }
+
+
+    // User info.
+    if (self.crashReport.userInfo.count > 0) {
+        [mailHTMLBody appendString:@"<br/><b><u>User info:</u></b><br/>"];
+        for (NSString *key in self.crashReport.userInfo.allKeys) {
+            NSString *value = self.crashReport.userInfo[key];
+            [mailHTMLBody appendFormat:@"<b>%@:</b> %@<br/>", key, value];
+        }
+    }
+
+
+    // Stack trace.
+    NSString *stackTrace = [self.crashReport.callStackSymbols componentsJoinedByString:@"\n"];
+    NSString *stackTraceWithIgnoredHTMLTags = [stackTrace stringByReplacingOccurrencesOfString:@"<" withString:@"&lt"];
+    NSString *stackTraceWithProperNewlines = [stackTraceWithIgnoredHTMLTags stringByReplacingOccurrencesOfString:@"\n" withString:@"<br/>"];
+    [mailHTMLBody appendFormat:@"<p><b><u>Stack trace:</u></b><br>%@</p>", stackTraceWithProperNewlines];
+
+    // Console output.
+    NSString *consoleOutputWithIgnoredHTMLTags = [self.crashReport.consoleOutput stringByReplacingOccurrencesOfString:@"<" withString:@"&lt"];
+    NSString *consoleOutputWithProperNewlines = [consoleOutputWithIgnoredHTMLTags stringByReplacingOccurrencesOfString:@"\n" withString:@"<br/>"];
+    [mailHTMLBody appendFormat:@"<p><b><u>Console output:</u></b><br>%@</p>", consoleOutputWithProperNewlines];
+
+    return mailHTMLBody;
+}
+
+@end

+ 36 - 0
Pods/DBDebugToolkit/DBDebugToolkit/Classes/CrashReports/DBCrashReportsTableViewController.h

@@ -0,0 +1,36 @@
+// The MIT License
+//
+// Copyright (c) 2017 Dariusz Bukowski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#import <UIKit/UIKit.h>
+#import "DBCrashReportsToolkit.h"
+
+/**
+ `DBCrashReportsTableViewController` is a view controller displaying a list of collected crash reports.
+ */
+@interface DBCrashReportsTableViewController : UITableViewController
+
+/**
+ `DBCrashReportsToolkit` object providing the list of crash reports.
+ */
+@property (nonatomic, strong) DBCrashReportsToolkit *crashReportsToolkit;
+
+@end

+ 113 - 0
Pods/DBDebugToolkit/DBDebugToolkit/Classes/CrashReports/DBCrashReportsTableViewController.m

@@ -0,0 +1,113 @@
+// The MIT License
+//
+// Copyright (c) 2017 Dariusz Bukowski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#import "DBCrashReportsTableViewController.h"
+#import "NSBundle+DBDebugToolkit.h"
+#import "DBTitleValueTableViewCell.h"
+#import "UILabel+DBDebugToolkit.h"
+#import "DBCrashReportDetailsTableViewController.h"
+#import "DBBuildInfoProvider.h"
+#import "DBDeviceInfoProvider.h"
+
+static NSString *const DBCrashReportsTableViewControllerTitleValueCellIdentifier = @"DBTitleValueTableViewCell";
+
+@interface DBCrashReportsTableViewController ()
+
+@property (nonatomic, strong) UILabel *backgroundLabel;
+@property (nonatomic, weak) IBOutlet UIBarButtonItem *clearButton;
+
+@end
+
+@implementation DBCrashReportsTableViewController
+
+- (void)viewDidLoad {
+    [super viewDidLoad];
+    NSBundle *bundle = [NSBundle debugToolkitBundle];
+    [self.tableView registerNib:[UINib nibWithNibName:@"DBTitleValueTableViewCell" bundle:bundle]
+         forCellReuseIdentifier:DBCrashReportsTableViewControllerTitleValueCellIdentifier];
+    self.tableView.rowHeight = UITableViewAutomaticDimension;
+    self.tableView.estimatedRowHeight = 44.0;
+    self.tableView.tableFooterView = [UIView new];
+    [self setupBackgroundLabel];
+}
+
+- (void)setupBackgroundLabel {
+    self.backgroundLabel = [UILabel tableViewBackgroundLabel];
+    self.tableView.backgroundView = self.backgroundLabel;
+}
+
+#pragma mark - Clear button
+
+- (IBAction)clearButtonAction:(id)sender {
+    [self.crashReportsToolkit clearCrashReports];
+    [self.tableView reloadData];
+}
+
+#pragma mark - UITableViewDelegate
+
+- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
+    DBCrashReport *crashReport = self.crashReportsToolkit.crashReports[indexPath.row];
+    [self openCrashReportDetailsWithCrashReport:crashReport];
+}
+
+- (void)openCrashReportDetailsWithCrashReport:(DBCrashReport *)crashReport {
+    NSBundle *bundle = [NSBundle debugToolkitBundle];
+    UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"DBCrashReportDetailsTableViewController" bundle:bundle];
+    DBCrashReportDetailsTableViewController *crashReportDetailsViewController = [storyboard instantiateInitialViewController];
+    crashReportDetailsViewController.crashReport = crashReport;
+    crashReportDetailsViewController.deviceInfoProvider = [DBDeviceInfoProvider new];
+    crashReportDetailsViewController.buildInfoProvider = [DBBuildInfoProvider new];
+    [self.navigationController pushViewController:crashReportDetailsViewController animated:YES];
+}
+
+#pragma mark - UITableViewDataSource
+
+- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
+    if (!self.crashReportsToolkit.isCrashReportingEnabled) {
+        self.backgroundLabel.text = @"Crash reporting disabled.";
+        self.clearButton.enabled = NO;
+        return 0;
+    }
+    NSInteger numberOfItems = self.crashReportsToolkit.crashReports.count;
+    self.backgroundLabel.text = numberOfItems == 0 ? @"There are no crash reports." : @"";
+    self.clearButton.enabled = numberOfItems > 0;
+    return numberOfItems;
+}
+
+- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
+    DBTitleValueTableViewCell *titleValueCell = [self.tableView dequeueReusableCellWithIdentifier:DBCrashReportsTableViewControllerTitleValueCellIdentifier];
+    DBTitleValueTableViewCellDataSource *dataSource = [self dataSourceForCellAtRow:indexPath.row];
+    [titleValueCell configureWithDataSource:dataSource];
+    [titleValueCell setSeparatorInset:UIEdgeInsetsZero];
+    [titleValueCell setAccessoryType:UITableViewCellAccessoryDisclosureIndicator];
+    return titleValueCell;
+}
+
+#pragma mark - Private methods
+
+- (DBTitleValueTableViewCellDataSource *)dataSourceForCellAtRow:(NSInteger)row {
+    DBCrashReport *crashReport = self.crashReportsToolkit.crashReports[row];
+    return [DBTitleValueTableViewCellDataSource dataSourceWithTitle:crashReport.name
+                                                              value:crashReport.dateString];
+}
+
+@end

+ 74 - 0
Pods/DBDebugToolkit/DBDebugToolkit/Classes/CrashReports/DBCrashReportsToolkit.h

@@ -0,0 +1,74 @@
+// The MIT License
+//
+// Copyright (c) 2017 Dariusz Bukowski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#import <Foundation/Foundation.h>
+#import "DBCrashReport.h"
+#import "DBConsoleOutputCaptor.h"
+#import "DBBuildInfoProvider.h"
+#import "DBDeviceInfoProvider.h"
+
+/**
+ `DBCrashReportsToolkit` is a class responsible for collecting crash reports.
+ */
+@interface DBCrashReportsToolkit : NSObject
+
+/**
+ Returns the singleton instance.
+ */
++ (instancetype)sharedInstance;
+
+/**
+ Enables reporting crashes.
+ */
+- (void)setupCrashReporting;
+
+/**
+ Removes the crash reports.
+ */
+- (void)clearCrashReports;
+
+/**
+ Returns an array containing all the collected crash reports.
+ */
+- (NSArray <DBCrashReport *> *)crashReports;
+
+/**
+ `DBConsoleOutputCaptor` instance providing console output attached to crash reports.
+ */
+@property (nonatomic, strong) DBConsoleOutputCaptor *consoleOutputCaptor;
+
+/**
+ `DBBuildInfoProvider` instance providing build information attached to crash reports.
+ */
+@property (nonatomic, strong) DBBuildInfoProvider *buildInfoProvider;
+
+/**
+ `DBDeviceInfoProvider` instance providing device information attached to crash reports.
+ */
+@property (nonatomic, strong) DBDeviceInfoProvider *deviceInfoProvider;
+
+/**
+ Boolean variable determining whether the crash reporting is enabled or not. It is false by default.
+ */
+@property (nonatomic, readonly) BOOL isCrashReportingEnabled;
+
+@end

+ 314 - 0
Pods/DBDebugToolkit/DBDebugToolkit/Classes/CrashReports/DBCrashReportsToolkit.m

@@ -0,0 +1,314 @@
+// The MIT License
+//
+// Copyright (c) 2017 Dariusz Bukowski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#import "DBCrashReportsToolkit.h"
+#import "UIView+Snapshot.h"
+#include <assert.h>
+#include <stdbool.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <sys/sysctl.h>
+
+typedef void (*sighandler_t)(int);
+
+static NSUncaughtExceptionHandler *_previousUncaughtExceptionHandler;
+static sighandler_t _previousSIGABRTHandler;
+static sighandler_t _previousSIGILLHandler;
+static sighandler_t _previousSIGSEGVHandler;
+static sighandler_t _previousSIGFPEHandler;
+static sighandler_t _previousSIGBUSHandler;
+static sighandler_t _previousSIGPIPEHandler;
+
+@interface DBCrashReportsToolkit ()
+
+@property (nonatomic, strong) NSArray <DBCrashReport *> *crashReports;
+@property (nonatomic, assign) BOOL isCrashReportingEnabled;
+
+@end
+
+@implementation DBCrashReportsToolkit
+
+#pragma mark - Initialization
+
++ (instancetype)sharedInstance {
+    static DBCrashReportsToolkit *sharedInstance = nil;
+    static dispatch_once_t onceToken;
+    dispatch_once(&onceToken, ^{
+        sharedInstance = [[DBCrashReportsToolkit alloc] init];
+        sharedInstance.isCrashReportingEnabled = NO;
+    });
+    return sharedInstance;
+}
+
+#pragma mark - Crash reporting
+
+- (NSString *)crashReportsPath {
+    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
+    NSString *documentsDirectory = [paths objectAtIndex:0];
+    return [documentsDirectory stringByAppendingPathComponent:@"DBDebugToolkit/CrashReports"];
+}
+
+- (NSString *)filePathWithCrashReport:(DBCrashReport *)crashReport {
+    return [[self crashReportsPath] stringByAppendingPathComponent:[NSString stringWithFormat:@"%lf", crashReport.date.timeIntervalSince1970]];
+}
+
+- (void)clearCrashReports {
+    NSFileManager *fileManager = [NSFileManager defaultManager];
+    NSString *directory = [self crashReportsPath];
+    NSError *error = nil;
+    for (NSString *file in [fileManager contentsOfDirectoryAtPath:directory error:&error]) {
+        NSString *filePath = [directory stringByAppendingPathComponent:file];
+        [fileManager removeItemAtPath:filePath error:&error];
+    }
+    self.crashReports = [NSArray array];
+}
+
+- (NSArray <DBCrashReport *> *)crashReports {
+    if (!_crashReports) {
+        NSMutableArray *crashReports = [NSMutableArray array];
+        NSArray *directoryContent = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:[self crashReportsPath] error:NULL];
+        for (NSString *fileName in directoryContent) {
+            NSString *filePath = [[self crashReportsPath] stringByAppendingPathComponent:fileName];
+            NSDictionary *dictionaryRepresentation = [NSDictionary dictionaryWithContentsOfFile:filePath];
+            DBCrashReport *crashReport = [[DBCrashReport alloc] initWithDictionary:dictionaryRepresentation];
+            [crashReports addObject:crashReport];
+        }
+        _crashReports = [crashReports sortedArrayUsingComparator:^NSComparisonResult(DBCrashReport *cr1, DBCrashReport *cr2) {
+            return cr1.date < cr2.date;
+        }];
+    }
+
+    return _crashReports;
+}
+
+- (void)setupCrashReporting {
+    if ([self isRunningUnderDebugger]) {
+        NSLog(@"[DBDebugToolkit] The app is running under the debugger. It may not generate crash reports correctly.");
+    }
+
+    self.isCrashReportingEnabled = YES;
+
+    // Uncaught exceptions
+    _previousUncaughtExceptionHandler = NSGetUncaughtExceptionHandler();
+    NSSetUncaughtExceptionHandler(&handleException);
+
+    // SIGABRT signal
+    _previousSIGABRTHandler = signal(SIGABRT, SIG_IGN);
+    signal(SIGABRT, handleSIGABRTSignal);
+
+    // SIGSEGV signal
+    _previousSIGSEGVHandler = signal(SIGSEGV, SIG_IGN);
+    signal(SIGSEGV, handleSIGSEGVSignal);
+
+    // SIGILL signal
+    _previousSIGILLHandler = signal(SIGILL, SIG_IGN);
+    signal(SIGILL, handleSIGILLSignal);
+
+    // SIGFPE signal
+    _previousSIGFPEHandler = signal(SIGFPE, SIG_IGN);
+    signal(SIGFPE, handleSIGFPESignal);
+
+    // SIGBUS signal
+    _previousSIGBUSHandler = signal(SIGBUS, SIG_IGN);
+    signal(SIGBUS, handleSIGBUSSignal);
+
+    // SIGPIPE signal
+    _previousSIGPIPEHandler = signal(SIGPIPE, SIG_IGN);
+    signal(SIGPIPE, handleSIGPIPESignal);
+}
+
+void stopCrashReporting() {
+    NSSetUncaughtExceptionHandler(_previousUncaughtExceptionHandler);
+    signal(SIGABRT, _previousSIGABRTHandler);
+    signal(SIGILL, _previousSIGILLHandler);
+    signal(SIGSEGV, _previousSIGSEGVHandler);
+    signal(SIGFPE, _previousSIGFPEHandler);
+    signal(SIGBUS, _previousSIGBUSHandler);
+    signal(SIGPIPE, _previousSIGPIPEHandler);
+}
+
+void handleException(NSException *exception) {
+    [[DBCrashReportsToolkit sharedInstance] saveCrashReportWithName:exception.name
+                                                             reason:exception.reason
+                                                           userInfo:exception.userInfo
+                                                   callStackSymbols:exception.callStackSymbols];
+    stopCrashReporting();
+    [exception raise];
+}
+
+static void handleSIGABRTSignal(int sig) {
+    [[DBCrashReportsToolkit sharedInstance] saveCrashReportWithName:@"SIGABRT signal"
+                                                             reason:nil
+                                                           userInfo:nil
+                                                   callStackSymbols:[NSThread callStackSymbols]];
+    stopCrashReporting();
+    kill(getpid(), sig);
+}
+
+static void handleSIGILLSignal(int sig) {
+    [[DBCrashReportsToolkit sharedInstance] saveCrashReportWithName:@"SIGILL signal"
+                                                             reason:nil
+                                                           userInfo:nil
+                                                   callStackSymbols:[NSThread callStackSymbols]];
+    stopCrashReporting();
+    kill(getpid(), sig);
+}
+
+static void handleSIGSEGVSignal(int sig) {
+    [[DBCrashReportsToolkit sharedInstance] saveCrashReportWithName:@"SIGSEGV signal"
+                                                             reason:nil
+                                                           userInfo:nil
+                                                   callStackSymbols:[NSThread callStackSymbols]];
+    stopCrashReporting();
+    kill(getpid(), sig);
+}
+
+static void handleSIGFPESignal(int sig) {
+    [[DBCrashReportsToolkit sharedInstance] saveCrashReportWithName:@"SIGFPE signal"
+                                                             reason:nil
+                                                           userInfo:nil
+                                                   callStackSymbols:[NSThread callStackSymbols]];
+    stopCrashReporting();
+    kill(getpid(), sig);
+}
+
+static void handleSIGBUSSignal(int sig) {
+    [[DBCrashReportsToolkit sharedInstance] saveCrashReportWithName:@"SIGBUS signal"
+                                                             reason:nil
+                                                           userInfo:nil
+                                                   callStackSymbols:[NSThread callStackSymbols]];
+    stopCrashReporting();
+    kill(getpid(), sig);
+}
+
+static void handleSIGPIPESignal(int sig) {
+    [[DBCrashReportsToolkit sharedInstance] saveCrashReportWithName:@"SIGPIPE signal"
+                                                             reason:nil
+                                                           userInfo:nil
+                                                   callStackSymbols:[NSThread callStackSymbols]];
+    stopCrashReporting();
+    kill(getpid(), sig);
+}
+
+#pragma mark - Private methods
+
+- (void)saveCrashReportWithName:(NSString *)name
+                         reason:(NSString *)reason
+                       userInfo:(NSDictionary *)userInfo
+               callStackSymbols:(NSArray<NSString *> *)callStackSymbols {
+    NSDate *date = [NSDate date];
+    [self saveCrashReportWithName:name
+                           reason:reason
+                         userInfo:userInfo
+                 callStackSymbols:callStackSymbols
+                             date:date];
+}
+
+- (void)saveCrashReportWithName:(NSString *)name
+                         reason:(NSString *)reason
+                       userInfo:(NSDictionary *)userInfo
+               callStackSymbols:(NSArray<NSString *> *)callStackSymbols
+                           date:(NSDate *)date {
+    BOOL isMainThread = [NSThread isMainThread];
+    UIImage *screenshot = isMainThread ? [[UIApplication sharedApplication].keyWindow snapshot] : nil;
+    [self saveCrashReportWithName:name
+                           reason:reason
+                         userInfo:userInfo
+                 callStackSymbols:callStackSymbols
+                             date:date
+                       screenshot:screenshot];
+    if (!isMainThread) {
+        dispatch_sync(dispatch_get_main_queue(), ^{
+            [self saveCrashReportWithName:name
+                                   reason:reason
+                                 userInfo:userInfo
+                         callStackSymbols:callStackSymbols
+                                     date:date];
+        });
+    }
+}
+
+- (void)saveCrashReportWithName:(NSString *)name
+                         reason:(NSString *)reason
+                       userInfo:(NSDictionary *)userInfo
+               callStackSymbols:(NSArray<NSString *> *)callStackSymbols
+                           date:(NSDate *)date
+                     screenshot:(UIImage *)screenshot {
+    NSString *consoleOutput = self.consoleOutputCaptor.consoleOutput;
+    NSString *systemVersion = [self.deviceInfoProvider systemVersion];
+    NSString *appVersion = [self.buildInfoProvider buildInfoString];
+    DBCrashReport *crashReport = [[DBCrashReport alloc] initWithName:name
+                                                              reason:reason
+                                                            userInfo:userInfo
+                                                    callStackSymbols:callStackSymbols
+                                                                date:date
+                                                       consoleOutput:consoleOutput
+                                                          screenshot:screenshot
+                                                       systemVersion:systemVersion
+                                                          appVersion:appVersion];
+    NSDictionary *dictionaryRepresentation = [crashReport dictionaryRepresentation];
+    NSString *filePath = [self filePathWithCrashReport:crashReport];
+    [[NSFileManager defaultManager] createDirectoryAtPath:[filePath stringByDeletingLastPathComponent]
+                              withIntermediateDirectories:YES
+                                               attributes:nil
+                                                    error:nil];
+    [dictionaryRepresentation writeToFile:filePath atomically:YES];
+}
+
+// Code from iOS Technical Q&A: https://developer.apple.com/library/content/qa/qa1361/_index.html
+- (BOOL)isRunningUnderDebugger {
+#ifndef DEBUG
+    return NO;
+#else
+    // Returns true if the current process is being debugged (either
+    // running under the debugger or has a debugger attached post facto).
+    
+    int                 junk;
+    int                 mib[4];
+    struct kinfo_proc   info;
+    size_t              size;
+    
+    // Initialize the flags so that, if sysctl fails for some bizarre
+    // reason, we get a predictable result.
+    
+    info.kp_proc.p_flag = 0;
+    
+    // Initialize mib, which tells sysctl the info we want, in this case
+    // we're looking for information about a specific process ID.
+    
+    mib[0] = CTL_KERN;
+    mib[1] = KERN_PROC;
+    mib[2] = KERN_PROC_PID;
+    mib[3] = getpid();
+    
+    // Call sysctl.
+    
+    size = sizeof(info);
+    junk = sysctl(mib, sizeof(mib) / sizeof(*mib), &info, &size, NULL, 0);
+    assert(junk == 0);
+    
+    // We're being debugged if the P_TRACED flag is set.
+    return ( (info.kp_proc.p_flag & P_TRACED) != 0 );
+#endif
+}
+
+@end

+ 39 - 0
Pods/DBDebugToolkit/DBDebugToolkit/Classes/CrashReports/DBImageViewViewController.h

@@ -0,0 +1,39 @@
+// The MIT License
+//
+// Copyright (c) 2017 Dariusz Bukowski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#import <UIKit/UIKit.h>
+
+/**
+ `DBImageViewViewController` is a view controller containing a single `UIImageView` instance.
+ */
+@interface DBImageViewViewController : UIViewController
+
+/**
+ Configures the view controller with an image and a title.
+
+ @param title String containing a title for the view controller.
+ @param image Image that should be displayed in the view controller's image view.
+ @param placeholderText Placeholder text displayed in case of invalid or missing image.
+ */
+- (void)configureWithTitle:(NSString *)title image:(UIImage *)image placeholderText:(NSString *)placeholderText;
+
+@end

+ 63 - 0
Pods/DBDebugToolkit/DBDebugToolkit/Classes/CrashReports/DBImageViewViewController.m

@@ -0,0 +1,63 @@
+// The MIT License
+//
+// Copyright (c) 2017 Dariusz Bukowski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#import "DBImageViewViewController.h"
+#import "UIView+Snapshot.h"
+
+@interface DBImageViewViewController ()
+
+@property (nonatomic, weak) IBOutlet UIImageView *imageView;
+@property (nonatomic, strong) UIImage *image;
+
+@property (nonatomic, weak) IBOutlet UILabel *placeholderLabel;
+@property (nonatomic, copy) NSString *placeholderText;
+
+@end
+
+@implementation DBImageViewViewController
+
+- (void)viewDidLoad {
+    [super viewDidLoad];
+    [self updateImage:self.image];
+    [self updatePlaceholder:self.placeholderText];
+}
+
+- (void)configureWithTitle:(NSString *)title image:(UIImage *)image placeholderText:(NSString *)placeholderText {
+    self.title = title;
+    [self updateImage:image];
+    [self updatePlaceholder:placeholderText];
+}
+
+#pragma mark - Private methods
+
+- (void)updateImage:(UIImage *)image {
+    self.image = image;
+    self.imageView.image = image;
+}
+
+- (void)updatePlaceholder:(NSString *)placeholderText {
+    self.placeholderText = placeholderText;
+    self.placeholderLabel.text = placeholderText;
+    self.placeholderLabel.hidden = self.image != nil;
+}
+
+@end

+ 58 - 0
Pods/DBDebugToolkit/DBDebugToolkit/Classes/CustomActions/DBCustomAction.h

@@ -0,0 +1,58 @@
+// The MIT License
+//
+// Copyright (c) 2016 Dariusz Bukowski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#import <Foundation/Foundation.h>
+
+typedef void(^DBCustomActionBody)(void);
+
+/**
+ `DBCustomAction` is an object describing an action that will be added to the menu for easy access.
+ */
+@interface DBCustomAction : NSObject
+
+/**
+ Name of the action presented in the menu. Read-only.
+ */
+@property (nonatomic, readonly, nullable) NSString * name;
+
+///---------------------
+/// @name Initialization
+///---------------------
+
+/**
+ Creates and returns a new `DBCustomAction` instance.
+ 
+ @param name The name of the action that will be presented in the menu.
+ @param body The block containing the code that should be run when the user selects the created custom action.
+ */
++ (nonnull instancetype)customActionWithName:(nullable NSString *)name body:(nullable DBCustomActionBody)body;
+
+///-----------------
+/// @name Performing
+///-----------------
+
+/**
+ Runs the block passed as the action body.
+ */
+- (void)perform;
+
+@end

+ 58 - 0
Pods/DBDebugToolkit/DBDebugToolkit/Classes/CustomActions/DBCustomAction.m

@@ -0,0 +1,58 @@
+// The MIT License
+//
+// Copyright (c) 2016 Dariusz Bukowski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#import "DBCustomAction.h"
+
+@interface DBCustomAction ()
+
+@property (nonatomic, strong, nullable) NSString *name;
+@property (nonatomic, copy, nullable) DBCustomActionBody body;
+
+@end
+
+@implementation DBCustomAction
+
+#pragma mark - Initialization
+
+- (nonnull instancetype)initWithName:(nullable NSString *)name body:(nullable DBCustomActionBody)body {
+    self = [super init];
+    if (self) {
+        self.name = name;
+        self.body = body;
+    }
+    
+    return self;
+}
+
++ (nonnull instancetype)customActionWithName:(nullable NSString *)name body:(nullable DBCustomActionBody)body {
+    return [[self alloc] initWithName:name body:body];
+}
+
+#pragma mark - Performing action
+
+- (void)perform {
+    if (self.body) {
+        self.body();
+    }
+}
+
+@end

+ 36 - 0
Pods/DBDebugToolkit/DBDebugToolkit/Classes/CustomActions/DBCustomActionsTableViewController.h

@@ -0,0 +1,36 @@
+// The MIT License
+//
+// Copyright (c) 2016 Dariusz Bukowski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#import <UIKit/UIKit.h>
+#import "DBCustomAction.h"
+
+/**
+ `DBCustomActionsTableViewController` is a table view controller presenting the list of the defined custom actions.
+ */
+@interface DBCustomActionsTableViewController : UITableViewController
+
+/**
+ Array of custom actions that should be displayed in the table view.
+ */
+@property (nonatomic, copy) NSArray <DBCustomAction *> *customActions;
+
+@end

+ 76 - 0
Pods/DBDebugToolkit/DBDebugToolkit/Classes/CustomActions/DBCustomActionsTableViewController.m

@@ -0,0 +1,76 @@
+// The MIT License
+//
+// Copyright (c) 2016 Dariusz Bukowski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#import "DBCustomActionsTableViewController.h"
+#import "UILabel+DBDebugToolkit.h"
+
+static NSString *const DBCustomActionsTableViewControllerButtonCellIdentifier = @"DBMenuButtonTableViewCell";
+
+@interface DBCustomActionsTableViewController ()
+
+@property (nonatomic, strong) UILabel *backgroundLabel;
+
+@end
+
+@implementation DBCustomActionsTableViewController
+
+- (void)viewDidLoad {
+    [super viewDidLoad];
+    self.tableView.tableFooterView = [UIView new];
+    [self setupBackgroundLabel];
+}
+
+- (void)setupBackgroundLabel {
+    self.backgroundLabel = [UILabel tableViewBackgroundLabel];
+    self.tableView.backgroundView = self.backgroundLabel;
+}
+
+#pragma mark - UITableViewDelegate
+
+- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
+    DBCustomAction *action = self.customActions[indexPath.row];
+    [action perform];
+    [tableView deselectRowAtIndexPath:indexPath animated:YES];
+}
+
+#pragma mark - UITableViewDataSource
+
+- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
+    NSInteger numberOfItems = self.customActions.count;
+    self.backgroundLabel.text = numberOfItems == 0 ? @"There are no custom actions." : @"";
+    return numberOfItems;
+}
+
+- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
+    DBCustomAction *action = self.customActions[indexPath.row];
+    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:DBCustomActionsTableViewControllerButtonCellIdentifier];
+    if (cell == nil) {
+        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
+                                      reuseIdentifier:DBCustomActionsTableViewControllerButtonCellIdentifier];
+        cell.separatorInset = UIEdgeInsetsZero;
+        cell.textLabel.textColor = cell.tintColor;
+    }
+    cell.textLabel.text = action.name;
+    return cell;
+}
+
+@end

+ 80 - 0
Pods/DBDebugToolkit/DBDebugToolkit/Classes/CustomVariables/DBCustomVariable.h

@@ -0,0 +1,80 @@
+// The MIT License
+//
+// Copyright (c) 2016 Dariusz Bukowski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#import <Foundation/Foundation.h>
+
+/**
+ Enum with types of the custom variables.
+ */
+typedef NS_ENUM(NSUInteger, DBCustomVariableType) {
+    DBCustomVariableTypeUnrecognized,
+    DBCustomVariableTypeString,
+    DBCustomVariableTypeInt,
+    DBCustomVariableTypeDouble,
+    DBCustomVariableTypeBool
+};
+
+/**
+ `DBCustomVariable` is an object describing a variable that will be added to the menu allowing the user to modify its value during runtime.
+ */
+@interface DBCustomVariable : NSObject
+
+/**
+ Type of the variable based on its `value` property. Determines the controls used in the menu for the variable value modification. Read-only.
+ */
+@property (nonatomic, readonly) DBCustomVariableType type;
+
+/**
+ The value of the property.
+ */
+@property (nonatomic, strong, nullable) id value;
+
+/**
+ The name of the variable. Should be unique. Read-only.
+ */
+@property (nonatomic, readonly, nullable) NSString *name;
+
+/**
+ Creates and returns a new `DBCustomVariable` instance with the given name and value.
+ 
+ @param name Name of the new variable. Should be unique.
+ @param value The value of the new variable. It determines the type of the property, so make sure to pass the value of a proper type.
+ */
++ (nonnull instancetype)customVariableWithName:(nullable NSString *)name value:(nullable id)value;
+
+/**
+ Adds a new action that will be invoked when the variable value changes.
+ 
+ @param target A target that will perform the action.
+ @param action A selector identifying an action method.
+ */
+- (void)addTarget:(nonnull id)target action:(nonnull SEL)action;
+
+/**
+ Stops the delivery of value change event to the specified target object.
+ 
+ @param target A target object registered with the variable. Specify nil to remove the actions for all target objects.
+ @param action A selector identifying a registered action method. You may specify nil for this parameter to remove all actions registered for the given target object.
+ */
+- (void)removeTarget:(nullable id)target action:(nullable SEL)action;
+
+@end

+ 124 - 0
Pods/DBDebugToolkit/DBDebugToolkit/Classes/CustomVariables/DBCustomVariable.m

@@ -0,0 +1,124 @@
+// The MIT License
+//
+// Copyright (c) 2016 Dariusz Bukowski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#import "DBCustomVariable.h"
+
+@interface DBCustomVariable ()
+
+@property (nonatomic, copy) NSString *name;
+@property (nonatomic, strong) NSMutableArray <NSInvocation *> *actions;
+
+@end
+
+@implementation DBCustomVariable
+
+#pragma mark - Initialization
+
+- (instancetype)initWithName:(NSString *)name value:(id)value {
+    self = [super init];
+    if (self) {
+        _name = name;
+        _value = value;
+        _actions = [NSMutableArray array];
+    }
+    
+    return self;
+}
+
++ (instancetype)customVariableWithName:(NSString *)name value:(id)value {
+    return [[self alloc] initWithName:name value:value];
+}
+
+#pragma mark - Setting value
+
+- (void)setValue:(id)value {
+    _value = value;
+    for (NSInvocation *action in self.actions) {
+        if (action.methodSignature.numberOfArguments > 2) {
+            [action setArgument:(void *)&self atIndex:2];
+        }
+        [action invoke];
+    }
+}
+
+#pragma mark - Variable type
+
+- (DBCustomVariableType)type {
+    if ([self.value isKindOfClass:[NSString class]]) {
+        return DBCustomVariableTypeString;
+    } else if ([self.value isKindOfClass:[NSNumber class]]) {
+        NSNumber *numberValue = (NSNumber *)self.value;
+        CFNumberType numberType = CFNumberGetType((CFNumberRef)numberValue);
+        switch (numberType) {
+            case kCFNumberCharType:
+                return DBCustomVariableTypeBool;
+            case kCFNumberSInt8Type:
+            case kCFNumberSInt16Type:
+            case kCFNumberSInt32Type:
+            case kCFNumberSInt64Type:
+            case kCFNumberShortType:
+            case kCFNumberIntType:
+            case kCFNumberLongType:
+            case kCFNumberLongLongType:
+            case kCFNumberCFIndexType:
+            case kCFNumberNSIntegerType:
+                return DBCustomVariableTypeInt;
+            case kCFNumberFloat32Type:
+            case kCFNumberFloat64Type:
+            case kCFNumberFloatType:
+            case kCFNumberDoubleType:
+            case kCFNumberCGFloatType:
+                return DBCustomVariableTypeDouble;
+        }
+    }
+    return DBCustomVariableTypeUnrecognized;
+}
+
+#pragma mark - Actions
+
+- (void)addTarget:(id)target action:(SEL)action {
+    NSMethodSignature *methodSignature = [target methodSignatureForSelector:action];
+    NSInvocation *newAction = [NSInvocation invocationWithMethodSignature:methodSignature];
+    newAction.target = target;
+    newAction.selector = action;
+    [newAction retainArguments];
+    [self.actions addObject:newAction];
+}
+
+- (void)removeTarget:(id)target action:(SEL)action {
+    if (target == nil) {
+        [self.actions removeAllObjects];
+    } else {
+        NSPredicate *filterPredicate = nil;
+        if (action == nil) {
+            filterPredicate = [NSPredicate predicateWithFormat:@"target != %@", target];
+        } else {
+            filterPredicate = [NSPredicate predicateWithBlock:^BOOL(id _Nullable evaluatedObject, NSDictionary<NSString *,id> * _Nullable bindings) {
+                NSInvocation *invocation = (NSInvocation *)evaluatedObject;
+                return invocation.target != target || NSStringFromSelector(invocation.selector) != NSStringFromSelector(action);
+            }];
+        }
+        [self.actions filterUsingPredicate:filterPredicate];
+    }
+}
+
+@end

+ 36 - 0
Pods/DBDebugToolkit/DBDebugToolkit/Classes/CustomVariables/DBCustomVariablesTableViewController.h

@@ -0,0 +1,36 @@
+// The MIT License
+//
+// Copyright (c) 2016 Dariusz Bukowski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#import <UIKit/UIKit.h>
+#import "DBCustomVariable.h"
+
+/**
+ `DBCustomVariablesTableViewController` is a table view controller presenting the list of the defined custom variables.
+ */
+@interface DBCustomVariablesTableViewController : UITableViewController
+
+/**
+ Array of custom variables that should be displayed in the table view.
+ */
+- (void)setCustomVariables:(NSArray <DBCustomVariable *> *)customVariables;
+
+@end

+ 281 - 0
Pods/DBDebugToolkit/DBDebugToolkit/Classes/CustomVariables/DBCustomVariablesTableViewController.m

@@ -0,0 +1,281 @@
+// The MIT License
+//
+// Copyright (c) 2016 Dariusz Bukowski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#import "DBCustomVariablesTableViewController.h"
+#import "UILabel+DBDebugToolkit.h"
+#import "NSBundle+DBDebugToolkit.h"
+#import "DBMenuSwitchTableViewCell.h"
+#import "DBTextViewTableViewCell.h"
+
+typedef NS_ENUM(NSUInteger, DBCustomVariablesTableViewControllerSection) {
+    DBCustomVariablesTableViewControllerSectionStringVariables,
+    DBCustomVariablesTableViewControllerSectionIntVariables,
+    DBCustomVariablesTableViewControllerSectionDoubleVariables,
+    DBCustomVariablesTableViewControllerSectionBoolVariables
+};
+
+static NSString *const DBCustomVariablesTableViewControllerSwitchCellIdentifier = @"DBMenuSwitchTableViewCell";
+static NSString *const DBCustomVariablesTableViewControllerTextViewCellIdentifier = @"DBTextViewTableViewCell";
+static NSString *const DBCustomVariablesTableViewControllerIntegerRegex = @"-?[0-9]*";
+static NSString *const DBCustomVariablesTableViewControllerDoubleRegex = @"-?[0-9]*([0-9]\\.)?[0-9]*";
+
+@interface DBCustomVariablesTableViewController () <DBMenuSwitchTableViewCellDelegate, DBTextViewTableViewCellDelegate>
+
+@property (nonatomic, strong) NSArray <DBCustomVariable *> *stringVariables;
+@property (nonatomic, strong) NSArray <DBCustomVariable *> *boolVariables;
+@property (nonatomic, strong) NSArray <DBCustomVariable *> *intVariables;
+@property (nonatomic, strong) NSArray <DBCustomVariable *> *doubleVariables;
+@property (nonatomic, strong) UILabel *backgroundLabel;
+
+@end
+
+@implementation DBCustomVariablesTableViewController
+
+- (void)viewDidLoad {
+    [super viewDidLoad];
+    NSBundle *bundle = [NSBundle debugToolkitBundle];
+    [self.tableView registerNib:[UINib nibWithNibName:@"DBMenuSwitchTableViewCell" bundle:bundle]
+         forCellReuseIdentifier:DBCustomVariablesTableViewControllerSwitchCellIdentifier];
+    [self.tableView registerNib:[UINib nibWithNibName:@"DBTextViewTableViewCell" bundle:bundle]
+         forCellReuseIdentifier:DBCustomVariablesTableViewControllerTextViewCellIdentifier];
+    self.tableView.rowHeight = UITableViewAutomaticDimension;
+    self.tableView.estimatedRowHeight = 44.0;
+    [self setupBackgroundLabel];
+}
+
+#pragma mark - Background label
+
+- (void)setupBackgroundLabel {
+    self.backgroundLabel = [UILabel tableViewBackgroundLabel];
+    self.tableView.backgroundView = self.backgroundLabel;
+    [self refreshBackgroundLabel];
+}
+
+- (void)refreshBackgroundLabel {
+    NSInteger presentedVariablesCount = self.stringVariables.count + self.boolVariables.count + self.intVariables.count + self.doubleVariables.count;
+    self.backgroundLabel.text = presentedVariablesCount == 0 ? @"There are no custom variables." : @"";
+}
+
+#pragma mark - Custom variables
+
+- (void)setCustomVariables:(NSArray<DBCustomVariable *> *)customVariables {
+    self.stringVariables = [self customVariablesWithType:DBCustomVariableTypeString fromArray:customVariables];
+    self.boolVariables = [self customVariablesWithType:DBCustomVariableTypeBool fromArray:customVariables];
+    self.intVariables = [self customVariablesWithType:DBCustomVariableTypeInt fromArray:customVariables];
+    self.doubleVariables = [self customVariablesWithType:DBCustomVariableTypeDouble fromArray:customVariables];
+    [self refreshBackgroundLabel];
+}
+
+- (NSArray <DBCustomVariable *> *)customVariablesWithType:(DBCustomVariableType)type fromArray:(NSArray<DBCustomVariable *> *)customVariables {
+    NSPredicate *predicate = [NSPredicate predicateWithFormat:@"type = %d", type];
+    return [customVariables filteredArrayUsingPredicate:predicate];
+}
+
+- (DBCustomVariable *)customVariableWithIndexPath:(NSIndexPath *)indexPath {
+    DBCustomVariablesTableViewControllerSection section = (DBCustomVariablesTableViewControllerSection)indexPath.section;
+    switch (section) {
+        case DBCustomVariablesTableViewControllerSectionStringVariables:
+            return self.stringVariables[indexPath.row];
+        case DBCustomVariablesTableViewControllerSectionDoubleVariables:
+            return self.doubleVariables[indexPath.row];
+        case DBCustomVariablesTableViewControllerSectionIntVariables:
+            return self.intVariables[indexPath.row];
+        case DBCustomVariablesTableViewControllerSectionBoolVariables:
+            return self.boolVariables[indexPath.row];
+        default:
+            return nil;
+    }
+}
+
+#pragma mark - UITableViewDelegate
+
+- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section {
+    return [self heightForFooterAndHeaderInSection:section];
+}
+
+- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section {
+    return [self heightForFooterAndHeaderInSection:section];
+}
+
+#pragma mark - UITableViewDataSource
+
+- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
+    return 4;
+}
+
+- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
+    switch (section) {
+        case DBCustomVariablesTableViewControllerSectionStringVariables:
+            return self.stringVariables.count;
+        case DBCustomVariablesTableViewControllerSectionIntVariables:
+            return self.intVariables.count;
+        case DBCustomVariablesTableViewControllerSectionDoubleVariables:
+            return self.doubleVariables.count;
+        case DBCustomVariablesTableViewControllerSectionBoolVariables:
+            return self.boolVariables.count;
+        default:
+            return 0;
+    }
+}
+
+- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
+    if ([self tableView:tableView numberOfRowsInSection:section] == 0) {
+        return nil;
+    }
+    switch (section) {
+        case DBCustomVariablesTableViewControllerSectionStringVariables:
+            return @"Strings";
+        case DBCustomVariablesTableViewControllerSectionIntVariables:
+            return @"Integers";
+        case DBCustomVariablesTableViewControllerSectionDoubleVariables:
+            return @"Doubles";
+        case DBCustomVariablesTableViewControllerSectionBoolVariables:
+            return @"Booleans";
+        default:
+            return nil;
+    }
+}
+
+- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
+    DBCustomVariablesTableViewControllerSection section = (DBCustomVariablesTableViewControllerSection)indexPath.section;
+    switch (section) {
+        case DBCustomVariablesTableViewControllerSectionStringVariables:
+        case DBCustomVariablesTableViewControllerSectionIntVariables:
+        case DBCustomVariablesTableViewControllerSectionDoubleVariables:
+            return [self textViewCellWithIndexPath:indexPath];
+        case DBCustomVariablesTableViewControllerSectionBoolVariables:
+            return [self boolVariableCellWithRow:indexPath.row];
+        default:
+            return nil;
+    }
+}
+
+#pragma mark - DBTextViewTableViewCellDelegate
+
+- (void)textViewTableViewCellDidChangeText:(DBTextViewTableViewCell *)textViewCell {
+    NSIndexPath *cellIndexPath = [self.tableView indexPathForCell:textViewCell];
+    CGPoint currentContentOffset = self.tableView.contentOffset;
+    [UIView setAnimationsEnabled:NO];
+    [self.tableView beginUpdates];
+    [self.tableView endUpdates];
+    [UIView setAnimationsEnabled:YES];
+    [self.tableView setContentOffset:currentContentOffset animated:NO];
+    DBCustomVariable *customVariable = [self customVariableWithIndexPath:cellIndexPath];
+    [self updateCustomVariable:customVariable withValueFromText:textViewCell.textView.text];
+}
+
+- (BOOL)textViewTableViewCell:(DBTextViewTableViewCell *)textViewCell shouldChangeTextTo:(NSString *)text {
+    NSIndexPath *cellIndexPath = [self.tableView indexPathForCell:textViewCell];
+    DBCustomVariablesTableViewControllerSection section = (DBCustomVariablesTableViewControllerSection)cellIndexPath.section;
+    switch (section) {
+        case DBCustomVariablesTableViewControllerSectionIntVariables:
+            return [self isTextInt:text];
+        case DBCustomVariablesTableViewControllerSectionDoubleVariables:
+            return [self isTextDouble:text];
+        default:
+            return YES;
+    }
+}
+
+#pragma mark - DBMenuSwitchTableViewCell
+
+- (void)menuSwitchTableViewCell:(DBMenuSwitchTableViewCell *)menuSwitchTableViewCell didSetOn:(BOOL)isOn {
+    NSIndexPath *indexPath = [self.tableView indexPathForCell:menuSwitchTableViewCell];
+    DBCustomVariable *variable = self.boolVariables[indexPath.row];
+    variable.value = @(isOn);
+}
+
+#pragma mark - Private methods
+
+#pragma mark - - Section heights
+
+- (CGFloat)heightForFooterAndHeaderInSection:(NSInteger)section {
+    return [self tableView:self.tableView numberOfRowsInSection:section] > 0 ? UITableViewAutomaticDimension : CGFLOAT_MIN;
+}
+
+#pragma mark - - Parsing values
+
+- (void)updateCustomVariable:(DBCustomVariable *)variable withValueFromText:(NSString *)text {
+    switch (variable.type) {
+        case DBCustomVariableTypeString:
+            variable.value = text;
+            break;
+        case DBCustomVariableTypeInt:
+            if (text.length > 0) {
+                variable.value = [self integerFromText:text];
+            }
+            break;
+        case DBCustomVariableTypeDouble:
+            if (text.length > 0) {
+                variable.value = [self doubleFromText:text];
+            }
+            break;
+        default:
+            return;
+    }
+}
+
+- (BOOL)isTextDouble:(NSString *)text {
+    return [self isText:text matchingRegex:DBCustomVariablesTableViewControllerDoubleRegex];
+}
+
+- (BOOL)isTextInt:(NSString *)text {
+    return [self isText:text matchingRegex:DBCustomVariablesTableViewControllerIntegerRegex];
+}
+
+- (BOOL)isText:(NSString *)text matchingRegex:(NSString *)regex {
+    NSPredicate *regexPredicate = [NSPredicate predicateWithFormat:@"SELF MATCHES %@", regex];
+    return [regexPredicate evaluateWithObject:text];
+}
+
+- (NSNumber *)integerFromText:(NSString *)text {
+    NSInteger integerValue = [text integerValue];
+    return [NSNumber numberWithInteger:integerValue];
+}
+
+- (NSNumber *)doubleFromText:(NSString *)text {
+    CGFloat doubleValue = [text doubleValue];
+    return [NSNumber numberWithDouble:doubleValue];
+}
+
+#pragma mark - - Cells
+
+- (DBTextViewTableViewCell *)textViewCellWithIndexPath:(NSIndexPath *)indexPath {
+    DBTextViewTableViewCell *cell = (DBTextViewTableViewCell *)[self.tableView dequeueReusableCellWithIdentifier:DBCustomVariablesTableViewControllerTextViewCellIdentifier];
+    DBCustomVariable *variable = [self customVariableWithIndexPath:indexPath];
+    cell.titleLabel.text = variable.name;
+    cell.textView.text = [NSString stringWithFormat:@"%@", variable.value];
+    cell.textView.keyboardType = indexPath.section == DBCustomVariablesTableViewControllerSectionStringVariables ? UIKeyboardTypeDefault : UIKeyboardTypeNumbersAndPunctuation;
+    cell.delegate = self;
+    return cell;
+}
+
+- (DBMenuSwitchTableViewCell *)boolVariableCellWithRow:(NSInteger)row {
+    DBMenuSwitchTableViewCell *switchCell = (DBMenuSwitchTableViewCell *)[self.tableView dequeueReusableCellWithIdentifier:DBCustomVariablesTableViewControllerSwitchCellIdentifier];
+    DBCustomVariable *variable = self.boolVariables[row];
+    switchCell.titleLabel.text = variable.name;
+    switchCell.valueSwitch.on = [variable.value boolValue];
+    switchCell.delegate = self;
+    return switchCell;
+}
+
+@end

+ 198 - 0
Pods/DBDebugToolkit/DBDebugToolkit/Classes/DBDebugToolkit.h

@@ -0,0 +1,198 @@
+// The MIT License
+//
+// Copyright (c) 2016 Dariusz Bukowski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#import <Foundation/Foundation.h>
+#import "DBDebugToolkitTrigger.h"
+#import "DBCustomAction.h"
+#import "DBCustomVariable.h"
+
+/**
+ `DBDebugToolkit` provides the interface that can be used to setup and customize the debugging tools.
+ */
+@interface DBDebugToolkit : NSObject
+
+///------------
+/// @name Setup
+///------------
+
+/**
+ Sets up the `DBDebugToolkit` with one default trigger: `DBShakeTrigger`.
+*/
++ (void)setup;
+
+/**
+ Sets up the `DBDebugToolkit` with provided triggers.
+ 
+ @param triggers Array of triggers that should be used to open the `DBDebugToolkit` menu. The objects it contains should adopt the protocol `DBDebugToolkitTrigger`.
+ */
++ (void)setupWithTriggers:(NSArray <id <DBDebugToolkitTrigger>> *)triggers;
+
+
+///--------------------------
+/// @name Convenience methods
+///--------------------------
+
+/**
+ Enables or disables console output capturing, which by default is enabled.
+ 
+ @param enabled Determines whether console output capturing should be enabled or disabled.
+ */
++ (void)setCapturingConsoleOutputEnabled:(BOOL)enabled;
+
+/**
+ Enables or disables network requests logging, which by default is enabled.
+ 
+ @param enabled Determines whether network requests logging should be enabled or disabled.
+ */
++ (void)setNetworkRequestsLoggingEnabled:(BOOL)enabled;
+
+/**
+ Removes all your application's entries from the keychain.
+ */
++ (void)clearKeychain;
+
+/**
+ Removes all your application's entries from the `NSUserDefaults`.
+ */
++ (void)clearUserDefaults;
+
+/**
+ Removes all files from the documents directory.
+ */
++ (void)clearDocuments;
+
+/**
+ Removes all cookies.
+ */
++ (void)clearCookies;
+
+/**
+ Shows the menu.
+ */
++ (void)showMenu;
+
+/**
+ Shows the performance widget.
+ */
++ (void)showPerformanceWidget;
+
+/**
+ Shows the `UIDebuggingInformationOverlay` (if available).
+ */
++ (void)showDebuggingInformationOverlay;
+
+///---------------------
+/// @name Custom actions
+///---------------------
+
+/**
+ Adds a single `DBCustomAction` instance to the array accessible in the menu.
+ 
+ @param customAction The `DBCustomAction` instance that should be accessible in the menu.
+ */
++ (void)addCustomAction:(DBCustomAction *)customAction;
+
+/**
+ Adds multiple `DBCustomAction` instances to the array accessible in the menu.
+ 
+ @param customActions An array of `DBCustomAction` instances that should be accessible in the menu.
+ */
++ (void)addCustomActions:(NSArray <DBCustomAction *> *)customActions;
+
+/**
+ Removes a single `DBCustomAction` instance from the array accessible in the menu.
+ 
+ @param customAction The `DBCustomAction` instance that should be removed.
+ */
++ (void)removeCustomAction:(DBCustomAction *)customAction;
+
+/**
+ Removes multiple `DBCustomAction` instances from the array accessible in the menu.
+ 
+ @param customActions An array of `DBCustomAction` instances that should be accessible in the menu.
+ */
++ (void)removeCustomActions:(NSArray <DBCustomAction *> *)customActions;
+
+///-----------------------
+/// @name Custom variables
+///-----------------------
+
+/**
+ Adds a single `DBCustomVariable` instance to the array accessible in the menu.
+ 
+ @param customVariable The `DBCustomVariable` instance that should be accessible in the menu.
+ */
++ (void)addCustomVariable:(DBCustomVariable *)customVariable;
+
+/**
+ Adds multiple `DBCustomVariable` instances to the array accessible in the menu.
+ 
+ @param customVariables An array of `DBCustomVariable` instances that should be accessible in the menu.
+ */
++ (void)addCustomVariables:(NSArray <DBCustomVariable *> *)customVariables;
+
+/**
+ Removes a single `DBCustomVariable` instance with the given name.
+ 
+ @param variableName The name of the variable that should be removed.
+ */
++ (void)removeCustomVariableWithName:(NSString *)variableName;
+
+/**
+ Removes multiple `DBCustomVariable` instances with the names contained in the given array.
+ 
+ @param variableNames An array of the names of the variables that should be removed.
+ */
++ (void)removeCustomVariablesWithNames:(NSArray <NSString *> *)variableNames;
+
+/**
+ Returns a `DBCustomVariable` instance with a given name. If there is no such an instance, the method returns `nil`.
+ 
+ @param variableName The name of the accessed variable.
+ */
++ (DBCustomVariable *)customVariableWithName:(NSString *)variableName;
+
+///--------------------
+/// @name Crash reports
+///--------------------
+
+/**
+ Enables collecting crash reports.
+ */
++ (void)setupCrashReporting;
+
+
+///---------------------
+/// @name Shortcut Items
+///---------------------
+
+/**
+ Adds a `Clear data` shortcut item.
+ */
++ (void)addClearDataShortcutItem NS_AVAILABLE_IOS(9_0);
+
+/**
+ Handles `Clear data` shortcut item action.
+ */
++ (void)handleClearDataShortcutItemAction NS_AVAILABLE_IOS(9_0);
+
+@end

+ 497 - 0
Pods/DBDebugToolkit/DBDebugToolkit/Classes/DBDebugToolkit.m

@@ -0,0 +1,497 @@
+// The MIT License
+//
+// Copyright (c) 2016 Dariusz Bukowski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#import "DBDebugToolkit.h"
+#import "DBShakeTrigger.h"
+#import "DBMenuTableViewController.h"
+#import "NSBundle+DBDebugToolkit.h"
+#import "DBPerformanceWidgetView.h"
+#import "DBPerformanceToolkit.h"
+#import "DBPerformanceTableViewController.h"
+#import "DBConsoleOutputCaptor.h"
+#import "DBNetworkToolkit.h"
+#import "DBUserInterfaceToolkit.h"
+#import "DBLocationToolkit.h"
+#import "DBKeychainToolkit.h"
+#import "DBUserDefaultsToolkit.h"
+#import "DBCoreDataToolkit.h"
+#import "DBCrashReportsToolkit.h"
+#import "DBTopLevelViewsWrapper.h"
+#import "UIApplication+DBDebugToolkit.h"
+
+static NSString *const DBDebugToolkitObserverPresentationControllerPropertyKeyPath = @"containerView";
+
+@interface DBDebugToolkit () <DBDebugToolkitTriggerDelegate, DBMenuTableViewControllerDelegate, DBPerformanceWidgetViewDelegate>
+
+@property (nonatomic, copy) NSArray <id <DBDebugToolkitTrigger>> *triggers;
+@property (nonatomic, strong) DBMenuTableViewController *menuViewController;
+@property (nonatomic, assign) BOOL showsMenu;
+@property (nonatomic, strong) DBPerformanceToolkit *performanceToolkit;
+@property (nonatomic, strong) DBConsoleOutputCaptor *consoleOutputCaptor;
+@property (nonatomic, strong) DBNetworkToolkit *networkToolkit;
+@property (nonatomic, strong) DBUserInterfaceToolkit *userInterfaceToolkit;
+@property (nonatomic, strong) DBLocationToolkit *locationToolkit;
+@property (nonatomic, strong) DBCoreDataToolkit *coreDataToolkit;
+@property (nonatomic, strong) DBCrashReportsToolkit *crashReportsToolkit;
+@property (nonatomic, strong) NSMutableArray <DBCustomAction *> *customActions;
+@property (nonatomic, strong) NSMutableDictionary <NSString *, DBCustomVariable *> *customVariables;
+@property (nonatomic, strong) DBTopLevelViewsWrapper *topLevelViewsWrapper;
+
+@end
+
+@implementation DBDebugToolkit
+
+#pragma mark - Setup
+
++ (void)setup {
+    NSArray <id <DBDebugToolkitTrigger>> *defaultTriggers = [self defaultTriggers];
+    [self setupWithTriggers:defaultTriggers];
+}
+
++ (void)setupWithTriggers:(NSArray<id<DBDebugToolkitTrigger>> *)triggers {
+    DBDebugToolkit *toolkit = [DBDebugToolkit sharedInstance];
+    toolkit.triggers = triggers;
+}
+
++ (NSArray <id <DBDebugToolkitTrigger>> *)defaultTriggers {
+    return [NSArray arrayWithObject:[DBShakeTrigger trigger]];
+}
+
++ (instancetype)sharedInstance {
+    static DBDebugToolkit *sharedInstance = nil;
+    static dispatch_once_t onceToken;
+    dispatch_once(&onceToken, ^{
+        sharedInstance = [[DBDebugToolkit alloc] init];
+        [sharedInstance registerForNotifications];
+        [sharedInstance setupTopLevelViewsWrapper];
+        [sharedInstance setupPerformanceToolkit];
+        [sharedInstance setupConsoleOutputCaptor];
+        [sharedInstance setupNetworkToolkit];
+        [sharedInstance setupUserInterfaceToolkit];
+        [sharedInstance setupLocationToolkit];
+        [sharedInstance setupCoreDataToolkit];
+        [sharedInstance setupCustomActions];
+        [sharedInstance setupCustomVariables];
+        [sharedInstance setupCrashReportsToolkit];
+    });
+    return sharedInstance;
+}
+
+- (void)dealloc {
+    [[NSNotificationCenter defaultCenter] removeObserver:self];
+}
+
+#pragma mark - Setting triggers
+
+- (void)setTriggers:(NSArray<id<DBDebugToolkitTrigger>> *)triggers {
+    _triggers = [triggers copy];
+    UIWindow *keyWindow = [UIApplication sharedApplication].keyWindow;
+    [self addTriggersToWindow:keyWindow];
+    for (id <DBDebugToolkitTrigger> trigger in triggers) {
+        trigger.delegate = self;
+    }
+}
+
+- (void)addTriggersToWindow:(UIWindow *)window {
+    for (id <DBDebugToolkitTrigger> trigger in self.triggers) {
+        [trigger addToWindow:window];
+    }
+}
+
+- (void)removeTriggersFromWindow:(UIWindow *)window {
+    for (id <DBDebugToolkitTrigger> trigger in self.triggers) {
+        [trigger removeFromWindow:window];
+    }
+}
+
+#pragma mark - Window notifications
+
+- (void)registerForNotifications {
+    [[NSNotificationCenter defaultCenter] addObserver:self
+                                             selector:@selector(newKeyWindowNotification:)
+                                                 name:UIWindowDidBecomeKeyNotification
+                                               object:nil];
+    [[NSNotificationCenter defaultCenter] addObserver:self
+                                             selector:@selector(windowDidResignKeyNotification:)
+                                                 name:UIWindowDidResignKeyNotification
+                                               object:nil];
+}
+
+- (void)newKeyWindowNotification:(NSNotification *)notification {
+    UIWindow *newKeyWindow = notification.object;
+    [self addTriggersToWindow:newKeyWindow];
+    [self addTopLevelViewsWrapperToWindow:newKeyWindow];
+}
+
+- (void)windowDidResignKeyNotification:(NSNotification *)notification {
+    UIWindow *windowResigningKey = notification.object;
+    [self removeTriggersFromWindow:windowResigningKey];
+}
+
+#pragma mark - Performance toolkit
+
+- (void)setupPerformanceToolkit {
+    self.performanceToolkit = [[DBPerformanceToolkit alloc] initWithWidgetDelegate:self];
+    [self.topLevelViewsWrapper addTopLevelView:self.performanceToolkit.widget];
+}
+
+#pragma mark - Console output captor
+
+- (void)setupConsoleOutputCaptor {
+    self.consoleOutputCaptor = [DBConsoleOutputCaptor sharedInstance];
+    self.consoleOutputCaptor.enabled = YES;
+}
+
++ (void)setCapturingConsoleOutputEnabled:(BOOL)enabled {
+    DBDebugToolkit *toolkit = [DBDebugToolkit sharedInstance];
+    toolkit.consoleOutputCaptor.enabled = enabled;
+}
+
+#pragma mark - Network toolkit
+
+- (void)setupNetworkToolkit {
+    self.networkToolkit = [DBNetworkToolkit sharedInstance];
+    self.networkToolkit.loggingEnabled = YES;
+}
+
++ (void)setNetworkRequestsLoggingEnabled:(BOOL)enabled {
+    DBDebugToolkit *toolkit = [DBDebugToolkit sharedInstance];
+    toolkit.networkToolkit.loggingEnabled = enabled;
+}
+
+#pragma mark - User interface toolkit
+
+- (void)setupUserInterfaceToolkit {
+    self.userInterfaceToolkit = [DBUserInterfaceToolkit sharedInstance];
+    self.userInterfaceToolkit.colorizedViewBordersEnabled = NO;
+    self.userInterfaceToolkit.slowAnimationsEnabled = NO;
+    self.userInterfaceToolkit.showingTouchesEnabled = NO;
+    [self.userInterfaceToolkit setupDebuggingInformationOverlay];
+    [self.userInterfaceToolkit setupGridOverlay];
+    [self.topLevelViewsWrapper addTopLevelView:self.userInterfaceToolkit.gridOverlay];
+}
+
+#pragma mark - Location toolkit
+
+- (void)setupLocationToolkit {
+    self.locationToolkit = [DBLocationToolkit sharedInstance];
+}
+
+#pragma mark - Core Data toolkit
+
+- (void)setupCoreDataToolkit {
+    self.coreDataToolkit = [DBCoreDataToolkit sharedInstance];
+}
+
+#pragma mark - Custom actions
+
+- (void)setupCustomActions {
+    self.customActions = [NSMutableArray array];
+}
+
++ (void)addCustomAction:(DBCustomAction *)customAction {
+    DBDebugToolkit *toolkit = [DBDebugToolkit sharedInstance];
+    [toolkit.customActions addObject:customAction];
+}
+
++ (void)addCustomActions:(NSArray <DBCustomAction *> *)customActions {
+    DBDebugToolkit *toolkit = [DBDebugToolkit sharedInstance];
+    [toolkit.customActions addObjectsFromArray:customActions];
+}
+
++ (void)removeCustomAction:(DBCustomAction *)customAction {
+    DBDebugToolkit *toolkit = [DBDebugToolkit sharedInstance];
+    [toolkit.customActions removeObject:customAction];
+}
+
++ (void)removeCustomActions:(NSArray <DBCustomAction *> *)customActions {
+    DBDebugToolkit *toolkit = [DBDebugToolkit sharedInstance];
+    [toolkit.customActions removeObjectsInArray:customActions];
+}
+
+#pragma mark - Custom variables
+
+- (void)setupCustomVariables {
+    self.customVariables = [NSMutableDictionary dictionary];
+}
+
++ (void)addCustomVariable:(DBCustomVariable *)customVariable {
+    [self removeCustomVariableWithName:customVariable.name];
+    DBDebugToolkit *toolkit = [DBDebugToolkit sharedInstance];
+    toolkit.customVariables[customVariable.name] = customVariable;
+}
+
++ (void)addCustomVariables:(NSArray <DBCustomVariable *> *)customVariables {
+    for (DBCustomVariable *customVariable in customVariables) {
+        [self addCustomVariable:customVariable];
+    }
+}
+
++ (void)removeCustomVariableWithName:(NSString *)variableName {
+    DBDebugToolkit *toolkit = [DBDebugToolkit sharedInstance];
+    DBCustomVariable *customVariable = toolkit.customVariables[variableName];
+    [customVariable removeTarget:nil action:nil];
+    toolkit.customVariables[variableName] = nil;
+}
+
++ (void)removeCustomVariablesWithNames:(NSArray <NSString *> *)variableNames {
+    for (NSString *variableName in variableNames) {
+        [self removeCustomVariableWithName:variableName];
+    }
+}
+
++ (DBCustomVariable *)customVariableWithName:(NSString *)variableName {
+    DBDebugToolkit *toolkit = [DBDebugToolkit sharedInstance];
+    return toolkit.customVariables[variableName];
+}
+
+#pragma mark - Crash reports toolkit
+
+- (void)setupCrashReportsToolkit {
+    self.crashReportsToolkit = [DBCrashReportsToolkit sharedInstance];
+    self.crashReportsToolkit.consoleOutputCaptor = self.consoleOutputCaptor;
+    self.crashReportsToolkit.buildInfoProvider = [DBBuildInfoProvider new];
+    self.crashReportsToolkit.deviceInfoProvider = [DBDeviceInfoProvider new];
+}
+
++ (void)setupCrashReporting {
+    DBDebugToolkit *toolkit = [DBDebugToolkit sharedInstance];
+    [toolkit.crashReportsToolkit setupCrashReporting];
+}
+
+#pragma mark - Resources 
+
++ (void)clearKeychain {
+    DBKeychainToolkit *keychainToolkit = [DBKeychainToolkit new];
+    [keychainToolkit handleClearAction];
+}
+
++ (void)clearUserDefaults {
+    DBUserDefaultsToolkit *userDefaultsToolkit = [DBUserDefaultsToolkit new];
+    [userDefaultsToolkit handleClearAction];
+}
+
++ (void)clearDocuments {
+    NSFileManager *fileManager = [NSFileManager defaultManager];
+    NSString *documentsPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
+    NSArray <NSString *> *documentsContents = [fileManager contentsOfDirectoryAtPath:documentsPath error:nil];
+    for (NSString *file in documentsContents) {
+        NSString *filePath = [documentsPath stringByAppendingPathComponent:file];
+        [fileManager removeItemAtPath:filePath error:nil];
+    }
+}
+
++ (void)clearCookies {
+    NSHTTPCookieStorage *cookieStorage = [NSHTTPCookieStorage sharedHTTPCookieStorage];
+    for (NSHTTPCookie *cookie in cookieStorage.cookies) {
+        [cookieStorage deleteCookie:cookie];
+    }
+    [[NSUserDefaults standardUserDefaults] synchronize];
+}
+
+#pragma mark - Shortcut items
+
++ (void)addClearDataShortcutItem {
+    if (![[NSProcessInfo processInfo] isOperatingSystemAtLeastVersion:(NSOperatingSystemVersion){9, 0, 0}]) {
+        // Shortcut items are not supported on the running iOS version.
+        return;
+    }
+
+    NSArray <UIApplicationShortcutItem *> *shortcutItems = UIApplication.sharedApplication.shortcutItems;
+    for (UIApplicationShortcutItem *item in shortcutItems) {
+        if ([item.type isEqualToString:DBClearDataShortcutItemType]) {
+            // We already added `Clear data` shortcut item.
+            return;
+        }
+    }
+
+    UIApplicationShortcutIcon *shortcutIcon = [UIApplicationShortcutIcon iconWithType:UIApplicationShortcutIconTypeCompose];
+    UIApplicationShortcutItem *clearDataShortcutItem = [[UIApplicationShortcutItem alloc] initWithType:DBClearDataShortcutItemType
+                                                                                        localizedTitle:@"Clear data"
+                                                                                     localizedSubtitle:@"DBDebugToolkit"
+                                                                                                  icon:shortcutIcon
+                                                                                              userInfo:nil];
+    NSMutableArray *newShortcutItems = shortcutItems == nil ? [NSMutableArray array] : [NSMutableArray arrayWithArray:shortcutItems];
+    [newShortcutItems insertObject:clearDataShortcutItem atIndex:0];
+    UIApplication.sharedApplication.shortcutItems = [newShortcutItems copy];
+}
+
++ (void)handleClearDataShortcutItemAction {
+    [self clearUserDefaults];
+    [self clearKeychain];
+    [self clearDocuments];
+    [self clearCookies];
+}
+
+#pragma mark - Top level views
+
+- (void)setupTopLevelViewsWrapper {
+    self.topLevelViewsWrapper = [DBTopLevelViewsWrapper new];
+    UIWindow *keyWindow = [UIApplication sharedApplication].keyWindow;
+    [self addTopLevelViewsWrapperToWindow:keyWindow];
+}
+
+- (void)addTopLevelViewsWrapperToWindow:(UIWindow *)window {
+    [self.topLevelViewsWrapper.superview removeObserver:self forKeyPath:@"layer.sublayers"];
+    [window addSubview:self.topLevelViewsWrapper];
+    // We observe the "layer.sublayers" property of the window to keep the widget on top.
+    [window addObserver:self
+             forKeyPath:@"layer.sublayers"
+                options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld
+                context:nil];
+}
+
+#pragma mark - Convenience methods
+
++ (void)showMenu {
+    DBDebugToolkit *toolkit = [DBDebugToolkit sharedInstance];
+    if (!toolkit.showsMenu) {
+        [toolkit showMenu];
+    }
+}
+
++ (void)showPerformanceWidget {
+    DBDebugToolkit *toolkit = [DBDebugToolkit sharedInstance];
+    DBPerformanceToolkit *performanceToolkit = toolkit.performanceToolkit;
+    performanceToolkit.isWidgetShown = YES;
+}
+
++ (void)showDebuggingInformationOverlay {
+    DBDebugToolkit *toolkit = [DBDebugToolkit sharedInstance];
+    DBUserInterfaceToolkit *userInterfaceToolkit = toolkit.userInterfaceToolkit;
+    if (userInterfaceToolkit.isDebuggingInformationOverlayAvailable) {
+        [userInterfaceToolkit showDebuggingInformationOverlay];
+    }
+}
+
+#pragma mark - Showing menu
+
+- (void)showMenu {
+    self.showsMenu = YES;
+    UIViewController *presentingViewController = [self topmostViewController];
+    UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:self.menuViewController];
+    navigationController.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
+    navigationController.modalPresentationStyle = UIModalPresentationOverFullScreen;
+    navigationController.modalPresentationCapturesStatusBarAppearance = YES;
+    [presentingViewController presentViewController:navigationController animated:YES completion:^{
+        // We need this to properly handle a case of menu being dismissed because of dismissing of the view controller that presents it.
+        [navigationController.presentationController addObserver:self
+                                                      forKeyPath:DBDebugToolkitObserverPresentationControllerPropertyKeyPath
+                                                         options:0
+                                                         context:nil];
+    }];
+}
+
+- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
+    if ([object isKindOfClass:[UIPresentationController class]]) {
+        UIPresentationController *presentationController = (UIPresentationController *)object;
+        if (presentationController.containerView == nil) {
+            // The menu was dismissed.
+            self.showsMenu = NO;
+            [presentationController removeObserver:self forKeyPath:DBDebugToolkitObserverPresentationControllerPropertyKeyPath];
+        }
+    } else if ([object isKindOfClass:[UIWindow class]]) {
+        [self.topLevelViewsWrapper.superview bringSubviewToFront:self.topLevelViewsWrapper];
+    }
+}
+
+- (DBMenuTableViewController *)menuViewController {
+    if (!_menuViewController) {
+        NSBundle *bundle = [NSBundle debugToolkitBundle];
+        UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"DBMenuTableViewController" bundle:bundle];
+        _menuViewController = [storyboard instantiateInitialViewController];
+        _menuViewController.performanceToolkit = self.performanceToolkit;
+        _menuViewController.consoleOutputCaptor = self.consoleOutputCaptor;
+        _menuViewController.networkToolkit = self.networkToolkit;
+        _menuViewController.userInterfaceToolkit = self.userInterfaceToolkit;
+        _menuViewController.locationToolkit = self.locationToolkit;
+        _menuViewController.coreDataToolkit = self.coreDataToolkit;
+        _menuViewController.crashReportsToolkit = self.crashReportsToolkit;
+        _menuViewController.buildInfoProvider = [DBBuildInfoProvider new];
+        _menuViewController.deviceInfoProvider = [DBDeviceInfoProvider new];
+        _menuViewController.delegate = self;
+    }
+    _menuViewController.customVariables = self.customVariables.allValues;
+    _menuViewController.customActions = self.customActions;
+    return _menuViewController;
+}
+
+- (UIViewController *)topmostViewController {
+    UIViewController *rootViewController = [UIApplication sharedApplication].keyWindow.rootViewController;
+    return [self topmostViewControllerWithRootViewController:rootViewController];
+}
+
+- (UIViewController *)topmostViewControllerWithRootViewController:(UIViewController *)rootViewController {
+    if ([rootViewController isKindOfClass:[UITabBarController class]]) {
+        UITabBarController *tabBarController = (UITabBarController *)rootViewController;
+        return [self topmostViewControllerWithRootViewController:tabBarController.selectedViewController];
+    } else if ([rootViewController isKindOfClass:[UINavigationController class]]) {
+        UINavigationController *navigationController = (UINavigationController *)rootViewController;
+        if (navigationController.visibleViewController == nil) {
+            return navigationController;
+        }
+        return [self topmostViewControllerWithRootViewController:navigationController.visibleViewController];
+    } else if (rootViewController.presentedViewController) {
+        UIViewController* presentedViewController = rootViewController.presentedViewController;
+        return [self topmostViewControllerWithRootViewController:presentedViewController];
+    }
+    return rootViewController;
+}
+
+#pragma mark - DBDebugToolkitTriggerDelegate
+
+- (void)debugToolkitTriggered:(id<DBDebugToolkitTrigger>)trigger {
+    if (!self.showsMenu) {
+        [self showMenu];
+    }
+}
+
+#pragma mark - DBMenuTableViewControllerDelegate
+
+- (void)menuTableViewControllerDidTapClose:(DBMenuTableViewController *)menuTableViewController {
+    UIViewController *presentingViewController = self.menuViewController.navigationController.presentingViewController;
+    [presentingViewController dismissViewControllerAnimated:YES completion:^{
+        self.showsMenu = NO;
+    }];
+}
+
+#pragma mark - DBPerformanceWidgetViewDelegate 
+
+- (void)performanceWidgetView:(DBPerformanceWidgetView *)performanceWidgetView didTapOnSection:(DBPerformanceSection)section {
+    BOOL shouldAnimateShowingPerformance = YES;
+    if (!self.showsMenu) {
+        [self showMenu];
+        shouldAnimateShowingPerformance = NO;
+    }
+    UINavigationController *navigationController = self.menuViewController.navigationController;
+    if (navigationController.viewControllers.count > 1 && [navigationController.viewControllers[1] isKindOfClass:[DBPerformanceTableViewController class]]) {
+        // Only update the presented DBPerformanceTableViewController instance.
+        DBPerformanceTableViewController *performanceTableViewController = (DBPerformanceTableViewController *)navigationController.viewControllers[1];
+        performanceTableViewController.selectedSection = section;
+    } else {
+        // Update navigation controller's view controllers.
+        [self.menuViewController openPerformanceMenuWithSection:section
+                                                       animated:shouldAnimateShowingPerformance];
+    }
+}
+
+@end

+ 45 - 0
Pods/DBDebugToolkit/DBDebugToolkit/Classes/Device/DBDeviceInfoProvider.h

@@ -0,0 +1,45 @@
+// The MIT License
+//
+// Copyright (c) 2016 Dariusz Bukowski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#import <Foundation/Foundation.h>
+
+/**
+ `DBDeviceInfoProvider` provides helper methods returning information about the device.
+ */
+@interface DBDeviceInfoProvider : NSObject
+
+/**
+ Returns the device model string, e.g. "iPhone 7 Plus".
+ */
+- (NSString *)deviceModel;
+
+/**
+ Returns the system version string, e.g. "iOS 10.0.2".
+ */
+- (NSString *)systemVersion;
+
+/**
+ Returns device information in format: "<device model>, <system version>"
+ */
+- (NSString *)deviceInfoString;
+
+@end

+ 114 - 0
Pods/DBDebugToolkit/DBDebugToolkit/Classes/Device/DBDeviceInfoProvider.m

@@ -0,0 +1,114 @@
+// The MIT License
+//
+// Copyright (c) 2016 Dariusz Bukowski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#import "DBDeviceInfoProvider.h"
+#import <sys/utsname.h>
+
+@implementation DBDeviceInfoProvider
+
+// Based on the list provided in https://github.com/dennisweissmann/DeviceKit
+// DeviceKit is a value-type replacement of UIDevice created by Dennis Weissmann.
+- (NSString *)deviceModel {
+    struct utsname systemInfo;
+    uname(&systemInfo);
+    NSString *deviceModelCode = [NSString stringWithCString:systemInfo.machine encoding:NSUTF8StringEncoding];
+    if ([deviceModelCode isEqualToString:@"iPod5,1"]) {
+        return @"iPod Touch 5";
+    } else if ([deviceModelCode isEqualToString:@"iPod7,1"]) {
+        return @"iPod Touch 6";
+    } else if ([deviceModelCode isEqualToString:@"iPhone3,1"] || [deviceModelCode isEqualToString:@"iPhone3,2"] || [deviceModelCode isEqualToString:@"iPhone3,3"]) {
+        return @"iPhone 4";
+    } else if ([deviceModelCode isEqualToString:@"iPhone4,1"]) {
+        return @"iPhone 4s";
+    } else if ([deviceModelCode isEqualToString:@"iPhone5,1"] || [deviceModelCode isEqualToString:@"iPhone5,2"]) {
+        return @"iPhone 5";
+    } else if ([deviceModelCode isEqualToString:@"iPhone5,3"] || [deviceModelCode isEqualToString:@"iPhone5,4"]) {
+        return @"iPhone 5c";
+    } else if ([deviceModelCode isEqualToString:@"iPhone6,1"] || [deviceModelCode isEqualToString:@"iPhone6,2"]) {
+        return @"iPhone 5s";
+    } else if ([deviceModelCode isEqualToString:@"iPhone7,2"]) {
+        return @"iPhone 6";
+    } else if ([deviceModelCode isEqualToString:@"iPhone7,1"]) {
+        return @"iPhone 6 Plus";
+    } else if ([deviceModelCode isEqualToString:@"iPhone8,1"]) {
+        return @"iPhone 6s";
+    } else if ([deviceModelCode isEqualToString:@"iPhone8,2"]) {
+        return @"iPhone 6s Plus";
+    } else if ([deviceModelCode isEqualToString:@"iPhone9,1"] || [deviceModelCode isEqualToString:@"iPhone9,3"]) {
+        return @"iPhone 7";
+    } else if ([deviceModelCode isEqualToString:@"iPhone9,2"] || [deviceModelCode isEqualToString:@"iPhone9,4"]) {
+        return @"iPhone 7 Plus";
+    } else if ([deviceModelCode isEqualToString:@"iPhone8,4"]) {
+        return @"iPhone SE";
+    } else if ([deviceModelCode isEqualToString:@"iPhone10,1"] || [deviceModelCode isEqualToString:@"iPhone10,4"]) {
+        return @"iPhone 8";
+    } else if ([deviceModelCode isEqualToString:@"iPhone10,2"] || [deviceModelCode isEqualToString:@"iPhone10,5"]) {
+        return @"iPhone 8 Plus";
+    } else if ([deviceModelCode isEqualToString:@"iPhone10,3"] || [deviceModelCode isEqualToString:@"iPhone10,6"]) {
+        return @"iPhone X";
+    } else if ([deviceModelCode isEqualToString:@"iPad2,1"] || [deviceModelCode isEqualToString:@"iPad2,2"] || [deviceModelCode isEqualToString:@"iPad2,3"] || [deviceModelCode isEqualToString:@"iPad2,4"]) {
+        return @"iPad 2";
+    } else if ([deviceModelCode isEqualToString:@"iPad3,1"] || [deviceModelCode isEqualToString:@"iPad3,2"] || [deviceModelCode isEqualToString:@"iPad3,3"]) {
+        return @"iPad 3";
+    } else if ([deviceModelCode isEqualToString:@"iPad3,4"] || [deviceModelCode isEqualToString:@"iPad3,5"] || [deviceModelCode isEqualToString:@"iPad3,6"]) {
+        return @"iPad 4";
+    } else if ([deviceModelCode isEqualToString:@"iPad4,1"] || [deviceModelCode isEqualToString:@"iPad4,2"] || [deviceModelCode isEqualToString:@"iPad4,3"]) {
+        return @"iPad Air";
+    } else if ([deviceModelCode isEqualToString:@"iPad5,3"] || [deviceModelCode isEqualToString:@"iPad5,4"]) {
+        return @"iPad Air 2";
+    } else if ([deviceModelCode isEqualToString:@"iPad2,5"] || [deviceModelCode isEqualToString:@"iPad2,6"] || [deviceModelCode isEqualToString:@"iPad2,7"]) {
+        return @"iPad Mini";
+    } else if ([deviceModelCode isEqualToString:@"iPad4,4"] || [deviceModelCode isEqualToString:@"iPad4,5"] || [deviceModelCode isEqualToString:@"iPad4,6"]) {
+        return @"iPad Mini 2";
+    } else if ([deviceModelCode isEqualToString:@"iPad4,7"] || [deviceModelCode isEqualToString:@"iPad4,8"] || [deviceModelCode isEqualToString:@"iPad4,9"]) {
+        return @"iPad Mini 3";
+    } else if ([deviceModelCode isEqualToString:@"iPad5,1"] || [deviceModelCode isEqualToString:@"iPad5,2"]) {
+        return @"iPad Mini 4";
+    } else if ([deviceModelCode isEqualToString:@"iPad6,3"] || [deviceModelCode isEqualToString:@"iPad6,4"]) {
+        return @"iPad Pro (9.7-inch)";
+    } else if ([deviceModelCode isEqualToString:@"iPad6,7"] || [deviceModelCode isEqualToString:@"iPad6,8"]) {
+        return @"iPad Pro (12.9-inch)";
+    } else if ([deviceModelCode isEqualToString:@"iPad7,1"] || [deviceModelCode isEqualToString:@"iPad7,2"]) {
+        return @"iPad Pro (12.9-inch) 2";
+    } else if ([deviceModelCode isEqualToString:@"iPad7,3"] || [deviceModelCode isEqualToString:@"iPad7,4"]) {
+        return @"iPad Pro (10.5-inch)";
+    } else if ([deviceModelCode isEqualToString:@"i386"] || [deviceModelCode isEqualToString:@"x86_64"]) {
+        return @"Simulator";
+    } else {
+        return [[UIDevice currentDevice] model];
+    }
+}
+
+//iPhone10,1", "iPhone10,4"],                  4.7,  (9, 16),  "iPhone 8", 326, False, False),
+//Device("iPhone8Plus",     "Device is an [iPhone 8 Plus](https://support.apple.com/kb/SP768)",               "https://support.apple.com/library/APPLE/APPLECARE_ALLGEOS/SP768/iphone8plus.png",                          ["iPhone10,2", "iPhone10,5"],                  5.5,  (9, 16),  "iPhone 8 Plus", 401, True, False),
+//Device("iPhoneX",         "Device is an [iPhone X](https://support.apple.com/kb/SP770)",                    "https://support.apple.com/library/APPLE/APPLECARE_ALLGEOS/SP770/iphonex.png",                              ["iPhone10,3", "iPhone10,6"],                  5.8,  (9, 19.5),  "iPhone X", 458, False, False)
+
+- (NSString *)systemVersion {
+    UIDevice *device = [UIDevice currentDevice];
+    return [NSString stringWithFormat:@"%@ %@", [device systemName], [device systemVersion]];
+}
+
+- (NSString *)deviceInfoString {
+    return [NSString stringWithFormat:@"%@, %@", [self deviceModel], [self systemVersion]];
+}
+
+@end

+ 30 - 0
Pods/DBDebugToolkit/DBDebugToolkit/Classes/Location/CLLocationManager+DBLocationToolkit.h

@@ -0,0 +1,30 @@
+// The MIT License
+//
+// Copyright (c) 2016 Dariusz Bukowski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#import <CoreLocation/CoreLocation.h>
+
+/**
+ `CLLocationManager` category that replaces private methods to simulate location.
+ */
+@interface CLLocationManager (DBLocationToolkit)
+
+@end

+ 62 - 0
Pods/DBDebugToolkit/DBDebugToolkit/Classes/Location/CLLocationManager+DBLocationToolkit.m

@@ -0,0 +1,62 @@
+// The MIT License
+//
+// Copyright (c) 2016 Dariusz Bukowski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#import "CLLocationManager+DBLocationToolkit.h"
+#import "DBLocationToolkit.h"
+#import "NSObject+DBDebugToolkit.h"
+#import <objc/runtime.h>
+
+static NSString *const CLLocationManagerLocationsKey = @"Locations";
+
+@implementation CLLocationManager (DBLocationToolkit)
+
++ (void)load {
+    static dispatch_once_t onceToken;
+    dispatch_once(&onceToken, ^{
+        // Making sure to minimize the risk of rejecting app because of the private API.
+        NSString *key = [[NSString alloc] initWithData:[NSData dataWithBytes:(unsigned char []){0x6f, 0x6e, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x4c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x3a} length:22] encoding:NSASCIIStringEncoding];
+        [self exchangeInstanceMethodsWithOriginalSelector:NSSelectorFromString(key)
+                                      andSwizzledSelector:@selector(db_onClientEventLocation:)];
+        // Making sure to minimize the risk of rejecting app because of the private API.
+        key = [[NSString alloc] initWithData:[NSData dataWithBytes:(unsigned char []){0x6f, 0x6e, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x4c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x3a, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x4d, 0x61, 0x70, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x69, 0x6e, 0x67, 0x3a, 0x74, 0x79, 0x70, 0x65, 0x3a} length:44] encoding:NSASCIIStringEncoding];
+        [self exchangeInstanceMethodsWithOriginalSelector:NSSelectorFromString(key)
+                                      andSwizzledSelector:@selector(db_onClientEventLocation:forceMapMatching:type:)];
+    });
+}
+
+- (void)db_onClientEventLocation:(NSDictionary *)dictionary {
+    if ([DBLocationToolkit sharedInstance].simulatedLocation == nil) {
+        [self db_onClientEventLocation:dictionary];
+    } else {
+        [self.delegate locationManager:self didUpdateLocations:@[[DBLocationToolkit sharedInstance].simulatedLocation]];
+    }
+}
+
+- (void)db_onClientEventLocation:(NSDictionary *)dictionary forceMapMatching:(BOOL)forceMapMatching type:(id)type {
+    if ([DBLocationToolkit sharedInstance].simulatedLocation == nil) {
+        [self db_onClientEventLocation:dictionary forceMapMatching:forceMapMatching type:type];
+    } else {
+        [self.delegate locationManager:self didUpdateLocations:@[[DBLocationToolkit sharedInstance].simulatedLocation]];
+    }
+}
+
+@end

+ 66 - 0
Pods/DBDebugToolkit/DBDebugToolkit/Classes/Location/DBCustomLocationViewController.h

@@ -0,0 +1,66 @@
+// The MIT License
+//
+// Copyright (c) 2016 Dariusz Bukowski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#import <UIKit/UIKit.h>
+#import <CoreLocation/CoreLocation.h>
+
+@class DBCustomLocationViewController;
+
+/**
+ A protocol used for informing the presenting view controller about the new selected location and about the need to dismiss the `DBCustomLocationViewController` instance.
+ */
+@protocol DBCustomLocationViewControllerDelegate <NSObject>
+
+/**
+ Informs the delegate that the user finished selecting a new location.
+ 
+ @param customLocationViewController The `DBCustomLocationViewController` that has a new location and needs to be dismissed.
+ @param location The selected location.
+ */
+- (void)customLocationViewController:(DBCustomLocationViewController *)customLocationViewController
+                   didSelectLocation:(CLLocation *)location;
+
+/**
+ Informs the delegate that the user cancelled selecting a new location.
+ 
+ @param customLocationViewController The `DBCustomLocationViewController` that needs to be dismissed.
+ */
+- (void)customLocationViewControllerDidTapCancelButton:(DBCustomLocationViewController *)customLocationViewController;
+
+@end
+
+/**
+ `DBCustomLocationViewController` is a simple view controller allowing the user to select a location on a map.
+ */
+@interface DBCustomLocationViewController : UIViewController
+
+/**
+ The currently selected location. Can be used to provide the location before showing `DBCustomLocationViewController` instance.
+ */
+@property (nonatomic, strong) CLLocation *selectedLocation;
+
+/**
+ Delegate that will be informed about the new selected location and about the need to dismiss the presented view controller. It needs to conform to `DBCustomLocationViewControllerDelegate` protocol.
+ */
+@property (nonatomic, weak) id <DBCustomLocationViewControllerDelegate> delegate;
+
+@end

+ 85 - 0
Pods/DBDebugToolkit/DBDebugToolkit/Classes/Location/DBCustomLocationViewController.m

@@ -0,0 +1,85 @@
+// The MIT License
+//
+// Copyright (c) 2016 Dariusz Bukowski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#import "DBCustomLocationViewController.h"
+#import <MapKit/MapKit.h>
+
+@interface DBCustomLocationViewController ()
+
+@property (nonatomic, weak) IBOutlet MKMapView *mapView;
+@property (nonatomic, strong) MKPointAnnotation *selectedLocationAnnotation;
+@property (nonatomic, strong) UITapGestureRecognizer *tapGestureRecognizer;
+@property (nonatomic, weak) IBOutlet UIBarButtonItem *doneButton;
+
+@end
+
+@implementation DBCustomLocationViewController
+
+- (void)viewDidLoad {
+    [super viewDidLoad];
+    [self setupGestureRecognizer];
+    if (self.selectedLocation) {
+        [self.mapView setCenterCoordinate:self.selectedLocation.coordinate];
+        self.selectedLocationAnnotation.coordinate = self.selectedLocation.coordinate;
+        [self.mapView addAnnotation:self.selectedLocationAnnotation];
+    }
+}
+
+#pragma mark - Navigation bar button actions
+
+- (IBAction)cancelButtonAction:(id)sender {
+    [self.delegate customLocationViewControllerDidTapCancelButton:self];
+}
+
+- (IBAction)doneButtonAction:(id)sender {
+    [self.delegate customLocationViewController:self
+                              didSelectLocation:self.selectedLocation];
+}
+
+#pragma mark - Selected location
+
+- (MKPointAnnotation *)selectedLocationAnnotation {
+    if (!_selectedLocationAnnotation) {
+        _selectedLocationAnnotation = [[MKPointAnnotation alloc] init];
+    }
+    
+    return _selectedLocationAnnotation;
+}
+
+#pragma mark - Gesture recognizer
+
+- (void)setupGestureRecognizer {
+    self.tapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapGestureAction:)];
+    [self.mapView addGestureRecognizer:self.tapGestureRecognizer];
+}
+
+- (void)tapGestureAction:(UITapGestureRecognizer *)gestureRecognizer {
+    CGPoint tapPoint = [gestureRecognizer locationInView:self.mapView];
+    CLLocationCoordinate2D coordinate = [self.mapView convertPoint:tapPoint toCoordinateFromView:self.mapView];
+    self.selectedLocation = [[CLLocation alloc] initWithLatitude:coordinate.latitude
+                                                       longitude:coordinate.longitude];
+    self.selectedLocationAnnotation.coordinate = coordinate;
+    [self.mapView addAnnotation:self.selectedLocationAnnotation];
+    self.doneButton.enabled = YES;
+}
+
+@end

+ 36 - 0
Pods/DBDebugToolkit/DBDebugToolkit/Classes/Location/DBLocationTableViewController.h

@@ -0,0 +1,36 @@
+// The MIT License
+//
+// Copyright (c) 2016 Dariusz Bukowski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#import <UIKit/UIKit.h>
+#import "DBLocationToolkit.h"
+
+/**
+ `DBLocationTableViewController` is a view controller allowing the user to select a simulated location.
+ */
+@interface DBLocationTableViewController : UITableViewController
+
+/**
+ `DBLocationToolkit` object that handles the location simulation. It also provides the list of preset locations.
+ */
+@property (nonatomic, strong) DBLocationToolkit *locationToolkit;
+
+@end

+ 181 - 0
Pods/DBDebugToolkit/DBDebugToolkit/Classes/Location/DBLocationTableViewController.m

@@ -0,0 +1,181 @@
+// The MIT License
+//
+// Copyright (c) 2016 Dariusz Bukowski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#import "DBLocationTableViewController.h"
+#import "NSBundle+DBDebugToolkit.h"
+#import "DBCustomLocationViewController.h"
+
+static NSString *const DBLocationTableViewControllerSelectedCustomCellIdentifier = @"DBDebugToolkit_selectedCustomCell";
+static NSString *const DBLocationTableViewControllerSimpleCellIdentifier = @"DBDebugToolkit_simpleCell";
+
+@interface DBLocationTableViewController () <DBCustomLocationViewControllerDelegate>
+
+@property (nonatomic, strong) NSNumber *selectedIndex;
+@property (nonatomic, weak) IBOutlet UIBarButtonItem *resetButton;
+
+@end
+
+@implementation DBLocationTableViewController
+
+- (void)viewDidLoad {
+    [super viewDidLoad];
+    self.resetButton.enabled = self.locationToolkit.simulatedLocation != nil;
+}
+
+- (IBAction)resetButtonAction:(id)sender {
+    self.locationToolkit.simulatedLocation = nil;
+    self.selectedIndex = @-1;
+    self.resetButton.enabled = NO;
+    [self.tableView reloadData];
+}
+
+- (NSNumber *)selectedIndex {
+    if (!_selectedIndex) {
+        CLLocation *simulatedLocation = self.locationToolkit.simulatedLocation;
+        if (simulatedLocation == nil) {
+            _selectedIndex = @-1;
+        } else {
+            for (int i = 0; i < self.locationToolkit.presetLocations.count; i++) {
+                DBPresetLocation *presetLocation = self.locationToolkit.presetLocations[i];
+                if (ABS(presetLocation.latitude - simulatedLocation.coordinate.latitude) < DBL_EPSILON
+                    && ABS(presetLocation.longitude - simulatedLocation.coordinate.longitude) < DBL_EPSILON) {
+                    _selectedIndex = @(i + 1);
+                }
+            }
+            if (!_selectedIndex) {
+                _selectedIndex = @0;
+            }
+        }
+    }
+    
+    return _selectedIndex;
+}
+
+#pragma mark - UITableViewDataSource
+
+- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
+    return 1;
+}
+
+- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
+    return self.locationToolkit.presetLocations.count + 1;
+}
+
+- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
+    return @"Simulated location";
+}
+
+- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
+    if (self.selectedIndex.integerValue == 0 && indexPath.row == 0) {
+        return [self selectedCustomCell];
+    } else {
+        return [self simpleCellForIndexPath:indexPath];
+    }
+}
+
+#pragma mark - UITableViewDelegate
+
+- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
+    if (indexPath.row > 0) {
+        self.selectedIndex = @(indexPath.row);
+        DBPresetLocation *presetLocation = self.locationToolkit.presetLocations[indexPath.row - 1];
+        self.locationToolkit.simulatedLocation = [[CLLocation alloc] initWithLatitude:presetLocation.latitude
+                                                                            longitude:presetLocation.longitude];
+        self.resetButton.enabled = YES;
+        [self.tableView reloadData];
+    } else {
+        NSBundle *bundle = [NSBundle debugToolkitBundle];
+        UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"DBCustomLocationViewController" bundle:bundle];
+        DBCustomLocationViewController *customLocationViewController = [storyboard instantiateInitialViewController];
+        customLocationViewController.delegate = self;
+        customLocationViewController.selectedLocation = self.locationToolkit.simulatedLocation;
+        UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:customLocationViewController];
+        [self.navigationController presentViewController:navigationController animated:YES completion:nil];
+    }
+}
+
+#pragma mark - DBCustomLocationViewControllerDelegate
+
+- (void)customLocationViewController:(DBCustomLocationViewController *)customLocationViewController didSelectLocation:(CLLocation *)location {
+    self.locationToolkit.simulatedLocation = location;
+    self.resetButton.enabled = YES;
+    self.selectedIndex = @0;
+    [self.tableView reloadData];
+    [self.navigationController dismissViewControllerAnimated:YES completion:nil];
+}
+
+- (void)customLocationViewControllerDidTapCancelButton:(DBCustomLocationViewController *)customLocationViewController {
+    [self.navigationController dismissViewControllerAnimated:YES completion:nil];
+}
+
+#pragma mark - Private methods
+
+- (UITableViewCell *)selectedCustomCell {
+    UITableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:DBLocationTableViewControllerSelectedCustomCellIdentifier];
+    if (!cell) {
+        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle
+                                      reuseIdentifier:DBLocationTableViewControllerSelectedCustomCellIdentifier];
+        cell.textLabel.textColor = cell.tintColor;
+        cell.detailTextLabel.textColor = cell.tintColor;
+        cell.accessoryType = UITableViewCellAccessoryCheckmark;
+    }
+    cell.textLabel.text = @"Custom";
+    cell.detailTextLabel.text = [self coordinateStringWithCoordinate:self.locationToolkit.simulatedLocation.coordinate];
+    return cell;
+}
+
+- (UITableViewCell *)simpleCellForIndexPath:(NSIndexPath *)indexPath {
+    UITableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:DBLocationTableViewControllerSimpleCellIdentifier];
+    if (!cell) {
+        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
+                                      reuseIdentifier:DBLocationTableViewControllerSimpleCellIdentifier];
+    }
+    BOOL isSelected = self.selectedIndex.integerValue == indexPath.row;
+    cell.textLabel.textColor = isSelected ? cell.tintColor : [UIColor blackColor];
+    cell.textLabel.text = indexPath.row == 0 ? @"Custom..." : self.locationToolkit.presetLocations[indexPath.row - 1].title;
+    cell.accessoryType = isSelected ? UITableViewCellAccessoryCheckmark : UITableViewCellAccessoryNone;
+    return cell;
+}
+
+- (NSString *)coordinateStringWithCoordinate:(CLLocationCoordinate2D)coordinate {
+    NSString *latitudeDegreesMinutesSeconds = [self degreesMinutesSecondsWithCoordinate:coordinate.latitude];
+    NSString *latitudeDirectionLetter = coordinate.latitude >= 0 ? @"N" : @"S";
+
+    NSString *longitudeDegreesMinutesSeconds = [self degreesMinutesSecondsWithCoordinate:coordinate.longitude];
+    NSString *longitudeDirectionLetter = coordinate.longitude >= 0 ? @"E" : @"W";
+    
+    return [NSString stringWithFormat:@"%@%@, %@%@", latitudeDegreesMinutesSeconds,
+                                                     latitudeDirectionLetter,
+                                                     longitudeDegreesMinutesSeconds,
+                                                     longitudeDirectionLetter];
+}
+
+- (NSString *)degreesMinutesSecondsWithCoordinate:(CLLocationDegrees)coordinate {
+    NSInteger seconds = coordinate * 3600;
+    NSInteger degrees = seconds / 3600;
+    seconds = ABS(seconds % 3600);
+    NSInteger minutes = seconds / 60;
+    seconds %= 60;
+    return [NSString stringWithFormat:@"%ld°%ld'%ld\"", ABS((long)degrees), (long)minutes, (long)seconds];
+}
+
+@end

+ 47 - 0
Pods/DBDebugToolkit/DBDebugToolkit/Classes/Location/DBLocationToolkit.h

@@ -0,0 +1,47 @@
+// The MIT License
+//
+// Copyright (c) 2016 Dariusz Bukowski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#import <Foundation/Foundation.h>
+#import <CoreLocation/CoreLocation.h>
+#import "DBPresetLocation.h"
+
+/**
+ `DBLocationToolkit` is a class responsible for simulating location and providing a list of preset locations.
+ */
+@interface DBLocationToolkit : NSObject
+
+/**
+ Returns the singleton instance.
+ */
++ (instancetype)sharedInstance;
+
+/**
+ Current simulated location.
+ */
+@property (nonatomic, strong) CLLocation *simulatedLocation;
+
+/**
+ A list of preset locations. Read-only.
+ */
+@property (nonatomic, readonly) NSArray <DBPresetLocation *> *presetLocations;
+
+@end

+ 125 - 0
Pods/DBDebugToolkit/DBDebugToolkit/Classes/Location/DBLocationToolkit.m

@@ -0,0 +1,125 @@
+// The MIT License
+//
+// Copyright (c) 2016 Dariusz Bukowski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#import "DBLocationToolkit.h"
+#import "DBDebugToolkitUserDefaultsKeys.h"
+
+@interface DBLocationToolkit ()
+
+@property (nonatomic, strong) NSArray <DBPresetLocation *> *presetLocations;
+
+@end
+
+@implementation DBLocationToolkit
+
+@synthesize simulatedLocation;
+
+#pragma mark - Initialization
+
++ (instancetype)sharedInstance {
+    static DBLocationToolkit *sharedInstance = nil;
+    static dispatch_once_t onceToken;
+    dispatch_once(&onceToken, ^{
+        sharedInstance = [[DBLocationToolkit alloc] init];
+    });
+    return sharedInstance;
+}
+
+#pragma mark - Simulated location
+
+- (void)setSimulatedLocation:(CLLocation *)aSimulatedLocation {
+    if (aSimulatedLocation != simulatedLocation) {
+        simulatedLocation = aSimulatedLocation;
+        if (simulatedLocation) {
+            [[NSUserDefaults standardUserDefaults] setObject:@(simulatedLocation.coordinate.latitude)
+                                                      forKey:DBDebugToolkitUserDefaultsSimulatedLocationLatitudeKey];
+            [[NSUserDefaults standardUserDefaults] setObject:@(simulatedLocation.coordinate.longitude)
+                                                      forKey:DBDebugToolkitUserDefaultsSimulatedLocationLongitudeKey];
+        } else {
+            [[NSUserDefaults standardUserDefaults] removeObjectForKey:DBDebugToolkitUserDefaultsSimulatedLocationLatitudeKey];
+            [[NSUserDefaults standardUserDefaults] removeObjectForKey:DBDebugToolkitUserDefaultsSimulatedLocationLongitudeKey];
+        }
+        [[NSUserDefaults standardUserDefaults] synchronize];
+    }
+}
+
+- (CLLocation *)simulatedLocation {
+    if (!simulatedLocation) {
+        NSNumber *latitude = [[NSUserDefaults standardUserDefaults] objectForKey:DBDebugToolkitUserDefaultsSimulatedLocationLatitudeKey];
+        NSNumber *longitude = [[NSUserDefaults standardUserDefaults] objectForKey:DBDebugToolkitUserDefaultsSimulatedLocationLongitudeKey];
+        if (latitude != nil && longitude != nil) {
+            simulatedLocation = [[CLLocation alloc] initWithLatitude:[latitude doubleValue]
+                                                           longitude:[longitude doubleValue]];
+        }
+    }
+    
+    return simulatedLocation;
+}
+
+#pragma mark - Preset locations
+
+- (NSArray <DBPresetLocation *> *)presetLocations {
+    if (!_presetLocations) {
+        NSMutableArray *presetLocations = [NSMutableArray array];
+        [presetLocations addObject:[DBPresetLocation presetLocationWithTitle:@"London, England"
+                                                                    latitude:51.509980
+                                                                   longitude:-0.133700]];
+        [presetLocations addObject:[DBPresetLocation presetLocationWithTitle:@"Johannesburg, South Africa"
+                                                                    latitude:-26.204103
+                                                                   longitude:28.047305]];
+        [presetLocations addObject:[DBPresetLocation presetLocationWithTitle:@"Moscow, Russia"
+                                                                    latitude:55.755786
+                                                                   longitude:37.617633]];
+        [presetLocations addObject:[DBPresetLocation presetLocationWithTitle:@"Mumbai, India"
+                                                                    latitude:19.017615
+                                                                   longitude:72.856164]];
+        [presetLocations addObject:[DBPresetLocation presetLocationWithTitle:@"Tokyo, Japan"
+                                                                    latitude:35.702069
+                                                                   longitude:139.775327]];
+        [presetLocations addObject:[DBPresetLocation presetLocationWithTitle:@"Sydney, Australia"
+                                                                    latitude:-33.863400
+                                                                   longitude:151.211000]];
+        [presetLocations addObject:[DBPresetLocation presetLocationWithTitle:@"Hong Kong, China"
+                                                                    latitude:22.284681
+                                                                   longitude:114.158177]];
+        [presetLocations addObject:[DBPresetLocation presetLocationWithTitle:@"Honolulu, HI, USA"
+                                                                    latitude:21.282778
+                                                                   longitude:-157.829444]];
+        [presetLocations addObject:[DBPresetLocation presetLocationWithTitle:@"San Francisco, CA, USA"
+                                                                    latitude:37.787359
+                                                                   longitude:-122.408227]];
+        [presetLocations addObject:[DBPresetLocation presetLocationWithTitle:@"Mexico City, Mexico"
+                                                                    latitude:19.435478
+                                                                   longitude:-99.136479]];
+        [presetLocations addObject:[DBPresetLocation presetLocationWithTitle:@"New York, NY, USA"
+                                                                    latitude:40.759211
+                                                                   longitude:-73.984638]];
+        [presetLocations addObject:[DBPresetLocation presetLocationWithTitle:@"Rio de Janeiro, Brazil"
+                                                                    latitude:-22.903539
+                                                                   longitude:-43.209587]];
+        _presetLocations = [presetLocations copy];
+    }
+    
+    return _presetLocations;
+}
+
+@end

+ 54 - 0
Pods/DBDebugToolkit/DBDebugToolkit/Classes/Location/DBPresetLocation.h

@@ -0,0 +1,54 @@
+// The MIT License
+//
+// Copyright (c) 2016 Dariusz Bukowski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#import <Foundation/Foundation.h>
+
+/**
+ `DBPresetLocation` is a simple wrapper that contains all the basic information about a preset location.
+ */
+@interface DBPresetLocation : NSObject
+
+/**
+ Creates and returns a new instance wrapping the information about a preset location.
+ 
+ @param title Simple title describing the location.
+ @param latitude The latitude of the location.
+ @param longitude The longitude of the location.
+ */
++ (instancetype)presetLocationWithTitle:(NSString *)title latitude:(CGFloat)latitude longitude:(CGFloat)longitude;
+
+/**
+ Simple title describing the location, e.g. "San Francisco, CA, USA". Read-only.
+ */
+@property (nonatomic, readonly) NSString *title;
+
+/**
+ The latitude of the location. Read-only.
+ */
+@property (nonatomic, readonly) CGFloat latitude;
+
+/**
+ The longitude of the location. Read-only.
+ */
+@property (nonatomic, readonly) CGFloat longitude;
+
+@end

+ 44 - 0
Pods/DBDebugToolkit/DBDebugToolkit/Classes/Location/DBPresetLocation.m

@@ -0,0 +1,44 @@
+// The MIT License
+//
+// Copyright (c) 2016 Dariusz Bukowski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#import "DBPresetLocation.h"
+
+@implementation DBPresetLocation
+
+#pragma mark - Initialization
+
+- (instancetype)initWithTitle:(NSString *)title latitude:(CGFloat)latitude longitude:(CGFloat)longitude {
+    self = [super init];
+    if (self) {
+        _title = title;
+        _latitude = latitude;
+        _longitude = longitude;
+    }
+    
+    return self;
+}
+
++ (instancetype)presetLocationWithTitle:(NSString *)title latitude:(CGFloat)latitude longitude:(CGFloat)longitude {
+    return [[self alloc] initWithTitle:title latitude:latitude longitude:longitude];
+}
+
+@end

+ 125 - 0
Pods/DBDebugToolkit/DBDebugToolkit/Classes/Menu/DBMenuTableViewController.h

@@ -0,0 +1,125 @@
+// The MIT License
+//
+// Copyright (c) 2016 Dariusz Bukowski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#import <UIKit/UIKit.h>
+#import "DBPerformanceToolkit.h"
+#import "DBConsoleOutputCaptor.h"
+#import "DBBuildInfoProvider.h"
+#import "DBDeviceInfoProvider.h"
+#import "DBNetworkToolkit.h"
+#import "DBUserInterfaceToolkit.h"
+#import "DBLocationToolkit.h"
+#import "DBCoreDataToolkit.h"
+#import "DBCustomAction.h"
+#import "DBCustomVariable.h"
+#import "DBCrashReportsToolkit.h"
+
+@class DBMenuTableViewController;
+
+/**
+ A protocol used to communicate between `DBMenuTableViewController` object and the object that presented it.
+ */
+@protocol DBMenuTableViewControllerDelegate <NSObject>
+
+/**
+ Informs the delegate that the user tapped `Close` button in the `DBMenuTableViewController`.
+ 
+ @param menuTableViewController `DBMenuTableViewController` object that should be dismissed.
+ */
+- (void)menuTableViewControllerDidTapClose:(DBMenuTableViewController *)menuTableViewController;
+
+@end
+
+/**
+ `DBMenuTableViewController` is the main view controller used by `DBDebugToolkit`. It contains a `UITableView` containing all the debug tools.
+ */
+@interface DBMenuTableViewController : UITableViewController
+
+/**
+ The delegate adopting `DBMenuTableViewControllerDelegate` protocol.
+ */
+@property (nonatomic, weak) id <DBMenuTableViewControllerDelegate> delegate;
+
+/**
+ `DBPerformanceToolkit` instance that will be passed on.
+ */
+@property (nonatomic, strong) DBPerformanceToolkit *performanceToolkit;
+
+/**
+ `DBConsoleOutputCaptor` instance that will be passed on.
+ */
+@property (nonatomic, strong) DBConsoleOutputCaptor *consoleOutputCaptor;
+
+/**
+ `DBNetworkToolkit` instance that will be passed on.
+ */
+@property (nonatomic, strong) DBNetworkToolkit *networkToolkit;
+
+/**
+ `DBUserInterfaceToolkit` instance that will be passed on.
+ */
+@property (nonatomic, strong) DBUserInterfaceToolkit *userInterfaceToolkit;
+
+/**
+ `DBLocationToolkit` instance that will be passed on.
+ */
+@property (nonatomic, strong) DBLocationToolkit *locationToolkit;
+
+/**
+ `DBCoreDataToolkit` instance that will be passed on.
+ */
+@property (nonatomic, strong) DBCoreDataToolkit *coreDataToolkit;
+
+/**
+ `DBBuildInfoProvider` instance that will provide data for section header.
+ */
+@property (nonatomic, strong) DBBuildInfoProvider *buildInfoProvider;
+
+/**
+ `DBDeviceInfoProvider` instance that will provide data for section footer.
+ */
+@property (nonatomic, strong) DBDeviceInfoProvider *deviceInfoProvider;
+
+/**
+ `DBCrashReportsToolkit` instance that will be passed on.
+ */
+@property (nonatomic, strong) DBCrashReportsToolkit *crashReportsToolkit;
+
+/**
+ Array of `DBCustomAction` instances that will be passed on.
+ */
+@property (nonatomic, copy) NSArray <DBCustomAction *> *customActions;
+
+/**
+ Array of `DBCustomVariable` instances that will be passed on.
+ */
+@property (nonatomic, copy) NSArray <DBCustomVariable *> *customVariables;
+
+/**
+ Pushes `DBPerformanceTableViewController` onto the current navigation controller with the preselected section. Optionally animated.
+ 
+ @param section Preselected section in performance table view controller.
+ @param animated Boolean determining whether the push should be animated or not.
+ */
+- (void)openPerformanceMenuWithSection:(DBPerformanceSection)section animated:(BOOL)animated;
+
+@end

+ 134 - 0
Pods/DBDebugToolkit/DBDebugToolkit/Classes/Menu/DBMenuTableViewController.m

@@ -0,0 +1,134 @@
+// The MIT License
+//
+// Copyright (c) 2016 Dariusz Bukowski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#import "DBMenuTableViewController.h"
+#import "DBPerformanceTableViewController.h"
+#import "NSBundle+DBDebugToolkit.h"
+#import "DBConsoleViewController.h"
+#import "DBNetworkViewController.h"
+#import "DBUserInterfaceTableViewController.h"
+#import "DBLocationTableViewController.h"
+#import "DBResourcesTableViewController.h"
+#import "DBCustomActionsTableViewController.h"
+#import "DBCustomVariablesTableViewController.h"
+#import "DBCrashReportsTableViewController.h"
+
+typedef NS_ENUM(NSUInteger, DBMenuTableViewControllerRow) {
+    DBMenuTableViewControllerRowPerformance,
+    DBMenuTableViewControllerRowUserInterface,
+    DBMenuTableViewControllerRowNetwork,
+    DBMenuTableViewControllerRowResources,
+    DBMenuTableViewControllerRowConsole,
+    DBMenuTableViewControllerRowLocation,
+    DBMenuTableViewControllerRowCrashReports,
+    DBMenuTableViewControllerRowCustomVariables,
+    DBMenuTableViewControllerRowCustomActions,
+    DBMenuTableViewControllerRowApplicationSettings
+};
+
+@interface DBMenuTableViewController () <DBUserInterfaceTableViewControllerDelegate>
+
+@end
+
+@implementation DBMenuTableViewController
+
+#pragma mark - Close button
+
+- (IBAction)closeButtonAction:(id)sender {
+    [self.delegate menuTableViewControllerDidTapClose:self];
+}
+
+#pragma mark - Opening Performance menu
+
+- (void)openPerformanceMenuWithSection:(DBPerformanceSection)section animated:(BOOL)animated {
+    NSBundle *bundle = [NSBundle debugToolkitBundle];
+    UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"DBPerformanceTableViewController" bundle:bundle];
+    DBPerformanceTableViewController *performanceTableViewController = [storyboard instantiateInitialViewController];
+    performanceTableViewController.performanceToolkit = self.performanceToolkit;
+    performanceTableViewController.selectedSection = section;
+    [self.navigationController setViewControllers:@[ self, performanceTableViewController ] animated:animated];
+}
+
+#pragma mark - UITableViewDelegate
+
+- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
+    if (indexPath.row == DBMenuTableViewControllerRowApplicationSettings) {
+        // Open application settings.
+        [[UIApplication sharedApplication] openURL:[NSURL URLWithString:UIApplicationOpenSettingsURLString]];
+        [tableView deselectRowAtIndexPath:indexPath animated:true];
+    }
+}
+
+#pragma mark - UITableViewDataSource
+
+- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
+    return [self.buildInfoProvider buildInfoString];
+}
+
+- (NSString *)tableView:(UITableView *)tableView titleForFooterInSection:(NSInteger)section {
+    return [self.deviceInfoProvider deviceInfoString];
+}
+
+#pragma mark - Navigation
+
+- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
+    UIViewController *destinationViewController = [segue destinationViewController];
+    if ([destinationViewController isKindOfClass:[DBPerformanceTableViewController class]]) {
+        DBPerformanceTableViewController *performanceTableViewController = (DBPerformanceTableViewController *)destinationViewController;
+        performanceTableViewController.performanceToolkit = self.performanceToolkit;
+    } else if ([destinationViewController isKindOfClass:[DBConsoleViewController class]]) {
+        DBConsoleViewController *consoleViewController = (DBConsoleViewController *)destinationViewController;
+        consoleViewController.consoleOutputCaptor = self.consoleOutputCaptor;
+        consoleViewController.buildInfoProvider = self.buildInfoProvider;
+        consoleViewController.deviceInfoProvider = self.deviceInfoProvider;
+    } else if ([destinationViewController isKindOfClass:[DBNetworkViewController class]]) {
+        DBNetworkViewController *networkViewController = (DBNetworkViewController *)destinationViewController;
+        networkViewController.networkToolkit = self.networkToolkit;
+    } else if ([destinationViewController isKindOfClass:[DBUserInterfaceTableViewController class]]) {
+        DBUserInterfaceTableViewController *userInterfaceTableViewController = (DBUserInterfaceTableViewController *)destinationViewController;
+        userInterfaceTableViewController.userInterfaceToolkit = self.userInterfaceToolkit;
+        userInterfaceTableViewController.delegate = self;
+    } else if ([destinationViewController isKindOfClass:[DBLocationTableViewController class]]) {
+        DBLocationTableViewController *locationTableViewController = (DBLocationTableViewController *)destinationViewController;
+        locationTableViewController.locationToolkit = self.locationToolkit;
+    } else if ([destinationViewController isKindOfClass:[DBResourcesTableViewController class]]) {
+        DBResourcesTableViewController *resourcesTableViewController = (DBResourcesTableViewController *)destinationViewController;
+        resourcesTableViewController.coreDataToolkit = self.coreDataToolkit;
+    } else if ([destinationViewController isKindOfClass:[DBCustomVariablesTableViewController class]]) {
+        DBCustomVariablesTableViewController *customVariablesTableViewController = (DBCustomVariablesTableViewController *)destinationViewController;
+        customVariablesTableViewController.customVariables = self.customVariables;
+    } else if ([destinationViewController isKindOfClass:[DBCustomActionsTableViewController class]]) {
+        DBCustomActionsTableViewController *customActionsTableViewController = (DBCustomActionsTableViewController *)destinationViewController;
+        customActionsTableViewController.customActions = self.customActions;
+    } else if ([destinationViewController isKindOfClass:[DBCrashReportsTableViewController class]]) {
+        DBCrashReportsTableViewController *crashReportsTableViewController = (DBCrashReportsTableViewController *)destinationViewController;
+        crashReportsTableViewController.crashReportsToolkit = self.crashReportsToolkit;
+    }
+}
+
+#pragma mark - DBUserInterfaceTableViewControllerDelegate
+
+- (void)userInterfaceTableViewControllerDidOpenDebuggingInformationOverlay:(DBUserInterfaceTableViewController *)userInterfaceTableViewController {
+    [self.delegate menuTableViewControllerDidTapClose:self];
+}
+
+@end

+ 48 - 0
Pods/DBDebugToolkit/DBDebugToolkit/Classes/Network/DBBodyPreviewViewController.h

@@ -0,0 +1,48 @@
+// The MIT License
+//
+// Copyright (c) 2016 Dariusz Bukowski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#import <UIKit/UIKit.h>
+#import "DBRequestModel.h"
+
+/**
+ Enum used for `DBBodyPreviewViewController` instance configuration.
+ */
+typedef NS_ENUM(NSUInteger, DBBodyPreviewViewControllerMode) {
+    DBBodyPreviewViewControllerModeRequest,
+    DBBodyPreviewViewControllerModeResponse,
+};
+
+/**
+ `DBBodyPreviewViewController` is a view controller displaying a preview of the request or response body.
+ It handles image, JSON and text data.
+ */
+@interface DBBodyPreviewViewController : UIViewController
+
+/**
+ Configures the view with request and mode.
+ 
+ @param requestModel `DBRequestModel` instance containing all the information about the logged request.
+ @param mode `DBBodyPreviewViewControllerMode` value determining the view controller title and which body (request or response) should be accessed.
+ */
+- (void)configureWithRequestModel:(DBRequestModel *)requestModel mode:(DBBodyPreviewViewControllerMode)mode;
+
+@end

+ 99 - 0
Pods/DBDebugToolkit/DBDebugToolkit/Classes/Network/DBBodyPreviewViewController.m

@@ -0,0 +1,99 @@
+// The MIT License
+//
+// Copyright (c) 2016 Dariusz Bukowski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#import "DBBodyPreviewViewController.h"
+
+typedef NS_ENUM(NSUInteger, DBBodyPreviewViewControllerViewState) {
+    DBBodyPreviewViewControllerViewStateLoading,
+    DBBodyPreviewViewControllerViewStateShowingText,
+    DBBodyPreviewViewControllerViewStateShowingImage,
+};
+
+@interface DBBodyPreviewViewController ()
+
+@property (weak, nonatomic) IBOutlet UIActivityIndicatorView *activityIndicator;
+@property (weak, nonatomic) IBOutlet UITextView *textView;
+@property (weak, nonatomic) IBOutlet UIImageView *imageView;
+
+@end
+
+@implementation DBBodyPreviewViewController
+
+- (void)viewDidLoad {
+    [super viewDidLoad];
+}
+
+- (void)configureWithRequestModel:(DBRequestModel *)requestModel mode:(DBBodyPreviewViewControllerMode)mode {
+    self.title = mode == DBBodyPreviewViewControllerModeRequest ? @"Request body" : @"Response body";
+    [self setViewState:DBBodyPreviewViewControllerViewStateLoading animated:YES];
+    DBRequestModelBodyType bodyType = mode == DBBodyPreviewViewControllerModeRequest ? requestModel.requestBodyType : requestModel.responseBodyType;
+    void (^completion)(NSData *) = ^void(NSData *data) {
+        if (bodyType == DBRequestModelBodyTypeImage) {
+            self.imageView.image = [UIImage imageWithData:data];
+            [self setViewState:DBBodyPreviewViewControllerViewStateShowingImage animated:YES];
+        } else {
+            NSString *dataString;
+            if (bodyType == DBRequestModelBodyTypeJSON) {
+                NSJSONSerialization *jsonSerialization = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments error:nil];
+                data = [NSJSONSerialization dataWithJSONObject:jsonSerialization options:NSJSONWritingPrettyPrinted error:nil];
+                dataString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
+                dataString = [dataString stringByReplacingOccurrencesOfString:@"\\/" withString:@"/"];
+            } else {
+                NSString *UTF8DecodedString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
+                dataString = UTF8DecodedString ?: [[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding];
+            }
+            self.textView.text = dataString;
+            [self setViewState:DBBodyPreviewViewControllerViewStateShowingText animated:YES];
+        }
+    };
+    if (mode == DBBodyPreviewViewControllerModeRequest) {
+        [requestModel readRequestBodyWithCompletion:completion];
+    } else {
+        [requestModel readResponseBodyWithCompletion:completion];
+    }
+}
+
+- (void)setViewState:(DBBodyPreviewViewControllerViewState)state animated:(BOOL)animated {
+    [UIView animateWithDuration:animated ? 0.35 : 0.0 animations:^{
+        self.activityIndicator.alpha = 0.0;
+        self.textView.alpha = 0.0;
+        self.imageView.alpha = 0.0;
+        switch (state) {
+            case DBBodyPreviewViewControllerViewStateLoading: {
+                [self.activityIndicator startAnimating];
+                self.activityIndicator.alpha = 1.0;
+                break;
+            }
+            case DBBodyPreviewViewControllerViewStateShowingText:
+                self.textView.alpha = 1.0;
+                break;
+            case DBBodyPreviewViewControllerViewStateShowingImage:
+                self.imageView.alpha = 1.0;
+        }
+    } completion:^(BOOL finished) {
+        if (state != DBBodyPreviewViewControllerViewStateLoading) {
+            [self.activityIndicator stopAnimating];
+        }
+    }];
+}
+
+@end

+ 36 - 0
Pods/DBDebugToolkit/DBDebugToolkit/Classes/Network/DBNetworkSettingsTableViewController.h

@@ -0,0 +1,36 @@
+// The MIT License
+//
+// Copyright (c) 2016 Dariusz Bukowski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#import <UIKit/UIKit.h>
+#import "DBNetworkToolkit.h"
+
+/**
+ `DBNetworkSettingsTableViewController` is a simple view controller displaying a switch controlling the requests logging.
+ */
+@interface DBNetworkSettingsTableViewController : UITableViewController
+
+/**
+ `DBNetworkToolkit` object, which is is configured by the settings presented on the view controller.
+ */
+@property (nonatomic, strong) DBNetworkToolkit *networkToolkit;
+
+@end

+ 71 - 0
Pods/DBDebugToolkit/DBDebugToolkit/Classes/Network/DBNetworkSettingsTableViewController.m

@@ -0,0 +1,71 @@
+// The MIT License
+//
+// Copyright (c) 2016 Dariusz Bukowski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#import "DBNetworkSettingsTableViewController.h"
+#import "DBMenuSwitchTableViewCell.h"
+#import "NSBundle+DBDebugToolkit.h"
+
+static NSString *const DBNetworkSettingsTableViewControllerSwitchCellIdentifier = @"DBMenuSwitchTableViewCell";
+
+@interface DBNetworkSettingsTableViewController () <DBMenuSwitchTableViewCellDelegate>
+
+@end
+
+@implementation DBNetworkSettingsTableViewController
+
+- (void)viewDidLoad {
+    [super viewDidLoad];
+    NSBundle *bundle = [NSBundle debugToolkitBundle];
+    [self.tableView registerNib:[UINib nibWithNibName:@"DBMenuSwitchTableViewCell" bundle:bundle]
+         forCellReuseIdentifier:DBNetworkSettingsTableViewControllerSwitchCellIdentifier];
+}
+
+
+#pragma mark - Table view data source
+
+- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
+    return 1;
+}
+
+- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
+    return 1;
+}
+
+- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
+    DBMenuSwitchTableViewCell *switchTableViewCell = [tableView dequeueReusableCellWithIdentifier:DBNetworkSettingsTableViewControllerSwitchCellIdentifier];
+    switchTableViewCell.titleLabel.text = @"Logging enabled";
+    switchTableViewCell.valueSwitch.on = self.networkToolkit.loggingEnabled;
+    switchTableViewCell.delegate = self;
+    return switchTableViewCell;
+}
+
+- (NSString *)tableView:(UITableView *)tableView titleForFooterInSection:(NSInteger)section {
+    return @"Logging requests may affect the memory usage.";
+}
+
+#pragma mark - DBMenuSwitchTableViewCellDelegate
+
+- (void)menuSwitchTableViewCell:(DBMenuSwitchTableViewCell *)menuSwitchTableViewCell didSetOn:(BOOL)isOn {
+    self.networkToolkit.loggingEnabled = isOn;
+}
+
+@end

+ 103 - 0
Pods/DBDebugToolkit/DBDebugToolkit/Classes/Network/DBNetworkToolkit.h

@@ -0,0 +1,103 @@
+// The MIT License
+//
+// Copyright (c) 2016 Dariusz Bukowski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#import <Foundation/Foundation.h>
+#import "DBRequestOutcome.h"
+
+@class DBNetworkToolkit;
+
+/**
+ A protocol used for informing about changes in logging settings or in logged requests list.
+ */
+@protocol DBNetworkToolkitDelegate <NSObject>
+
+/**
+ Informs the delegate that the logged requests list has changed.
+ 
+ @param networkToolkit The `DBNetworkToolkit` instance that changed the logged requests list.
+ */
+- (void)networkDebugToolkitDidUpdateRequestsList:(DBNetworkToolkit *)networkToolkit;
+
+/**
+ Informs the delegate that the object at the given index on the logged requests list has changed.
+ 
+ @param networkToolkit The `DBNetworkToolkit` instance that changed the logged request.
+ @param index The index of the logged request that has changed.
+ */
+- (void)networkDebugToolkit:(DBNetworkToolkit *)networkToolkit didUpdateRequestAtIndex:(NSInteger)index;
+
+/**
+ Informs the delegate that the logging setting have changed.
+ 
+ @param networkToolkit The `DBNetworkToolkit` instance that changed the logging settings.
+ @param enabled The new value of the flag determining whether the requests logging is enabled or not.
+ */
+- (void)networkDebugToolkit:(DBNetworkToolkit *)networkToolkit didSetEnabled:(BOOL)enabled;
+
+@end
+
+/**
+ `DBNetworkToolkit` is a class responsible for logging all the requests sent by the application.
+ */
+@interface DBNetworkToolkit : NSObject
+
+/**
+ Returns the singleton instance.
+ */
++ (instancetype)sharedInstance;
+
+/**
+ Returns a string containing the path to the directory where the requests data is stored.
+ */
+- (NSString *)savedRequestsPath;
+
+/**
+ Saves the given request.
+ 
+ @param request The request that should be saved by `DBNetworkToolkit` instance.
+ */
+- (void)saveRequest:(NSURLRequest *)request;
+
+/**
+ Saves the response for a given request.
+ 
+ @param requestOutcome The `DBRequestOutcome` instance containing all the information about the request outcome.
+ @param request The request that finished with the given outcome.
+ */
+- (void)saveRequestOutcome:(DBRequestOutcome *)requestOutcome forRequest:(NSURLRequest *)request;
+
+/**
+ Boolean variable determining whether the requests logging is enabled or not. It is true by default.
+ */
+@property (nonatomic, assign) BOOL loggingEnabled;
+
+/**
+ Delegate that will be informed about changes in the logged requests list or in the enabled flag value. It needs to conform to `DBNetworkToolkitDelegate` protocol.
+ */
+@property (nonatomic, weak) id <DBNetworkToolkitDelegate> delegate;
+
+/**
+ An array containing all the logged requests.
+ */
+@property (nonatomic, readonly) NSArray *savedRequests;
+
+@end

+ 142 - 0
Pods/DBDebugToolkit/DBDebugToolkit/Classes/Network/DBNetworkToolkit.m

@@ -0,0 +1,142 @@
+// The MIT License
+//
+// Copyright (c) 2016 Dariusz Bukowski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#import "DBNetworkToolkit.h"
+#import "DBURLProtocol.h"
+#import "DBRequestModel.h"
+
+@interface DBNetworkToolkit () <DBRequestModelDelegate>
+
+@property (nonatomic, copy) NSMutableArray *requests;
+@property (nonatomic, strong) NSMapTable *runningRequestsModels;
+@property (nonatomic, strong) NSOperationQueue *operationQueue;
+
+@end
+
+@implementation DBNetworkToolkit
+
+#pragma mark - Initialization
+
+- (instancetype)init {
+    self = [super init];
+    if (self) {
+        [self setupOperationQueue];
+        [self resetLoggedData];
+    }
+    
+    return self;
+}
+
++ (instancetype)sharedInstance {
+    static DBNetworkToolkit *sharedInstance = nil;
+    static dispatch_once_t onceToken;
+    dispatch_once(&onceToken, ^{
+        sharedInstance = [[DBNetworkToolkit alloc] init];
+        [self setupURLProtocol];
+    });
+    return sharedInstance;
+}
+
++ (void)setupURLProtocol {
+    [NSURLProtocol registerClass:[DBURLProtocol class]];
+}
+
+- (void)setupOperationQueue {
+    self.operationQueue = [NSOperationQueue new];
+    self.operationQueue.maxConcurrentOperationCount = 1;
+}
+
+#pragma mark - Logging settings 
+
+- (void)setLoggingEnabled:(BOOL)loggingEnabled {
+    _loggingEnabled = loggingEnabled;
+    if (!loggingEnabled) {
+        [self resetLoggedData];
+        [self.delegate networkDebugToolkitDidUpdateRequestsList:self];
+    }
+    [self.delegate networkDebugToolkit:self didSetEnabled:loggingEnabled];
+}
+
+- (void)resetLoggedData {
+    [self.operationQueue addOperationWithBlock:^{
+        _requests = [NSMutableArray array];
+        _runningRequestsModels = [NSMapTable strongToWeakObjectsMapTable];
+        [self removeOldSavedRequests];
+    }];
+}
+
+#pragma mark - Saving request data
+
+- (void)removeOldSavedRequests {
+    NSFileManager *fileManager = [NSFileManager defaultManager];
+    NSString *directory = [self savedRequestsPath];
+    NSError *error = nil;
+    for (NSString *file in [fileManager contentsOfDirectoryAtPath:directory error:&error]) {
+        NSString *filePath = [directory stringByAppendingPathComponent:file];
+        [fileManager removeItemAtPath:filePath error:&error];
+    }
+}
+
+- (NSString *)savedRequestsPath {
+    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
+    NSString *documentsDirectory = [paths objectAtIndex:0];
+    return [documentsDirectory stringByAppendingPathComponent:@"DBDebugToolkit/Network"];
+}
+
+- (void)saveRequest:(NSURLRequest *)request {
+    [self.operationQueue addOperationWithBlock:^{
+        DBRequestModel *requestModel = [DBRequestModel requestModelWithRequest:request];
+        requestModel.delegate = self;
+        [self.runningRequestsModels setObject:requestModel forKey:request.description];
+        [self.requests addObject:requestModel];
+        [self.delegate networkDebugToolkitDidUpdateRequestsList:self];
+    }];
+}
+
+- (void)saveRequestOutcome:(DBRequestOutcome *)requestOutcome forRequest:(NSURLRequest *)request {
+    [self.operationQueue addOperationWithBlock:^{
+        DBRequestModel *requestModel = [self.runningRequestsModels objectForKey:request.description];
+        [requestModel saveOutcome:requestOutcome];
+        [requestModel saveBodyWithData:request.HTTPBody inputStream:request.HTTPBodyStream];
+        [self.runningRequestsModels removeObjectForKey:request.description];
+        [self didUpdateRequestModel:requestModel];
+    }];
+}
+
+- (NSArray *)savedRequests {
+    return self.requests;
+}
+
+#pragma mark - DBRequestModelDelegate
+
+- (void)requestModelDidFinishSynchronization:(DBRequestModel *)requestModel {
+    [self didUpdateRequestModel:requestModel];
+}
+
+- (void)didUpdateRequestModel:(DBRequestModel *)requestModel {
+    [self.operationQueue addOperationWithBlock:^{
+        NSInteger requestIndex = [self.requests indexOfObject:requestModel];
+        [self.delegate networkDebugToolkit:self didUpdateRequestAtIndex:requestIndex];
+    }];
+}
+
+@end

+ 37 - 0
Pods/DBDebugToolkit/DBDebugToolkit/Classes/Network/DBNetworkViewController.h

@@ -0,0 +1,37 @@
+// The MIT License
+//
+// Copyright (c) 2016 Dariusz Bukowski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#import <UIKit/UIKit.h>
+#import "DBNetworkToolkit.h"
+
+/**
+ `DBNetworkViewController` is a view controller displaying the list of logged requests with a search bar.
+ It has one button on the navigation bar, that opens a `DBNetworkSettingsTableViewController`.
+ */
+@interface DBNetworkViewController : UIViewController
+
+/**
+ `DBNetworkToolkit` object providing the list of logged requests and informing about any changes.
+ */
+@property (nonatomic, strong) DBNetworkToolkit *networkToolkit;
+
+@end

+ 231 - 0
Pods/DBDebugToolkit/DBDebugToolkit/Classes/Network/DBNetworkViewController.m

@@ -0,0 +1,231 @@
+// The MIT License
+//
+// Copyright (c) 2016 Dariusz Bukowski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#import "DBNetworkViewController.h"
+#import "DBRequestTableViewCell.h"
+#import "NSBundle+DBDebugToolkit.h"
+#import "DBNetworkSettingsTableViewController.h"
+#import "DBRequestDetailsViewController.h"
+#import "NSOperationQueue+DBMainQueueOperation.h"
+
+static NSString *const DBNetworkViewControllerRequestCellIdentifier = @"DBRequestTableViewCell";
+
+@interface DBNetworkViewController () <UITableViewDataSource, UITableViewDelegate, UISearchBarDelegate, DBNetworkToolkitDelegate, DBRequestDetailsViewControllerDelegate>
+
+@property (nonatomic, weak) IBOutlet UISearchBar *searchBar;
+@property (nonatomic, weak) IBOutlet UITableView *tableView;
+@property (nonatomic, weak) IBOutlet UILabel *loggingRequestsDisabledLabel;
+@property (nonatomic, strong) NSArray *filteredRequests;
+@property (nonatomic, strong) DBRequestDetailsViewController *requestDetailsViewController;
+@property (nonatomic, strong) DBRequestModel *openedRequest;
+@property (nonatomic, strong) NSOperationQueue *operationQueue;
+
+@end
+
+@implementation DBNetworkViewController
+
+- (void)viewDidLoad {
+    [super viewDidLoad];
+    self.searchBar.delegate = self;
+    self.networkToolkit.delegate = self;
+    self.filteredRequests = self.networkToolkit.savedRequests;
+    self.tableView.delegate = self;
+    self.tableView.dataSource = self;
+    NSBundle *bundle = [NSBundle debugToolkitBundle];
+    [self.tableView registerNib:[UINib nibWithNibName:@"DBRequestTableViewCell" bundle:bundle]
+         forCellReuseIdentifier:DBNetworkViewControllerRequestCellIdentifier];
+    self.tableView.tableFooterView = [UIView new];
+    [self configureViewWithLoggingRequestsEnabled:self.networkToolkit.loggingEnabled];
+    self.operationQueue = [NSOperationQueue new];
+    self.operationQueue.maxConcurrentOperationCount = 1;
+}
+
+- (void)viewDidAppear:(BOOL)animated {
+    [super viewDidAppear:animated];
+    [[NSNotificationCenter defaultCenter] addObserver:self
+                                             selector:@selector(keyboardWillShow:)
+                                                 name:UIKeyboardWillShowNotification
+                                               object:nil];
+    [[NSNotificationCenter defaultCenter] addObserver:self
+                                             selector:@selector(keyboardWillHide:)
+                                                 name:UIKeyboardWillHideNotification
+                                               object:nil];
+}
+
+- (void)viewDidDisappear:(BOOL)animated {
+    [super viewDidDisappear:animated];
+    [[NSNotificationCenter defaultCenter] removeObserver:self
+                                                    name:UIKeyboardWillShowNotification
+                                                  object:nil];
+    [[NSNotificationCenter defaultCenter] removeObserver:self
+                                                    name:UIKeyboardWillHideNotification
+                                                  object:nil];
+}
+
+#pragma mark - Configuring View
+
+- (void)updateRequests {
+    NSString *searchBarText = self.searchBar.text;
+    if (searchBarText.length > 0) {
+        NSPredicate *predicate = [NSPredicate predicateWithFormat:@"(SELF.url.relativePath contains[cd] %@) OR (SELF.url.host contains[cd] %@)", searchBarText, searchBarText];
+        self.filteredRequests = [self.networkToolkit.savedRequests filteredArrayUsingPredicate:predicate];
+    } else {
+        self.filteredRequests = self.networkToolkit.savedRequests;
+    }
+}
+
+- (void)reloadData {
+    __weak DBNetworkViewController *weakSelf = self;
+    [self.operationQueue addMainQueueOperationWithBlock:^{
+        __strong DBNetworkViewController *strongSelf = weakSelf;
+        [strongSelf updateRequests];
+        [strongSelf.tableView reloadData];
+    }];
+}
+
+- (void)configureViewWithLoggingRequestsEnabled:(BOOL)enabled {
+    self.tableView.alpha = enabled ? 1.0 : 0.0;
+    self.searchBar.alpha = enabled ? 1.0 : 0.0;
+    self.loggingRequestsDisabledLabel.alpha = enabled ? 0.0 : 1.0;
+}
+
+#pragma mark - Navigation
+
+- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
+    if ([segue.destinationViewController isKindOfClass:[DBNetworkSettingsTableViewController class]]) {
+        DBNetworkSettingsTableViewController *settingsViewController = (DBNetworkSettingsTableViewController *)segue.destinationViewController;
+        settingsViewController.networkToolkit = self.networkToolkit;
+    }
+}
+
+#pragma mark - Keyboard notifications
+
+- (void)keyboardWillShow:(NSNotification *)notification {
+    NSDictionary *userInfo = [notification userInfo];
+    CGSize keyboardSize = [[userInfo objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].size;
+    UIEdgeInsets newContentInsets = UIEdgeInsetsMake(0, 0, keyboardSize.height, 0);
+    self.tableView.contentInset = newContentInsets;
+    self.tableView.scrollIndicatorInsets = newContentInsets;
+}
+
+- (void)keyboardWillHide:(NSNotification *)notification {
+    self.tableView.contentInset = UIEdgeInsetsZero;
+    self.tableView.scrollIndicatorInsets = UIEdgeInsetsZero;
+}
+
+#pragma mark - UITableViewDataSource
+
+- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
+    return 1;
+}
+
+- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
+    return self.filteredRequests.count;
+}
+
+- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
+    DBRequestTableViewCell *requestCell = [tableView dequeueReusableCellWithIdentifier:DBNetworkViewControllerRequestCellIdentifier];
+    [requestCell configureWithRequestModel:self.filteredRequests[self.filteredRequests.count - 1 - indexPath.row]];
+    return requestCell;
+}
+
+#pragma mark - UITableViewDelegate
+
+- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
+    return 60.0;
+}
+
+- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
+    if (!self.openedRequest) {
+        DBRequestModel *requestModel = self.filteredRequests[self.filteredRequests.count - 1 - indexPath.row];
+        self.openedRequest = requestModel;
+        [self.requestDetailsViewController configureWithRequestModel:requestModel];
+        [self.navigationController pushViewController:self.requestDetailsViewController animated:YES];
+    }
+}
+
+- (DBRequestDetailsViewController *)requestDetailsViewController {
+    if (!_requestDetailsViewController) {
+        NSBundle *bundle = [NSBundle debugToolkitBundle];
+        UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"DBRequestDetailsViewController" bundle:bundle];
+        _requestDetailsViewController = [storyboard instantiateInitialViewController];
+        _requestDetailsViewController.delegate = self;
+    }
+    return _requestDetailsViewController;
+}
+
+#pragma mark - UISearchBarDelegate
+
+- (void)searchBarTextDidBeginEditing:(UISearchBar *)searchBar {
+    [searchBar setShowsCancelButton:YES animated:YES];
+}
+
+- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText {
+    [self reloadData];
+}
+
+- (void)searchBarCancelButtonClicked:(UISearchBar *)searchBar {
+    if (searchBar.text.length > 0) {
+        [searchBar setText:@""];
+        [self reloadData];
+    }
+    [searchBar setShowsCancelButton:NO animated:YES];
+    [searchBar resignFirstResponder];
+}
+
+#pragma mark - DBNetworkToolkitDelegate
+
+- (void)networkDebugToolkitDidUpdateRequestsList:(DBNetworkToolkit *)networkToolkit {
+    [self reloadData];
+}
+
+- (void)networkDebugToolkit:(DBNetworkToolkit *)networkToolkit didUpdateRequestAtIndex:(NSInteger)index {
+    __weak DBNetworkViewController *weakSelf = self;
+    [self.operationQueue addMainQueueOperationWithBlock:^{
+        __strong DBNetworkViewController *strongSelf = weakSelf;
+        DBRequestModel *requestModel = strongSelf.networkToolkit.savedRequests[index];
+        if (requestModel == strongSelf.openedRequest) {
+            [strongSelf.requestDetailsViewController configureWithRequestModel:requestModel];
+        }
+        [strongSelf updateRequests];
+        NSInteger updatedRequestIndex = [strongSelf.filteredRequests indexOfObject:requestModel];
+        if (updatedRequestIndex != NSNotFound) {
+            NSIndexPath *indexPath = [NSIndexPath indexPathForRow:strongSelf.filteredRequests.count - 1 - updatedRequestIndex inSection:0];
+            DBRequestTableViewCell *requestCell = [strongSelf.tableView cellForRowAtIndexPath:indexPath];
+            DBRequestModel *requestModel = strongSelf.filteredRequests[strongSelf.filteredRequests.count - 1 - indexPath.row];
+            [requestCell configureWithRequestModel:requestModel];
+        }
+    }];
+}
+
+- (void)networkDebugToolkit:(DBNetworkToolkit *)networkToolkit didSetEnabled:(BOOL)enabled {
+    [self configureViewWithLoggingRequestsEnabled:enabled];
+}
+
+#pragma mark - DBRequestDetailsViewControllerDelegate
+
+- (void)requestDetailsViewControllerDidDismiss:(DBRequestDetailsViewController *)requestDetailsViewController {
+    self.requestDetailsViewController = nil;
+    self.openedRequest = nil;
+}
+
+@end

+ 59 - 0
Pods/DBDebugToolkit/DBDebugToolkit/Classes/Network/DBRequestDetailsViewController.h

@@ -0,0 +1,59 @@
+// The MIT License
+//
+// Copyright (c) 2016 Dariusz Bukowski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#import <UIKit/UIKit.h>
+#import "DBRequestModel.h"
+
+@class DBRequestDetailsViewController;
+
+/**
+ A protocol used for informing the presenting view controller about the dismissing of the `DBRequestDetailsViewController` instance.
+ */
+@protocol DBRequestDetailsViewControllerDelegate <NSObject>
+
+/**
+ Informs the delegate that it will be dismissed.
+ 
+ @param requestDetailsViewController The `DBRequestDetailsViewController` that is being dismissed.
+ */
+- (void)requestDetailsViewControllerDidDismiss:(DBRequestDetailsViewController *)requestDetailsViewController;
+
+@end
+
+/**
+ `DBRequestDetailsViewController` is a view controller displaying all the information about a given request sent by the application.
+ */
+@interface DBRequestDetailsViewController : UIViewController
+
+/**
+ Delegate that will be informed about the dismissing of the view controller. It needs to conform to `DBRequestDetailsViewControllerDelegate` protocol.
+ */
+@property (nonatomic, weak) id <DBRequestDetailsViewControllerDelegate> delegate;
+
+/**
+ Configures the view controller with a `DBRequestModel` instance.
+ 
+ @param requestModel `DBRequestModel` instance containing all the information about the logged request.
+ */
+- (void)configureWithRequestModel:(DBRequestModel *)requestModel;
+
+@end

+ 411 - 0
Pods/DBDebugToolkit/DBDebugToolkit/Classes/Network/DBRequestDetailsViewController.m

@@ -0,0 +1,411 @@
+// The MIT License
+//
+// Copyright (c) 2016 Dariusz Bukowski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#import "DBRequestDetailsViewController.h"
+#import "NSBundle+DBDebugToolkit.h"
+#import "DBMenuSegmentedControlTableViewCell.h"
+#import "DBTitleValueTableViewCell.h"
+#import "DBBodyPreviewViewController.h"
+
+typedef NS_ENUM(NSInteger, DBRequestDetailsViewControllerTab) {
+    DBRequestDetailsViewControllerTabRequest,
+    DBRequestDetailsViewControllerTabResponse,
+    DBRequestDetailsViewControllerTabError
+};
+
+static NSString *const DBRequestDetailsViewControllerSegmentedControlCellIdentifier = @"DBMenuSegmentedControlTableViewCell";
+static NSString *const DBRequestDetailsViewControllerTitleValueCellIdentifier = @"DBTitleValueTableViewCell";
+static NSString *const DBRequestDetailsViewControllerPrototypeSimpleCellIdentifier = @"OpenBodyCell";
+
+@interface DBRequestDetailsViewController () <UITableViewDelegate, UITableViewDataSource, DBMenuSegmentedControlTableViewCellDelegate>
+
+@property (nonatomic, weak) IBOutlet UITableView *tableView;
+@property (nonatomic, strong) DBRequestModel *requestModel;
+@property (nonatomic, assign) DBRequestDetailsViewControllerTab selectedTab;
+@property (nonatomic, strong) NSArray *requestDetailsDataSources;
+@property (nonatomic, strong) NSArray *requestHeaderFieldsDataSources;
+@property (nonatomic, strong) NSArray *responseDetailsDataSources;
+@property (nonatomic, strong) NSArray *responseHeaderFieldsDataSources;
+@property (nonatomic, strong) NSArray *errorDetailsDataSources;
+
+@end
+
+@implementation DBRequestDetailsViewController
+
+- (void)viewDidLoad {
+    [super viewDidLoad];
+    NSBundle *bundle = [NSBundle debugToolkitBundle];
+    [self.tableView registerNib:[UINib nibWithNibName:@"DBMenuSegmentedControlTableViewCell" bundle:bundle]
+         forCellReuseIdentifier:DBRequestDetailsViewControllerSegmentedControlCellIdentifier];
+    [self.tableView registerNib:[UINib nibWithNibName:@"DBTitleValueTableViewCell" bundle:bundle]
+         forCellReuseIdentifier:DBRequestDetailsViewControllerTitleValueCellIdentifier];
+    self.tableView.delegate = self;
+    self.tableView.dataSource = self;
+    self.tableView.rowHeight = UITableViewAutomaticDimension;
+    self.tableView.estimatedRowHeight = 44.0;
+}
+
+- (void)viewDidDisappear:(BOOL)animated {
+    [super viewDidDisappear:animated];
+    if (self.isMovingFromParentViewController) {
+        [self.delegate requestDetailsViewControllerDidDismiss:self];
+    }
+}
+
+- (void)configureWithRequestModel:(DBRequestModel *)requestModel {
+    self.requestModel = requestModel;
+    [self createDataSources];
+    [self.tableView reloadData];
+}
+
+#pragma mark - Opening body preview
+
+- (void)openBodyPreview {
+    NSBundle *bundle = [NSBundle debugToolkitBundle];
+    UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"DBBodyPreviewViewController" bundle:bundle];
+    DBBodyPreviewViewController *bodyPreviewViewController = [storyboard instantiateInitialViewController];
+    DBBodyPreviewViewControllerMode mode = self.selectedTab == DBRequestDetailsViewControllerTabRequest ? DBBodyPreviewViewControllerModeRequest : DBBodyPreviewViewControllerModeResponse;
+    [bodyPreviewViewController configureWithRequestModel:self.requestModel mode:mode];
+    [self.navigationController pushViewController:bodyPreviewViewController animated:true];
+}
+
+#pragma mark - UITableViewDelegate
+
+- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
+    if (indexPath.section == 3 && indexPath.row == 1) {
+        [self openBodyPreview];
+        [tableView deselectRowAtIndexPath:indexPath animated:YES];
+    }
+}
+
+- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
+    switch (indexPath.section) {
+        case 0:
+            return 44.0;
+        case 3:
+            return indexPath.row == 0 ? UITableViewAutomaticDimension : 44.0;
+        default:
+            return UITableViewAutomaticDimension;
+    }
+}
+
+- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section {
+    return [self heightForFooterAndHeaderInSection:section];
+}
+
+- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section {
+    return [self heightForFooterAndHeaderInSection:section];
+}
+
+#pragma mark - UITableViewDataSource
+
+- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
+    switch (self.selectedTab) {
+        case DBRequestDetailsViewControllerTabRequest:
+            return self.requestModel.requestBodySynchronizationStatus == DBRequestModelBodySynchronizationStatusFinished ? 4 : 3;
+        case DBRequestDetailsViewControllerTabResponse:
+            return self.requestModel.responseBodySynchronizationStatus == DBRequestModelBodySynchronizationStatusFinished ? 4 : 3;
+        case DBRequestDetailsViewControllerTabError:
+            return 2;
+    }
+}
+
+- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
+    switch (section) {
+        case 0:
+            return 1;
+        case 1:
+            return [self numberOfRowsInDetailsSection];
+        case 2:
+            return [self numberOfRowsInHeaderFieldsSection];
+        case 3:
+            return [self numberOfRowsInBodySection];
+    }
+    
+    return 0;
+}
+
+- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
+    switch (indexPath.section) {
+        case 0: {
+            DBMenuSegmentedControlTableViewCell *segmentedControlCell = [tableView dequeueReusableCellWithIdentifier:DBRequestDetailsViewControllerSegmentedControlCellIdentifier];
+            [segmentedControlCell configureWithTitles:[self segmentedControlTitles] selectedIndex:[self segmentedControlSelectedIndex]];
+            segmentedControlCell.delegate = self;
+            return segmentedControlCell;
+        }
+        case 3: {
+            if (indexPath.row == 1) {
+                UITableViewCell *openBodyCell = [tableView dequeueReusableCellWithIdentifier:DBRequestDetailsViewControllerPrototypeSimpleCellIdentifier];
+                openBodyCell.textLabel.text = @"Body preview";
+                return openBodyCell;
+            }
+        }
+        default: {
+            return [self titleValueCellWithIndexPath:indexPath];
+        }
+    }
+}
+
+- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
+    switch (section) {
+        case 0:
+            return nil;
+        case 1:
+            return [self firstSectionTitle];
+        case 2:
+            return [self numberOfRowsInHeaderFieldsSection] > 0 ? @"HTTP header fields" : @"";
+        case 3:
+            return @"Body";
+    }
+    
+    return nil;
+}
+
+#pragma mark - DBMenuSegmentedControlTableViewCellDelegate
+
+- (void)menuSegmentedControlTableViewCell:(DBMenuSegmentedControlTableViewCell *)menuSegmentedControlTableViewCell didSelectSegmentAtIndex:(NSUInteger)index {
+    if (index == 0) {
+        self.selectedTab = DBRequestDetailsViewControllerTabRequest;
+    } else {
+        self.selectedTab = self.requestModel.didFinishWithError ? DBRequestDetailsViewControllerTabError : DBRequestDetailsViewControllerTabResponse;
+    }
+    [self.tableView reloadData];
+}
+
+#pragma mark - Private methods
+
+#pragma mark - - Section heights
+
+- (CGFloat)heightForFooterAndHeaderInSection:(NSInteger)section {
+    return [self tableView:self.tableView numberOfRowsInSection:section] > 0 ? UITableViewAutomaticDimension : CGFLOAT_MIN;
+}
+
+#pragma mark - - Title value cells 
+
+- (DBTitleValueTableViewCell *)titleValueCellWithIndexPath:(NSIndexPath *)indexPath {
+    DBTitleValueTableViewCell *titleValueCell = [self.tableView dequeueReusableCellWithIdentifier:DBRequestDetailsViewControllerTitleValueCellIdentifier];
+    DBTitleValueTableViewCellDataSource *dataSource = [self titleValueCellDataSourceWithIndexPath:indexPath];
+    [titleValueCell configureWithDataSource:dataSource];
+    return titleValueCell;
+}
+
+- (DBTitleValueTableViewCellDataSource *)titleValueCellDataSourceWithIndexPath:(NSIndexPath *)indexPath {
+    switch (indexPath.section) {
+        case 1:
+            return [self titleValueDetailsCellDataSourceForRow:indexPath.row];
+        case 2:
+            return [self titleValueHeaderFieldsCellDataSourceForRow:indexPath.row];
+        case 3:
+            return [self titleValueBodyCellDataSourceForRow:indexPath.row];
+    }
+    return nil;
+}
+
+- (DBTitleValueTableViewCellDataSource *)titleValueDetailsCellDataSourceForRow:(NSInteger)row {
+    switch (self.selectedTab) {
+        case DBRequestDetailsViewControllerTabRequest:
+            return self.requestDetailsDataSources[row];
+        case DBRequestDetailsViewControllerTabResponse:
+            return self.responseDetailsDataSources[row];
+        case DBRequestDetailsViewControllerTabError:
+            return self.errorDetailsDataSources[row];
+    }
+}
+
+- (DBTitleValueTableViewCellDataSource *)titleValueHeaderFieldsCellDataSourceForRow:(NSInteger)row {
+    switch (self.selectedTab) {
+        case DBRequestDetailsViewControllerTabRequest:
+            return self.requestHeaderFieldsDataSources[row];
+        case DBRequestDetailsViewControllerTabResponse:
+            return self.responseHeaderFieldsDataSources[row];
+        case DBRequestDetailsViewControllerTabError:
+            return nil;
+    }
+}
+
+- (DBTitleValueTableViewCellDataSource *)titleValueBodyCellDataSourceForRow:(NSInteger)row {
+    if (row == 0) {
+        NSInteger dataLength = self.selectedTab == DBRequestDetailsViewControllerTabResponse ? self.requestModel.responseBodyLength : self.requestModel.requestBodyLength;
+        return [DBTitleValueTableViewCellDataSource dataSourceWithTitle:@"Body length" value:[@(dataLength) stringValue]];
+    }
+    
+    return nil;
+}
+
+#pragma mark - - Data sources
+
+- (void)createDataSources {
+    if (self.requestModel.finished) {
+        if (self.requestModel.didFinishWithError) {
+            [self createErrorDetailsDataSources];
+        } else {
+            [self createResponseDetailsDataSources];
+            self.responseHeaderFieldsDataSources = [self dataSourcesWithHTTPHeaderFields:self.requestModel.allResponseHTTPHeaderFields];
+        }
+    }
+    [self createRequestDetailsDataSources];
+    self.requestHeaderFieldsDataSources = [self dataSourcesWithHTTPHeaderFields:self.requestModel.allRequestHTTPHeaderFields];
+}
+
+- (void)createRequestDetailsDataSources {
+    NSMutableArray *dataSources = [NSMutableArray array];
+    [dataSources addObject:[DBTitleValueTableViewCellDataSource dataSourceWithTitle:@"URL" value:self.requestModel.url.absoluteString]];
+    [dataSources addObject:[DBTitleValueTableViewCellDataSource dataSourceWithTitle:@"Cache policy" value:[self cachePolicyString]]];
+    [dataSources addObject:[DBTitleValueTableViewCellDataSource dataSourceWithTitle:@"Timeout interval" value:[self stringWithTimeInterval:self.requestModel.timeoutInterval]]];
+    [dataSources addObject:[DBTitleValueTableViewCellDataSource dataSourceWithTitle:@"Sending date" value:[self stringWithDate:self.requestModel.sendingDate]]];
+    if (self.requestModel.httpMethod.length > 0) {
+        [dataSources addObject:[DBTitleValueTableViewCellDataSource dataSourceWithTitle:@"HTTP method" value:self.requestModel.httpMethod]];
+    }
+    self.requestDetailsDataSources = [dataSources copy];
+}
+
+- (void)createResponseDetailsDataSources {
+    NSMutableArray *dataSources = [NSMutableArray array];
+    [dataSources addObject:[DBTitleValueTableViewCellDataSource dataSourceWithTitle:@"MIME type" value:self.requestModel.MIMEType]];
+    if (self.requestModel.textEncodingName.length > 0) {
+        [dataSources addObject:[DBTitleValueTableViewCellDataSource dataSourceWithTitle:@"Text encoding name" value:self.requestModel.textEncodingName]];
+    }
+    [dataSources addObject:[DBTitleValueTableViewCellDataSource dataSourceWithTitle:@"Receiving date" value:[self stringWithDate:self.requestModel.receivingDate]]];
+    [dataSources addObject:[DBTitleValueTableViewCellDataSource dataSourceWithTitle:@"Duration" value:[self stringWithTimeInterval:self.requestModel.duration]]];
+    if (self.requestModel.statusCode != nil) {
+        NSString *statusCodeString = [NSString stringWithFormat:@"%@ - %@", self.requestModel.statusCode, self.requestModel.localizedStatusCodeString];
+        [dataSources addObject:[DBTitleValueTableViewCellDataSource dataSourceWithTitle:@"HTTP status code" value:statusCodeString]];
+    }
+    self.responseDetailsDataSources = [dataSources copy];
+}
+
+- (void)createErrorDetailsDataSources {
+    self.errorDetailsDataSources =  @[
+        [DBTitleValueTableViewCellDataSource dataSourceWithTitle:@"Error code" value:[@(self.requestModel.errorCode) stringValue]],
+        [DBTitleValueTableViewCellDataSource dataSourceWithTitle:@"Error description" value:self.requestModel.localizedErrorDescription]
+    ];
+}
+
+- (NSArray *)dataSourcesWithHTTPHeaderFields:(NSDictionary<NSString *, NSString *> *)headerFields {
+    NSMutableArray *dataSources = [NSMutableArray array];
+    for (NSString *key in headerFields.allKeys) {
+        [dataSources addObject:[DBTitleValueTableViewCellDataSource dataSourceWithTitle:key value:headerFields[key]]];
+    }
+    return [dataSources copy];
+}
+
+#pragma mark - - Number of rows
+
+- (NSInteger)numberOfRowsInDetailsSection {
+    switch (self.selectedTab) {
+        case DBRequestDetailsViewControllerTabRequest:
+            return self.requestDetailsDataSources.count;
+        case DBRequestDetailsViewControllerTabResponse:
+            return self.responseDetailsDataSources.count;
+        case DBRequestDetailsViewControllerTabError:
+            return self.errorDetailsDataSources.count;
+    }
+}
+
+- (NSInteger)numberOfRowsInHeaderFieldsSection {
+    switch (self.selectedTab) {
+        case DBRequestDetailsViewControllerTabRequest:
+            return self.requestModel.allRequestHTTPHeaderFields.count;
+        case DBRequestDetailsViewControllerTabResponse:
+            return self.requestModel.allResponseHTTPHeaderFields.count;
+        case DBRequestDetailsViewControllerTabError:
+            return 0;
+    }
+}
+
+- (NSInteger)numberOfRowsInBodySection {
+    switch (self.selectedTab) {
+        case DBRequestDetailsViewControllerTabRequest: {
+            if (self.requestModel.requestBodySynchronizationStatus == DBRequestModelBodySynchronizationStatusFinished) {
+                return self.requestModel.requestBodyLength > 0 ? 2 : 1;
+            }
+            return 0;
+        }
+        case DBRequestDetailsViewControllerTabResponse: {
+            if (self.requestModel.responseBodySynchronizationStatus == DBRequestModelBodySynchronizationStatusFinished) {
+                return self.requestModel.responseBodyLength > 0 ? 2 : 1;
+            }
+            return 0;
+        }
+        case DBRequestDetailsViewControllerTabError:
+            return 0;
+            
+    }
+}
+
+#pragma mark - - Section title
+
+- (NSString *)firstSectionTitle {
+    switch (self.selectedTab) {
+        case DBRequestDetailsViewControllerTabRequest:
+            return @"Request";
+        case DBRequestDetailsViewControllerTabResponse:
+            return @"Response";
+        case DBRequestDetailsViewControllerTabError:
+            return @"Error";
+    }
+}
+
+#pragma mark - - Value strings
+
+- (NSString *)cachePolicyString {
+    switch (self.requestModel.cachePolicy) {
+        case NSURLRequestReloadIgnoringLocalAndRemoteCacheData:
+            return @"Reload ignoring local and remote cache data";
+        case NSURLRequestReloadIgnoringLocalCacheData:
+            return @"Reload ignoring local cache data";
+        case NSURLRequestReturnCacheDataElseLoad:
+            return @"Return cache data else load";
+        case NSURLRequestReloadRevalidatingCacheData:
+            return @"Reload revalidating cache data";
+        case NSURLRequestReturnCacheDataDontLoad:
+            return @"Return cache data, don't load";
+        case NSURLRequestUseProtocolCachePolicy:
+            return @"Use protocol cache policy";
+    }
+}
+
+- (NSString *)stringWithTimeInterval:(NSTimeInterval)timeInterval {
+    return [NSString stringWithFormat:@"%.2lfs", timeInterval];
+}
+
+- (NSString *)stringWithDate:(NSDate *)date {
+    return [NSDateFormatter localizedStringFromDate:date
+                                          dateStyle:NSDateFormatterMediumStyle
+                                          timeStyle:NSDateFormatterMediumStyle];
+}
+
+#pragma mark - - Segmented control
+
+- (NSArray *)segmentedControlTitles {
+    NSMutableArray *titles = [NSMutableArray arrayWithObject:@"Request"];
+    if (self.requestModel.finished) {
+        [titles addObject:self.requestModel.didFinishWithError ? @"Error" : @"Response"];
+    }
+    return [titles copy];
+}
+
+- (NSInteger)segmentedControlSelectedIndex {
+    return self.selectedTab == DBRequestDetailsViewControllerTabRequest ? 0 : 1;
+}
+
+@end

+ 46 - 0
Pods/DBDebugToolkit/DBDebugToolkit/Classes/Network/MainQueueOperation/DBMainQueueOperation.h

@@ -0,0 +1,46 @@
+// The MIT License
+//
+// Copyright (c) 2016 Dariusz Bukowski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#import <Foundation/Foundation.h>
+
+typedef void(^DBMainQueueOperationBlock)(void);
+
+/**
+ `DBMainQueueOperation` is a `NSOperation` subclass running a given block on a main queue.
+ */
+@interface DBMainQueueOperation : NSOperation
+
+/**
+ Initializes `DBMainQueueOperation` object with a block.
+
+ @param block The block that will be invoked on the main queue.
+ */
+- (instancetype)initWithMainQueueOperationBlock:(DBMainQueueOperationBlock)block;
+
+/**
+ Creates and returns a new `DBMainQueueOperation` instance encapsulating a given block.
+
+ @param block The block that will be invoked on the main queue.
+ */
++ (instancetype)mainQueueOperationWithBlock:(DBMainQueueOperationBlock)block;
+
+@end

+ 97 - 0
Pods/DBDebugToolkit/DBDebugToolkit/Classes/Network/MainQueueOperation/DBMainQueueOperation.m

@@ -0,0 +1,97 @@
+// The MIT License
+//
+// Copyright (c) 2016 Dariusz Bukowski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#import "DBMainQueueOperation.h"
+
+typedef NS_ENUM(NSUInteger, DBMainQueueOperationState) {
+    DBMainQueueOperationStateNotStarted,
+    DBMainQueueOperationStateStarted,
+    DBMainQueueOperationStateFinished
+};
+
+@interface DBMainQueueOperation ()
+
+@property (nonatomic, assign) DBMainQueueOperationState state;
+@property (nonatomic, copy) DBMainQueueOperationBlock block;
+
+@end
+
+@implementation DBMainQueueOperation
+
+#pragma mark - Initialization
+
+- (instancetype)initWithMainQueueOperationBlock:(DBMainQueueOperationBlock)block {
+    self = [super init];
+    if (self) {
+        self.block = block;
+        self.state = DBMainQueueOperationStateNotStarted;
+    }
+
+    return self;
+}
+
++ (instancetype)mainQueueOperationWithBlock:(DBMainQueueOperationBlock)block {
+    return [[self alloc] initWithMainQueueOperationBlock:block];
+}
+
+#pragma mark - NSOperation methods
+
+- (void)start {
+    if (self.isCancelled) {
+        [self willChangeValueForKey:@"isFinished"];
+        self.state = DBMainQueueOperationStateFinished;
+        [self didChangeValueForKey:@"isFinished"];
+        return;
+    }
+
+    dispatch_async(dispatch_get_main_queue(), ^{
+        [self willChangeValueForKey:@"isExecuting"];
+        self.state = DBMainQueueOperationStateStarted;
+        [self didChangeValueForKey:@"isExecuting"];
+
+        if (self.block) {
+            self.block();
+        }
+
+        [self willChangeValueForKey:@"isFinished"];
+        [self willChangeValueForKey:@"isExecuting"];
+
+        self.state = DBMainQueueOperationStateFinished;
+
+        [self didChangeValueForKey:@"isFinished"];
+        [self didChangeValueForKey:@"isExecuting"];
+    });
+}
+
+- (BOOL)isFinished {
+    return self.state == DBMainQueueOperationStateFinished;
+}
+
+- (BOOL)isExecuting {
+    return self.state == DBMainQueueOperationStateStarted;
+}
+
+- (BOOL)isAsynchronous {
+    return NO;
+}
+
+@end

+ 38 - 0
Pods/DBDebugToolkit/DBDebugToolkit/Classes/Network/MainQueueOperation/NSOperationQueue+DBMainQueueOperation.h

@@ -0,0 +1,38 @@
+// The MIT License
+//
+// Copyright (c) 2016 Dariusz Bukowski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#import <Foundation/Foundation.h>
+#import "DBMainQueueOperation.h"
+
+/**
+ `NSOperationQueue` category that provides a helper method for adding a single main queue operation.
+ */
+@interface NSOperationQueue (DBMainQueueOperation)
+
+/**
+ Adds a single main queue operation encapsulating a given block.
+
+ @param block The block that will be invoked on the main queue.
+ */
+- (void)addMainQueueOperationWithBlock:(DBMainQueueOperationBlock)block;
+
+@end

+ 32 - 0
Pods/DBDebugToolkit/DBDebugToolkit/Classes/Network/MainQueueOperation/NSOperationQueue+DBMainQueueOperation.m

@@ -0,0 +1,32 @@
+// The MIT License
+//
+// Copyright (c) 2016 Dariusz Bukowski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#import "NSOperationQueue+DBMainQueueOperation.h"
+
+@implementation NSOperationQueue (DBMainQueueOperation)
+
+- (void)addMainQueueOperationWithBlock:(DBMainQueueOperationBlock)block {
+    DBMainQueueOperation *operation = [DBMainQueueOperation mainQueueOperationWithBlock:block];
+    [self addOperation:operation];
+}
+
+@end

+ 88 - 0
Pods/DBDebugToolkit/DBDebugToolkit/Classes/Network/RequestModel/DBRequestDataHandler.h

@@ -0,0 +1,88 @@
+// The MIT License
+//
+// Copyright (c) 2016 Dariusz Bukowski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#import <Foundation/Foundation.h>
+#import "DBRequestModel.h"
+
+@class DBRequestDataHandler;
+
+/**
+ A protocol used for informing about finishing of the data synchronization.
+ */
+@protocol DBRequestDataHandlerDelegate <NSObject>
+
+/**
+ Informs the delegate that the data has finished the synchronization.
+ 
+ @param requestDataHandler The `DBRequestDataHandler` instance that finished the data synchronization.
+ */
+- (void)requestDataHandlerDidFinishSynchronization:(DBRequestDataHandler *)requestDataHandler;
+
+@end
+
+/**
+ `DBRequestDataHandler` is a class handling the request and response body synchronization (saving it to a file).
+ */
+@interface DBRequestDataHandler : NSObject
+
+/**
+ Creates and returns a new instance.
+ 
+ @param filename The string containing the name of the file that will contain the saved data.
+ @param data The data that needs to be saved.
+ @param shouldGenerateThumbnail The boolean flag determining if the thumbnail generation is needed for this data.
+ */
++ (instancetype)dataHandlerWithFilename:(NSString *)filename data:(NSData *)data shouldGenerateThumbnail:(BOOL)shouldGenerateThumbnail;
+
+/**
+ Reads the saved data.
+ 
+ @param completion The block that will be called with the read data.
+ */
+- (void)readWithCompletion:(void(^)(NSData *))completion;
+
+/**
+ Recognized type of the data.
+ */
+@property (nonatomic, readonly) DBRequestModelBodyType dataType;
+
+/**
+ Current data synchronization status.
+ */
+@property (nonatomic, readonly) DBRequestModelBodySynchronizationStatus synchronizationStatus;
+
+/**
+ `NSInteger` containing the data length. Should be accessed after finishing the data synchronization.
+ */
+@property (nonatomic, readonly) NSInteger dataLength;
+
+/**
+ `UIImage` instance containing the thumbnail. It is `nil` if the data is not yet synchronized or if the data is not describing an image.
+ */
+@property (nonatomic, strong) UIImage *thumbnail;
+
+/**
+ Delegate that will be informed about finishing the data synchronization. It needs to conform to `DBRequestDataHandlerDelegate` protocol.
+ */
+@property (nonatomic, weak) id <DBRequestDataHandlerDelegate> delegate;
+
+@end

+ 162 - 0
Pods/DBDebugToolkit/DBDebugToolkit/Classes/Network/RequestModel/DBRequestDataHandler.m

@@ -0,0 +1,162 @@
+// The MIT License
+//
+// Copyright (c) 2016 Dariusz Bukowski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#import "DBRequestDataHandler.h"
+#import "DBNetworkToolkit.h"
+
+static CGSize const DBRequestDataHandlerThumbnailSize = { 50, 50 };
+
+@interface DBRequestDataHandler ()
+
+@property (nonatomic, copy) NSString *filename;
+@property (nonatomic, copy) void (^readingCompletion)(NSData *);
+@property (nonatomic, assign) DBRequestModelBodyType dataType;
+@property (nonatomic, assign) DBRequestModelBodySynchronizationStatus synchronizationStatus;
+@property (nonatomic, assign) NSInteger dataLength;
+
+@end
+
+@implementation DBRequestDataHandler
+
+#pragma mark - Initialization
+
+- (instancetype)initWithFilename:(NSString *)filename data:(NSData *)data shouldGenerateThumbnail:(BOOL)shouldGenerateThumbnail {
+    self = [super init];
+    if (self) {
+        self.filename = filename;
+        [self saveData:data shouldGenerateThumbnail:shouldGenerateThumbnail];
+    }
+    
+    return self;
+}
+
++ (instancetype)dataHandlerWithFilename:(NSString *)filename data:(NSData *)data shouldGenerateThumbnail:(BOOL)shouldGenerateThumbnail {
+    return [[self alloc] initWithFilename:filename data:data shouldGenerateThumbnail:(BOOL)shouldGenerateThumbnail];
+}
+
+#pragma mark - Saving data
+
+- (NSString *)pathForFile {
+    NSString *savedRequestsPath = [DBNetworkToolkit sharedInstance].savedRequestsPath;
+    return [savedRequestsPath stringByAppendingPathComponent:self.filename];
+}
+
+- (UIImage *)imageWithImage:(UIImage *)image scaledToFitSize:(CGSize)maxSize {
+    CGFloat scale = MAX(image.size.width / maxSize.width, image.size.height / maxSize.height);
+    CGSize newSize = CGSizeMake(image.size.width / scale, image.size.height / scale);
+    UIGraphicsBeginImageContextWithOptions(newSize, NO, 0.0);
+    [image drawInRect:CGRectMake(0, 0, newSize.width, newSize.height)];
+    UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
+    UIGraphicsEndImageContext();
+    return newImage;
+}
+
+- (void)determineDataTypeWithData:(NSData *)data shouldGenerateThumbnail:(BOOL)shouldGenerateThumbnail completion:(void(^)(void))completion {
+    __weak DBRequestDataHandler *weakSelf = self;
+    dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void){
+        DBRequestDataHandler *strongSelf = weakSelf;
+        UIImage *image = [UIImage imageWithData:data];
+        NSError *error;
+        if (image) {
+            strongSelf.dataType = DBRequestModelBodyTypeImage;
+            if (shouldGenerateThumbnail) {
+                strongSelf.thumbnail = [strongSelf imageWithImage:image scaledToFitSize:DBRequestDataHandlerThumbnailSize];
+            }
+        } else if ([NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&error] != nil) {
+            strongSelf.dataType = DBRequestModelBodyTypeJSON;
+        } else {
+            strongSelf.dataType = DBRequestModelBodyTypeOther;
+        }
+        dispatch_async(dispatch_get_main_queue(), ^(void){
+            if (completion) {
+                completion();
+            }
+        });
+    });
+}
+
+- (void)saveData:(NSData *)data atFilePath:(NSString *)filePath {
+    [[NSFileManager defaultManager] createDirectoryAtPath:[filePath stringByDeletingLastPathComponent]
+                              withIntermediateDirectories:YES
+                                               attributes:nil
+                                                    error:nil];
+    [data writeToFile:filePath atomically:YES];
+}
+
+- (void)saveData:(NSData *)data withCompletion:(void(^)(void))completion {
+    NSString *filePath = [self pathForFile];
+    dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void){
+        [self saveData:data atFilePath:filePath];
+        dispatch_async(dispatch_get_main_queue(), ^(void){
+            if (completion) {
+                completion();
+            }
+        });
+    });
+}
+
+- (void)saveData:(NSData *)data shouldGenerateThumbnail:(BOOL)shouldGenerateThumbnail {
+    self.dataLength = data.length;
+    self.synchronizationStatus = DBRequestModelBodySynchronizationStatusStarted;
+    if (self.dataLength > 0) {
+        __weak DBRequestDataHandler *weakSelf = self;
+        [self determineDataTypeWithData:data shouldGenerateThumbnail:(BOOL)shouldGenerateThumbnail completion:^{
+            [self saveData:data withCompletion:^{
+                DBRequestDataHandler *strongSelf = weakSelf;
+                strongSelf.synchronizationStatus = DBRequestModelBodySynchronizationStatusFinished;
+                [strongSelf.delegate requestDataHandlerDidFinishSynchronization:self];
+                if (strongSelf.readingCompletion) {
+                    strongSelf.readingCompletion(data);
+                    strongSelf.readingCompletion = nil;
+                }
+            }];
+        }];
+    } else {
+        self.dataType = DBRequestModelBodyTypeOther;
+        self.synchronizationStatus = DBRequestModelBodySynchronizationStatusFinished;
+        [self.delegate requestDataHandlerDidFinishSynchronization:self];
+        if (self.readingCompletion) {
+            self.readingCompletion(nil);
+            self.readingCompletion = nil;
+        }
+    }
+}
+
+#pragma mark - Reading data
+
+- (void)readWithCompletion:(void(^)(NSData *))completion {
+    if (self.synchronizationStatus != DBRequestModelBodySynchronizationStatusFinished) {
+        self.readingCompletion = completion;
+    } else {
+        NSString *filePath = [self pathForFile];
+        dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void){
+            NSData *data = [[NSData alloc] initWithContentsOfFile:filePath];
+            dispatch_async(dispatch_get_main_queue(), ^(void){
+                if (completion) {
+                    completion(data);
+                }
+            });
+        });
+    }
+}
+
+@end

+ 239 - 0
Pods/DBDebugToolkit/DBDebugToolkit/Classes/Network/RequestModel/DBRequestModel.h

@@ -0,0 +1,239 @@
+// The MIT License
+//
+// Copyright (c) 2016 Dariusz Bukowski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#import <Foundation/Foundation.h>
+#import "DBRequestOutcome.h"
+
+/**
+ Enum with the data type of the request or response body.
+ */
+typedef NS_ENUM(NSUInteger, DBRequestModelBodyType) {
+    DBRequestModelBodyTypeJSON,
+    DBRequestModelBodyTypeImage,
+    DBRequestModelBodyTypeOther
+};
+
+/**
+ Enum with the status of the request or response body data synchronization.
+ */
+typedef NS_ENUM(NSUInteger, DBRequestModelBodySynchronizationStatus) {
+    DBRequestModelBodySynchronizationStatusNotStarted,
+    DBRequestModelBodySynchronizationStatusStarted,
+    DBRequestModelBodySynchronizationStatusFinished
+};
+
+@class DBRequestModel;
+
+/**
+ A protocol used for informing about the finishing of the request or response body data synchronization.
+ */
+@protocol DBRequestModelDelegate <NSObject>
+
+/**
+ Informs the delegate that the synchronization of the request or response body data has finished.
+ 
+ @param requestModel The `DBRequestModel` instance that finished the data synchronization.
+ */
+- (void)requestModelDidFinishSynchronization:(DBRequestModel *)requestModel;
+
+@end
+
+/**
+ `DBRequestModel` is an object containing all the information about a request and its outcome.
+ */
+@interface DBRequestModel : NSObject
+
+/**
+ Creates and returns a new instance for the given request.
+ 
+ @param request The `NSURLRequest` instance.
+ */
++ (instancetype)requestModelWithRequest:(NSURLRequest *)request;
+
+/**
+ Saves the outcome of the request.
+ 
+ @param requestOutcome The `DBRequestOutcome` instance wrapping the response and data or the error received.
+ */
+- (void)saveOutcome:(DBRequestOutcome *)requestOutcome;
+
+/**
+ Saves the body of the request, contained either in an `NSData` instance or in `NSInputStream` instance.
+ 
+ @param body The `NSData` instance containing the body. If empty, the body is contained in the input stream.
+ @param bodyStream The `NSInputStream` instance containing the body in case of empty `NSData` instance.
+ */
+- (void)saveBodyWithData:(NSData *)body inputStream:(NSInputStream *)bodyStream;
+
+/**
+ Delegate that will be informed about the finishing of the body data synchronization. It needs to conform to `DBRequestModelDelegate` protocol.
+ */
+@property (nonatomic, weak) id <DBRequestModelDelegate> delegate;
+
+///-------------------------
+/// @name Request properties
+///-------------------------
+
+/**
+ `NSURL` instance that was used for the request. Read-only.
+ */
+@property (nonatomic, readonly) NSURL *url;
+
+/**
+ Cache policy that was used for the request. Read-only.
+ */
+@property (nonatomic, readonly) NSURLRequestCachePolicy cachePolicy;
+
+/**
+ Timeout interval that was used for the request. Read-only.
+ */
+@property (nonatomic, readonly) NSTimeInterval timeoutInterval;
+
+/**
+ Sending date of the request. Read-only.
+ */
+@property (nonatomic, readonly) NSDate *sendingDate;
+
+/**
+ HTTP method of the request. Is `nil` if the request used a different protocol. Read-only.
+ */
+@property (nonatomic, readonly) NSString *httpMethod;
+
+/**
+ Dictionary containing all the request HTTP hearers. Is `nil` if the request used a different protocol. Read-only.
+ */
+@property (nonatomic, readonly) NSDictionary<NSString *, NSString *> *allRequestHTTPHeaderFields;
+
+/**
+ The size of request body in bytes. Read-only.
+ */
+@property (nonatomic, readonly) NSInteger requestBodyLength;
+
+/**
+ `DBRequestModelBodyType` value describing the request body data type. Read-only.
+ */
+@property (nonatomic, readonly) DBRequestModelBodyType requestBodyType;
+
+/**
+ `DBRequestModelBodySynchronizationStatus` value describing the request body data synchronization status. Read-only.
+ */
+@property (nonatomic, readonly) DBRequestModelBodySynchronizationStatus requestBodySynchronizationStatus;
+
+/**
+ Reads the saved request body data.
+ 
+ @param completion The block that will be called with the read data.
+ */
+- (void)readRequestBodyWithCompletion:(void(^)(NSData *))completion;
+
+///--------------------------
+/// @name Response properties
+///--------------------------
+
+/**
+ The boolean flag informing if the request did finish. Read-only.
+ */
+@property (nonatomic, readonly) BOOL finished;
+
+/**
+ The MIME type of the response. Read-only.
+ */
+@property (nonatomic, readonly) NSString *MIMEType;
+
+/**
+ Text encoding name of the response. Read-only.
+ */
+@property (nonatomic, readonly) NSString *textEncodingName;
+
+/**
+ Receiving date of the response. Read-only.
+ */
+@property (nonatomic, readonly) NSDate *receivingDate;
+
+/**
+ Request duration. Read-only.
+ */
+@property (nonatomic, readonly) NSTimeInterval duration;
+
+/**
+ HTTP status code of the response. Is `nil` if the request used a different protocol. Read-only.
+ */
+@property (nonatomic, readonly) NSNumber *statusCode;
+
+/**
+ Localized description of the response HTTP status code. Is `nil` if the request used a different protocol. Read-only.
+ */
+@property (nonatomic, readonly) NSString *localizedStatusCodeString;
+
+/**
+ Dictionary containing all the response HTTP hearers. Is `nil` if the request used a different protocol. Read-only.
+ */
+@property (nonatomic, readonly) NSDictionary<NSString *, NSString *> *allResponseHTTPHeaderFields;
+
+/**
+ The size of response body in bytes. Read-only.
+ */
+@property (nonatomic, readonly) NSInteger responseBodyLength;
+
+/**
+ `DBRequestModelBodyType` value describing the response body data type. Read-only.
+ */
+@property (nonatomic, readonly) DBRequestModelBodyType responseBodyType;
+
+/**
+ `DBRequestModelBodySynchronizationStatus` value describing the response body data synchronization status. Read-only.
+ */
+@property (nonatomic, readonly) DBRequestModelBodySynchronizationStatus responseBodySynchronizationStatus;
+
+/**
+ `UIImage` instance containing the thumbnail for the image received in the response data.
+ It is `nil` if the data is not yet synchronized or if the data is not describing an image. Read-only.
+ */
+@property (nonatomic, readonly) UIImage *thumbnail;
+
+/**
+ Reads the saved response body data.
+ 
+ @param completion The block that will be called with the read data.
+ */
+- (void)readResponseBodyWithCompletion:(void(^)(NSData *))completion;
+
+///-----------------------
+/// @name Error properties
+///-----------------------
+
+/**
+ The boolean flag informing if the request did finish with error. Read-only.
+ */
+@property (nonatomic, readonly) BOOL didFinishWithError;
+
+/**
+ The code of the client side error.
+ */
+@property (nonatomic, readonly) NSInteger errorCode;
+
+/**
+ The localized description of the client side error.
+ */
+@property (nonatomic, readonly) NSString *localizedErrorDescription;
+
+@end

+ 235 - 0
Pods/DBDebugToolkit/DBDebugToolkit/Classes/Network/RequestModel/DBRequestModel.m

@@ -0,0 +1,235 @@
+// The MIT License
+//
+// Copyright (c) 2016 Dariusz Bukowski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#import "DBRequestModel.h"
+#import "DBRequestDataHandler.h"
+
+static const NSInteger DBRequestModelBodyStreamBufferSize = 4096;
+
+@interface DBRequestModel () <DBRequestDataHandlerDelegate>
+
+@property (nonatomic, strong) NSURL *url;
+@property (nonatomic, assign) NSURLRequestCachePolicy cachePolicy;
+@property (nonatomic, assign) NSTimeInterval timeoutInterval;
+@property (nonatomic, copy) NSString *httpMethod;
+@property (nonatomic, strong) NSDictionary<NSString *, NSString *> *allRequestHTTPHeaderFields;
+@property (nonatomic, strong) NSURLResponse *response;
+@property (nonatomic, strong) NSError *error;
+@property (nonatomic, assign) BOOL finished;
+@property (nonatomic, strong) NSDate *sendingDate;
+@property (nonatomic, strong) NSDate *receivingDate;
+@property (nonatomic, assign) NSTimeInterval duration;
+@property (nonatomic, strong) DBRequestDataHandler *requestDataHandler;
+@property (nonatomic, strong) DBRequestDataHandler *responseDataHandler;
+
+@end
+
+@implementation DBRequestModel
+
+#pragma mark - Initialization
+
+- (instancetype)initWithRequest:(NSURLRequest *)request {
+    self = [super init];
+    if (self) {
+        _url = request.URL;
+        self.cachePolicy = request.cachePolicy;
+        self.timeoutInterval = request.timeoutInterval;
+        _httpMethod = request.HTTPMethod;
+        _allRequestHTTPHeaderFields = request.allHTTPHeaderFields;
+        _sendingDate = [NSDate date];
+    }
+    
+    return self;
+}
+
++ (instancetype)requestModelWithRequest:(NSURLRequest *)request {
+    return [[self alloc] initWithRequest:request];
+}
+
+#pragma mark - Saving body
+
+- (void)saveBodyWithData:(NSData *)body inputStream:(NSInputStream *)bodyStream {
+    NSData *bodyData = body;
+    if ([bodyData length] == 0) {
+        bodyData = [self dataFromInputStream:bodyStream];
+    }
+    
+    [self saveRequestBody:bodyData];
+}
+
+- (NSData *)dataFromInputStream:(NSInputStream *)inputStream {
+    uint8_t byteBuffer[DBRequestModelBodyStreamBufferSize];
+    NSMutableData *mutableData = [NSMutableData data];
+    [inputStream open];
+    while (inputStream.hasBytesAvailable) {
+        NSInteger bytesRead = [inputStream read:byteBuffer maxLength:sizeof(byteBuffer)];
+        if (bytesRead == 0) {
+            break;
+        }
+        [mutableData appendBytes:byteBuffer length:bytesRead];
+    }
+    return [mutableData copy];
+}
+
+#pragma mark - Saving outcome
+
+- (void)saveOutcome:(DBRequestOutcome *)requestOutcome {
+    if (requestOutcome.error != nil) {
+        [self saveError:requestOutcome.error];
+    } else {
+        [self saveResponse:requestOutcome.response data:requestOutcome.data];
+    }
+}
+
+- (void)saveError:(NSError *)error {
+    [self finish];
+    self.error = error;
+}
+
+- (void)saveResponse:(NSURLResponse *)response data:(NSData *)data {
+    [self finish];
+    self.response = response;
+    [self saveResponseBody:data];
+}
+
+- (void)finish {
+    self.finished = true;
+    self.receivingDate = [NSDate date];
+    self.duration = [self.receivingDate timeIntervalSinceDate:self.sendingDate];
+}
+
+#pragma mark - Saving body
+
+- (NSString *)urlStringByRemovingSchemeFromURL:(NSURL *)url {
+    NSRange dividerRange = [url.absoluteString rangeOfString:@"://"];
+    return dividerRange.length == 0 ? url.absoluteString : [url.absoluteString substringFromIndex:NSMaxRange(dividerRange)];
+}
+
+- (NSString *)requestBodyFilename {
+    return [NSString stringWithFormat:@"Request/%@_%@", [self urlStringByRemovingSchemeFromURL:self.url], @(self.sendingDate.timeIntervalSince1970)];
+}
+
+- (NSString *)responseBodyFilename {
+    return [NSString stringWithFormat:@"Response/%@_%@", [self urlStringByRemovingSchemeFromURL:self.url], @(self.receivingDate.timeIntervalSince1970)];
+}
+
+- (void)saveRequestBody:(NSData *)data {
+    self.requestDataHandler = [DBRequestDataHandler dataHandlerWithFilename:[self requestBodyFilename] data:data shouldGenerateThumbnail:NO];
+    self.requestDataHandler.delegate = self;
+}
+
+- (void)saveResponseBody:(NSData *)data {
+    self.responseDataHandler = [DBRequestDataHandler dataHandlerWithFilename:[self responseBodyFilename] data:data shouldGenerateThumbnail:YES];
+    self.responseDataHandler.delegate = self;
+}
+
+#pragma mark - Accessing saved body
+
+- (void)readRequestBodyWithCompletion:(void (^)(NSData *))completion {
+    [self.requestDataHandler readWithCompletion:completion];
+}
+
+- (void)readResponseBodyWithCompletion:(void (^)(NSData *))completion {
+    [self.responseDataHandler readWithCompletion:completion];
+}
+
+#pragma mark - Request properties 
+
+- (NSInteger)requestBodyLength {
+    return self.requestDataHandler.dataLength;
+}
+
+- (DBRequestModelBodyType)requestBodyType {
+    return self.requestDataHandler.dataType;
+}
+
+- (DBRequestModelBodySynchronizationStatus)requestBodySynchronizationStatus {
+    return self.requestDataHandler.synchronizationStatus;
+}
+
+#pragma mark - Response properties
+
+- (NSInteger)responseBodyLength {
+    return self.responseDataHandler.dataLength;
+}
+
+- (DBRequestModelBodyType)responseBodyType {
+    return self.responseDataHandler.dataType;
+}
+
+- (NSHTTPURLResponse *)httpURLResponse {
+    if ([self.response isKindOfClass:[NSHTTPURLResponse class]]) {
+        return (NSHTTPURLResponse *)self.response;
+    }
+    return nil;
+}
+
+- (NSString *)MIMEType {
+    return self.response.MIMEType;
+}
+
+- (NSString *)textEncodingName {
+    return self.response.textEncodingName;
+}
+
+- (NSNumber *)statusCode {
+    NSHTTPURLResponse *httpResponse = [self httpURLResponse];
+    return httpResponse == nil ? nil : @(httpResponse.statusCode);
+}
+
+- (NSString *)localizedStatusCodeString {
+    return [NSHTTPURLResponse localizedStringForStatusCode:self.statusCode.integerValue];
+}
+
+- (NSDictionary<NSString *, NSString *> *)allResponseHTTPHeaderFields {
+    return [self httpURLResponse].allHeaderFields;
+}
+
+- (UIImage *)thumbnail {
+    return self.responseDataHandler.thumbnail;
+}
+
+- (DBRequestModelBodySynchronizationStatus)responseBodySynchronizationStatus {
+    return self.responseDataHandler.synchronizationStatus;
+}
+
+#pragma mark - Error properties
+
+- (BOOL)didFinishWithError {
+    return self.error != nil;
+}
+
+- (NSInteger)errorCode {
+    return self.error.code;
+}
+
+- (NSString *)localizedErrorDescription {
+    return self.error.localizedDescription;
+}
+
+#pragma mark - DBRequestDataHandlerDelegate
+
+- (void)requestDataHandlerDidFinishSynchronization:(DBRequestDataHandler *)requestDataHandler {
+    [self.delegate requestModelDidFinishSynchronization:self];
+}
+
+@end

+ 60 - 0
Pods/DBDebugToolkit/DBDebugToolkit/Classes/Network/RequestModel/DBRequestOutcome.h

@@ -0,0 +1,60 @@
+// The MIT License
+//
+// Copyright (c) 2016 Dariusz Bukowski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#import <Foundation/Foundation.h>
+
+/**
+ `DBRequestOutcome` is a class wrapping all the information about the request result.
+ */
+@interface DBRequestOutcome : NSObject
+
+/**
+ Creates and returns a new instance wrapping the information about a request that received a proper response.
+ 
+ @param response The received response.
+ @param data The data received with the response.
+ */
++ (instancetype)outcomeWithResponse:(NSURLResponse *)response data:(NSData *)data;
+
+/**
+ Creates and returns a new instance wrapping the information about a request that failed on the client side.
+ 
+ @param error The client side error.
+ */
++ (instancetype)outcomeWithError:(NSError *)error;
+
+/**
+ `NSURLResponse` instance that was received. Read-only.
+ */
+@property (nonatomic, readonly) NSURLResponse *response;
+
+/**
+ `NSData` instance that was received. Read-only.
+ */
+@property (nonatomic, readonly) NSData *data;
+
+/**
+ `NSError` instance that was received. Read-only.
+ */
+@property (nonatomic, readonly) NSError *error;
+
+@end

+ 64 - 0
Pods/DBDebugToolkit/DBDebugToolkit/Classes/Network/RequestModel/DBRequestOutcome.m

@@ -0,0 +1,64 @@
+// The MIT License
+//
+// Copyright (c) 2016 Dariusz Bukowski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#import "DBRequestOutcome.h"
+
+@interface DBRequestOutcome ()
+
+@property (nonatomic, strong) NSURLResponse *response;
+@property (nonatomic, strong) NSData *data;
+@property (nonatomic, strong) NSError *error;
+
+@end
+
+@implementation DBRequestOutcome
+
+#pragma mark - Initialization
+
+- (instancetype)initWithResponse:(NSURLResponse *)response data:(NSData *)data {
+    self = [super init];
+    if (self) {
+        _response = response;
+        _data = data;
+    }
+    
+    return self;
+}
+
+- (instancetype)initWithError:(NSError *)error {
+    self = [super init];
+    if (self) {
+        _error = error;
+    }
+    
+    return self;
+}
+
++ (instancetype)outcomeWithResponse:(NSURLResponse *)response data:(NSData *)data {
+    return [[self alloc] initWithResponse:response data:data];
+}
+
++ (instancetype)outcomeWithError:(NSError *)error {
+    return [[self alloc] initWithError:error];
+}
+
+@end

+ 38 - 0
Pods/DBDebugToolkit/DBDebugToolkit/Classes/Network/URLProtocol/DBAuthenticationChallengeSender.h

@@ -0,0 +1,38 @@
+// The MIT License
+//
+// Copyright (c) 2016 Dariusz Bukowski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+#import <Foundation/Foundation.h>
+
+typedef void (^SessionCompletionHandler)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential);
+
+/**
+ `DBAuthenticationChallengeSender` is a wrapper object responsible for calling the `NSURLSession` completion handler.
+ */
+@interface DBAuthenticationChallengeSender : NSObject <NSURLAuthenticationChallengeSender>
+
+/**
+ Creates and returns a new instance of `DBAuthenticationChallengeSender` with a given session completion handler.
+
+ @param sessionCompletionHandler The block passed to `NSURLSessionDelegate`.
+ */
++ (instancetype)authenticationChallengeSenderWithSessionCompletionHandler:(SessionCompletionHandler)sessionCompletionHandler;
+
+@end

+ 81 - 0
Pods/DBDebugToolkit/DBDebugToolkit/Classes/Network/URLProtocol/DBAuthenticationChallengeSender.m

@@ -0,0 +1,81 @@
+// The MIT License
+//
+// Copyright (c) 2016 Dariusz Bukowski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+
+#import "DBAuthenticationChallengeSender.h"
+
+@interface DBAuthenticationChallengeSender()
+
+@property (nonatomic, copy) SessionCompletionHandler sessionCompletionHandler;
+
+@end
+
+@implementation DBAuthenticationChallengeSender
+
+#pragma mark - Initialization
+
+- (instancetype)initWithSessionCompletionHandler:(SessionCompletionHandler)sessionCompletionHandler {
+    self = [super init];
+    if (self) {
+        self.sessionCompletionHandler = sessionCompletionHandler;
+    }
+
+    return self;
+}
+
++ (instancetype)authenticationChallengeSenderWithSessionCompletionHandler:(SessionCompletionHandler)sessionCompletionHandler {
+    return [[self alloc] initWithSessionCompletionHandler:sessionCompletionHandler];
+}
+
+#pragma mark - NSURLAuthenticationChallengeSender
+
+- (void)useCredential:(NSURLCredential *)credential forAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge {
+    if (self.sessionCompletionHandler) {
+        self.sessionCompletionHandler(NSURLSessionAuthChallengeUseCredential, credential);
+    }
+}
+
+- (void)continueWithoutCredentialForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge {
+    if (self.sessionCompletionHandler) {
+        self.sessionCompletionHandler(NSURLSessionAuthChallengeUseCredential, nil);
+    }
+}
+
+- (void)cancelAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge; {
+    if (self.sessionCompletionHandler) {
+        self.sessionCompletionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, nil);
+    }
+}
+
+- (void)performDefaultHandlingForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge {
+    if (self.sessionCompletionHandler) {
+        self.sessionCompletionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil);
+    }
+}
+
+- (void)rejectProtectionSpaceAndContinueWithChallenge:(NSURLAuthenticationChallenge *)challenge {
+    if (self.sessionCompletionHandler) {
+        self.sessionCompletionHandler(NSURLSessionAuthChallengeRejectProtectionSpace, nil);
+    }
+}
+
+@end

+ 30 - 0
Pods/DBDebugToolkit/DBDebugToolkit/Classes/Network/URLProtocol/DBURLProtocol.h

@@ -0,0 +1,30 @@
+// The MIT License
+//
+// Copyright (c) 2016 Dariusz Bukowski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#import <Foundation/Foundation.h>
+
+/**
+ `DBURLProtocol` is a `NSURLProtocol` subclass that is used for logging all the requests sent by the application.
+ */
+@interface DBURLProtocol : NSURLProtocol
+
+@end

+ 112 - 0
Pods/DBDebugToolkit/DBDebugToolkit/Classes/Network/URLProtocol/DBURLProtocol.m

@@ -0,0 +1,112 @@
+// The MIT License
+//
+// Copyright (c) 2016 Dariusz Bukowski
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#import "DBURLProtocol.h"
+#import "DBNetworkToolkit.h"
+#import "DBRequestOutcome.h"
+#import "DBAuthenticationChallengeSender.h"
+
+static NSString *const DBURLProtocolHandledKey = @"DBURLProtocolHandled";
+
+@interface DBURLProtocol () <NSURLSessionDelegate>
+
+@property (nonatomic, strong) NSURLSession *urlSession;
+
+@end
+
+@implementation DBURLProtocol
+
++ (BOOL)canInitWithTask:(NSURLSessionTask *)task {
+    NSURLRequest *request = task.currentRequest;
+    return request == nil ? NO : [self canInitWithRequest:request];
+}
+
++ (BOOL)canInitWithRequest:(NSURLRequest *)request {
+    if (![DBNetworkToolkit sharedInstance].loggingEnabled) {
+        return NO;
+    }
+    
+    if ([[self propertyForKey:DBURLProtocolHandledKey inRequest:request] boolValue]) {
+        return NO;
+    }
+    
+    return YES;
+}
+
++ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request {
+    return request;
+}
+
+- (void)startLoading {
+    [[DBNetworkToolkit sharedInstance] saveRequest:self.request];
+    NSMutableURLRequest *request = [[DBURLProtocol canonicalRequestForRequest:self.request] mutableCopy];
+    
+    [DBURLProtocol setProperty:@YES forKey:DBURLProtocolHandledKey inRequest:request];
+    
+    if (!self.urlSession) {
+        if ([[[UIDevice currentDevice] systemVersion] compare:@"10.0" options:NSNumericSearch] != NSOrderedAscending) {
+            self.urlSession = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]
+                                                            delegate:self
+                                                       delegateQueue:nil];
+        } else {
+            self.urlSession = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]];
+        }
+    }
+    
+    [[self.urlSession dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
+        
+        if (error != nil) {
+            [self finishWithOutcome:[DBRequestOutcome outcomeWithError:error]];
+            [self.client URLProtocol:self didFailWithError:error];
+        } else {
+            [self finishWithOutcome:[DBRequestOutcome outcomeWithResponse:response data:data]];
+        }
+        
+        if (response != nil) {
+            [self.client URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed];
+        }
+        
+        if (data != nil) {
+            [self.client URLProtocol:self didLoadData:data];
+        }
+        
+        [self.client URLProtocolDidFinishLoading:self];
+    }] resume];
+}
+
+- (void)stopLoading {
+    // Do nothing
+}
+
+- (void)finishWithOutcome:(DBRequestOutcome *)requestOutcome {
+    [[DBNetworkToolkit sharedInstance] saveRequestOutcome:requestOutcome forRequest:self.request];
+}
+
+#pragma mark - NSURLSessionDelegate 
+
+- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential * _Nullable))completionHandler {
+    DBAuthenticationChallengeSender *challengeSender = [DBAuthenticationChallengeSender authenticationChallengeSenderWithSessionCompletionHandler:completionHandler];
+    NSURLAuthenticationChallenge *modifiedChallenge = [[NSURLAuthenticationChallenge alloc] initWithAuthenticationChallenge:challenge sender:challengeSender];
+    [self.client URLProtocol:self didReceiveAuthenticationChallenge:modifiedChallenge];
+}
+
+@end

Энэ ялгаанд хэт олон файл өөрчлөгдсөн тул зарим файлыг харуулаагүй болно