diff --git a/Assets/crashDetails.gif b/Assets/crashDetails.gif new file mode 100644 index 0000000..5a42ea7 Binary files /dev/null and b/Assets/crashDetails.gif differ diff --git a/Assets/crashesList.gif b/Assets/crashesList.gif new file mode 100644 index 0000000..ce28bde Binary files /dev/null and b/Assets/crashesList.gif differ diff --git a/Assets/fontFamilies.PNG b/Assets/fontFamilies.PNG deleted file mode 100644 index 6b6b9c8..0000000 Binary files a/Assets/fontFamilies.PNG and /dev/null differ diff --git a/Assets/fontFamilies.gif b/Assets/fontFamilies.gif new file mode 100644 index 0000000..01e0cb8 Binary files /dev/null and b/Assets/fontFamilies.gif differ diff --git a/Assets/opening.gif b/Assets/opening.gif index fac06a5..c491811 100644 Binary files a/Assets/opening.gif and b/Assets/opening.gif differ diff --git a/DBDebugToolkit/Classes/Categories/UIView+Snapshot.h b/DBDebugToolkit/Classes/Categories/UIView+Snapshot.h new file mode 100644 index 0000000..2f5f320 --- /dev/null +++ b/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 diff --git a/DBDebugToolkit/Classes/Categories/UIView+Snapshot.m b/DBDebugToolkit/Classes/Categories/UIView+Snapshot.m new file mode 100644 index 0000000..aec5e5f --- /dev/null +++ b/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 diff --git a/DBDebugToolkit/Classes/CrashReports/DBCrashReport.h b/DBDebugToolkit/Classes/CrashReports/DBCrashReport.h new file mode 100644 index 0000000..af3534a --- /dev/null +++ b/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 + +/** + `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 *)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 *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 diff --git a/DBDebugToolkit/Classes/CrashReports/DBCrashReport.m b/DBDebugToolkit/Classes/CrashReports/DBCrashReport.m new file mode 100644 index 0000000..f2c7bc5 --- /dev/null +++ b/DBDebugToolkit/Classes/CrashReports/DBCrashReport.m @@ -0,0 +1,139 @@ +// 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 *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]; + NSData *screenshotData = [[NSData alloc] initWithBase64EncodedString:dictionary[DBCrashReportScreenshotKey] + options:NSDataBase64DecodingIgnoreUnknownCharacters]; + self.screenshot = [UIImage imageWithData:screenshotData]; + self.systemVersion = dictionary[DBCrashReportSystemVersionKey]; + self.appVersion = dictionary[DBCrashReportAppVersionKey]; + } + + return self; +} + +- (instancetype)initWithName:(NSString *)name + reason:(NSString *)reason + userInfo:(NSDictionary *)userInfo + callStackSymbols:(NSArray *)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 diff --git a/DBDebugToolkit/Classes/CrashReports/DBCrashReportDetailsTableViewController.h b/DBDebugToolkit/Classes/CrashReports/DBCrashReportDetailsTableViewController.h new file mode 100644 index 0000000..3d73a5f --- /dev/null +++ b/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 +#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 diff --git a/DBDebugToolkit/Classes/CrashReports/DBCrashReportDetailsTableViewController.m b/DBDebugToolkit/Classes/CrashReports/DBCrashReportDetailsTableViewController.m new file mode 100644 index 0000000..f7350df --- /dev/null +++ b/DBDebugToolkit/Classes/CrashReports/DBCrashReportDetailsTableViewController.m @@ -0,0 +1,317 @@ +// 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 + +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 () + +@property (nonatomic, weak) IBOutlet UIBarButtonItem *shareButton; +@property (nonatomic, strong) NSArray *detailCellDataSources; +@property (nonatomic, strong) NSArray *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]; + 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]; + [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:@"Environment:
"]; + + // App version. + [mailHTMLBody appendFormat:@"App version: %@
", [self.buildInfoProvider buildInfoString]]; + + // System version. + [mailHTMLBody appendFormat:@"System version: %@
", [self.deviceInfoProvider systemVersion]]; + + // Device model. + [mailHTMLBody appendFormat:@"Device model: %@

", [self.deviceInfoProvider deviceModel]]; + + + // Details. + [mailHTMLBody appendString:@"Details:
"]; + + // Name. + [mailHTMLBody appendFormat:@"Name: %@
", self.crashReport.name]; + + // Date. + [mailHTMLBody appendFormat:@"Date: %@
", self.crashReport.dateString]; + + // Reason. + if (self.crashReport.reason) { + [mailHTMLBody appendFormat:@"Reason: %@
", self.crashReport.reason]; + } + + + // User info. + if (self.crashReport.userInfo.count > 0) { + [mailHTMLBody appendString:@"
User info:
"]; + for (NSString *key in self.crashReport.userInfo.allKeys) { + NSString *value = self.crashReport.userInfo[key]; + [mailHTMLBody appendFormat:@"%@: %@
", key, value]; + } + } + + + // Stack trace. + NSString *stackTrace = [self.crashReport.callStackSymbols componentsJoinedByString:@"\n"]; + NSString *stackTraceWithIgnoredHTMLTags = [stackTrace stringByReplacingOccurrencesOfString:@"<" withString:@"<"]; + NSString *stackTraceWithProperNewlines = [stackTraceWithIgnoredHTMLTags stringByReplacingOccurrencesOfString:@"\n" withString:@"
"]; + [mailHTMLBody appendFormat:@"

Stack trace:
%@

", stackTraceWithProperNewlines]; + + // Console output. + NSString *consoleOutputWithIgnoredHTMLTags = [self.crashReport.consoleOutput stringByReplacingOccurrencesOfString:@"<" withString:@"<"]; + NSString *consoleOutputWithProperNewlines = [consoleOutputWithIgnoredHTMLTags stringByReplacingOccurrencesOfString:@"\n" withString:@"
"]; + [mailHTMLBody appendFormat:@"

Console output:
%@

", consoleOutputWithProperNewlines]; + + return mailHTMLBody; +} + +@end diff --git a/DBDebugToolkit/Classes/CrashReports/DBCrashReportsTableViewController.h b/DBDebugToolkit/Classes/CrashReports/DBCrashReportsTableViewController.h new file mode 100644 index 0000000..e34c808 --- /dev/null +++ b/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 +#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 diff --git a/DBDebugToolkit/Classes/CrashReports/DBCrashReportsTableViewController.m b/DBDebugToolkit/Classes/CrashReports/DBCrashReportsTableViewController.m new file mode 100644 index 0000000..710c209 --- /dev/null +++ b/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 diff --git a/DBDebugToolkit/Classes/CrashReports/DBCrashReportsToolkit.h b/DBDebugToolkit/Classes/CrashReports/DBCrashReportsToolkit.h new file mode 100644 index 0000000..9f93ad2 --- /dev/null +++ b/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 +#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 *)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 diff --git a/DBDebugToolkit/Classes/CrashReports/DBCrashReportsToolkit.m b/DBDebugToolkit/Classes/CrashReports/DBCrashReportsToolkit.m new file mode 100644 index 0000000..7bf0de4 --- /dev/null +++ b/DBDebugToolkit/Classes/CrashReports/DBCrashReportsToolkit.m @@ -0,0 +1,277 @@ +// 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 +#include +#include +#include +#include + +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 *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 *)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 *)callStackSymbols { + NSDate *date = [NSDate date]; + NSString *consoleOutput = self.consoleOutputCaptor.consoleOutput; + UIImage *screenshot = [[UIApplication sharedApplication].keyWindow snapshot]; + 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; +#endif + // 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 ); +} + +@end diff --git a/DBDebugToolkit/Classes/CrashReports/DBImageViewViewController.h b/DBDebugToolkit/Classes/CrashReports/DBImageViewViewController.h new file mode 100644 index 0000000..6418ed1 --- /dev/null +++ b/DBDebugToolkit/Classes/CrashReports/DBImageViewViewController.h @@ -0,0 +1,38 @@ +// 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` 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. + */ +- (void)configureWithTitle:(NSString *)title image:(UIImage *)image; + +@end diff --git a/DBDebugToolkit/Classes/CrashReports/DBImageViewViewController.m b/DBDebugToolkit/Classes/CrashReports/DBImageViewViewController.m new file mode 100644 index 0000000..0b7c737 --- /dev/null +++ b/DBDebugToolkit/Classes/CrashReports/DBImageViewViewController.m @@ -0,0 +1,52 @@ +// 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; + +@end + +@implementation DBImageViewViewController + +- (void)viewDidLoad { + [super viewDidLoad]; + [self updateImage:self.image]; +} + +- (void)configureWithTitle:(NSString *)title image:(UIImage *)image { + self.title = title; + [self updateImage:image]; +} + +#pragma mark - Private methods + +- (void)updateImage:(UIImage *)image { + self.image = image; + self.imageView.image = image; +} + +@end diff --git a/DBDebugToolkit/Classes/DBDebugToolkit.h b/DBDebugToolkit/Classes/DBDebugToolkit.h index d770b75..db680c2 100644 --- a/DBDebugToolkit/Classes/DBDebugToolkit.h +++ b/DBDebugToolkit/Classes/DBDebugToolkit.h @@ -147,4 +147,13 @@ */ + (DBCustomVariable *)customVariableWithName:(NSString *)variableName; +///-------------------- +/// @name Crash reports +///-------------------- + +/** + Enables collecting crash reports. + */ ++ (void)setupCrashReporting; + @end diff --git a/DBDebugToolkit/Classes/DBDebugToolkit.m b/DBDebugToolkit/Classes/DBDebugToolkit.m index 94af60d..ac6dda8 100644 --- a/DBDebugToolkit/Classes/DBDebugToolkit.m +++ b/DBDebugToolkit/Classes/DBDebugToolkit.m @@ -34,6 +34,7 @@ #import "DBKeychainToolkit.h" #import "DBUserDefaultsToolkit.h" #import "DBCoreDataToolkit.h" +#import "DBCrashReportsToolkit.h" static NSString *const DBDebugToolkitObserverPresentationControllerPropertyKeyPath = @"containerView"; @@ -48,6 +49,7 @@ @interface DBDebugToolkit () *customActions; @property (nonatomic, strong) NSMutableDictionary *customVariables; @@ -85,6 +87,7 @@ + (instancetype)sharedInstance { [sharedInstance setupCoreDataToolkit]; [sharedInstance setupCustomActions]; [sharedInstance setupCustomVariables]; + [sharedInstance setupCrashReportsToolkit]; }); return sharedInstance; } @@ -245,6 +248,20 @@ + (DBCustomVariable *)customVariableWithName:(NSString *)variableName { 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 { @@ -320,6 +337,7 @@ - (DBMenuTableViewController *)menuViewController { _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; diff --git a/DBDebugToolkit/Classes/Menu/DBMenuTableViewController.h b/DBDebugToolkit/Classes/Menu/DBMenuTableViewController.h index 3bf6908..816160f 100644 --- a/DBDebugToolkit/Classes/Menu/DBMenuTableViewController.h +++ b/DBDebugToolkit/Classes/Menu/DBMenuTableViewController.h @@ -31,6 +31,7 @@ #import "DBCoreDataToolkit.h" #import "DBCustomAction.h" #import "DBCustomVariable.h" +#import "DBCrashReportsToolkit.h" @class DBMenuTableViewController; @@ -98,6 +99,11 @@ */ @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. */ diff --git a/DBDebugToolkit/Classes/Menu/DBMenuTableViewController.m b/DBDebugToolkit/Classes/Menu/DBMenuTableViewController.m index c080636..60e7727 100644 --- a/DBDebugToolkit/Classes/Menu/DBMenuTableViewController.m +++ b/DBDebugToolkit/Classes/Menu/DBMenuTableViewController.m @@ -30,6 +30,7 @@ #import "DBResourcesTableViewController.h" #import "DBCustomActionsTableViewController.h" #import "DBCustomVariablesTableViewController.h" +#import "DBCrashReportsTableViewController.h" typedef NS_ENUM(NSUInteger, DBMenuTableViewControllerRow) { DBMenuTableViewControllerRowPerformance, @@ -38,6 +39,7 @@ typedef NS_ENUM(NSUInteger, DBMenuTableViewControllerRow) { DBMenuTableViewControllerRowResources, DBMenuTableViewControllerRowConsole, DBMenuTableViewControllerRowLocation, + DBMenuTableViewControllerRowCrashReports, DBMenuTableViewControllerRowCustomVariables, DBMenuTableViewControllerRowCustomActions, DBMenuTableViewControllerRowApplicationSettings @@ -117,6 +119,9 @@ - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { } 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; } } diff --git a/DBDebugToolkit/Classes/Network/RequestModel/DBRequestModel.h b/DBDebugToolkit/Classes/Network/RequestModel/DBRequestModel.h index df2b36b..15900ee 100644 --- a/DBDebugToolkit/Classes/Network/RequestModel/DBRequestModel.h +++ b/DBDebugToolkit/Classes/Network/RequestModel/DBRequestModel.h @@ -57,6 +57,9 @@ typedef NS_ENUM(NSUInteger, DBRequestModelBodySynchronizationStatus) { @end +/** + `DBRequestModel` is an object containing all the information about a request and its outcome. + */ @interface DBRequestModel : NSObject /** diff --git a/DBDebugToolkit/Classes/UserInterface/DBFontFamiliesTableViewController.h b/DBDebugToolkit/Classes/UserInterface/DBFontFamiliesTableViewController.h new file mode 100644 index 0000000..d3ff14b --- /dev/null +++ b/DBDebugToolkit/Classes/UserInterface/DBFontFamiliesTableViewController.h @@ -0,0 +1,30 @@ +// 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 + +/** + `DBFontFamiliesTableViewController` is a table view controller presenting all the available fonts grouped by the font families. + */ +@interface DBFontFamiliesTableViewController : UITableViewController + +@end diff --git a/DBDebugToolkit/Classes/UserInterface/DBFontFamiliesTableViewController.m b/DBDebugToolkit/Classes/UserInterface/DBFontFamiliesTableViewController.m new file mode 100644 index 0000000..ae1a0bf --- /dev/null +++ b/DBDebugToolkit/Classes/UserInterface/DBFontFamiliesTableViewController.m @@ -0,0 +1,116 @@ +// 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 "DBFontFamiliesTableViewController.h" +#import "NSBundle+DBDebugToolkit.h" +#import "DBFontPreviewViewController.h" + +static NSString *const DBFontFamiliesTableViewControllerFontCellIdentifier = @"FontCell"; + +@interface DBFontFamiliesTableViewController () + +@property (nonatomic, copy) NSArray *fontFamilyNames; +@property (nonatomic, copy) NSArray *> *fontNames; +@property (nonatomic, copy) NSArray *allFontNames; + +@end + +@implementation DBFontFamiliesTableViewController + +#pragma mark - Setup + +- (void)viewDidLoad { + [super viewDidLoad]; + [self setupFonts]; +} + +- (void)setupFonts { + self.fontFamilyNames = [[UIFont familyNames] sortedArrayUsingSelector:@selector(localizedCaseInsensitiveCompare:)]; + NSMutableArray *fontNames = [NSMutableArray array]; + NSMutableArray *allFontNames = [NSMutableArray array]; + for (NSString *fontFamilyName in self.fontFamilyNames) { + NSArray *familyFonts = [UIFont fontNamesForFamilyName:fontFamilyName]; + NSArray *sortedFamilyFonts = [familyFonts sortedArrayUsingSelector:@selector(localizedCaseInsensitiveCompare:)]; + [fontNames addObject:sortedFamilyFonts]; + [allFontNames addObjectsFromArray:sortedFamilyFonts]; + } + self.fontNames = [fontNames copy]; + self.allFontNames = [allFontNames copy]; +} + +#pragma mark - UITableViewDataSource + +- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { + return self.fontFamilyNames.count; +} + +- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { + return self.fontNames[section].count; +} + +- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section { + if ([self tableView:tableView numberOfRowsInSection:section] == 0) { + return nil; + } + + return self.fontFamilyNames[section]; +} + +- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { + UITableViewCell *fontCell = [tableView dequeueReusableCellWithIdentifier:DBFontFamiliesTableViewControllerFontCellIdentifier]; + if (fontCell == nil) { + fontCell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault + reuseIdentifier:DBFontFamiliesTableViewControllerFontCellIdentifier]; + fontCell.accessoryType = UITableViewCellAccessoryDisclosureIndicator; + fontCell.textLabel.adjustsFontSizeToFitWidth = YES; + } + fontCell.textLabel.text = self.fontNames[indexPath.section][indexPath.row]; + return fontCell; +} + +#pragma mark - UITableViewDelegate + +- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { + NSBundle *bundle = [NSBundle debugToolkitBundle]; + NSString *selectedFontName = self.fontNames[indexPath.section][indexPath.row]; + NSInteger selectedFontIndex = [self.allFontNames indexOfObject:selectedFontName]; + UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"DBFontPreviewViewController" bundle:bundle]; + DBFontPreviewViewController *fontPreviewViewController = [storyboard instantiateInitialViewController]; + [fontPreviewViewController configureWithSelectedFontIndex:selectedFontIndex allFontNames:self.allFontNames]; + [self.navigationController pushViewController:fontPreviewViewController animated:YES]; +} + +- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section { + return [self heightForFooterAndHeaderInSection:section]; +} + +- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section { + return [self heightForFooterAndHeaderInSection:section]; +} + +#pragma mark - Private methods + +- (CGFloat)heightForFooterAndHeaderInSection:(NSInteger)section { + return [self tableView:self.tableView numberOfRowsInSection:section] > 0 ? UITableViewAutomaticDimension : CGFLOAT_MIN; +} + +@end diff --git a/DBDebugToolkit/Classes/UserInterface/DBFontPreviewViewController.h b/DBDebugToolkit/Classes/UserInterface/DBFontPreviewViewController.h new file mode 100644 index 0000000..5271c78 --- /dev/null +++ b/DBDebugToolkit/Classes/UserInterface/DBFontPreviewViewController.h @@ -0,0 +1,38 @@ +// 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 + +/** + `DBFontPreviewViewController` is a view controller presenting an example text with a given font. + */ +@interface DBFontPreviewViewController : UIViewController + +/** + Configures the view controller with an array of available fonts and an index of the selected font. + + @param selectedFontIndex Index of the selected font in the array of available fonts. + @param allFontNames Array containing all the names of the available fonts. + */ +- (void)configureWithSelectedFontIndex:(NSInteger)selectedFontIndex allFontNames:(NSArray *)allFontNames; + +@end diff --git a/DBDebugToolkit/Classes/UserInterface/DBFontPreviewViewController.m b/DBDebugToolkit/Classes/UserInterface/DBFontPreviewViewController.m new file mode 100644 index 0000000..405fec9 --- /dev/null +++ b/DBDebugToolkit/Classes/UserInterface/DBFontPreviewViewController.m @@ -0,0 +1,88 @@ +// 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 "DBFontPreviewViewController.h" + +static const NSUInteger DBFontPreviewViewControllerFontSize = 12; + +@interface DBFontPreviewViewController () + +@property (nonatomic, weak) IBOutlet UITextView *textView; +@property (nonatomic, weak) IBOutlet UIButton *previousButton; +@property (nonatomic, weak) IBOutlet UIButton *nextButton; +@property (nonatomic, weak) IBOutlet UILabel *familyNameLabel; +@property (nonatomic, weak) IBOutlet UILabel *fontNameLabel; +@property (nonatomic, weak) IBOutlet NSLayoutConstraint *separatorViewHeightConstraint; +@property (nonatomic, assign) NSInteger selectedFontIndex; +@property (nonatomic, copy) NSArray *allFontNames; + +@end + +@implementation DBFontPreviewViewController + +- (void)viewDidLoad { + [super viewDidLoad]; + self.separatorViewHeightConstraint.constant = 1.0 / [UIScreen mainScreen].scale; + self.textView.text = [self exampleText]; + [self refreshView]; +} + +- (void)viewDidLayoutSubviews { + [super viewDidLayoutSubviews]; + self.textView.contentOffset = CGPointZero; +} + +#pragma mark - Navigation + +- (IBAction)previousButtonAction:(id)sender { + self.selectedFontIndex -= 1; + [self refreshView]; +} + +- (IBAction)nextButtonAction:(id)sender { + self.selectedFontIndex += 1; + [self refreshView]; +} + +#pragma mark - Configuration + +- (void)configureWithSelectedFontIndex:(NSInteger)selectedFontIndex allFontNames:(NSArray *)allFontNames { + self.selectedFontIndex = selectedFontIndex; + self.allFontNames = allFontNames; +} + +#pragma mark - Private methods + +- (void)refreshView { + NSString *selectedFontName = self.allFontNames[self.selectedFontIndex]; + self.textView.font = [UIFont fontWithName:selectedFontName size:DBFontPreviewViewControllerFontSize]; + self.fontNameLabel.text = selectedFontName; + self.familyNameLabel.text = self.textView.font.familyName; + self.previousButton.enabled = self.selectedFontIndex > 0; + self.nextButton.enabled = self.selectedFontIndex < self.allFontNames.count - 1; +} + +- (NSString *)exampleText { + return @"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.\n\nSed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur?\n\nAt vero eos et accusamus et iusto odio dignissimos ducimus qui blanditiis praesentium voluptatum deleniti atque corrupti quos dolores et quas molestias excepturi sint occaecati cupiditate non provident, similique sunt in culpa qui officia deserunt mollitia animi, id est laborum et dolorum fuga. Et harum quidem rerum facilis est et expedita distinctio. Nam libero tempore, cum soluta nobis est eligendi optio cumque nihil impedit quo minus id quod maxime placeat facere possimus, omnis voluptas assumenda est, omnis dolor repellendus. Temporibus autem quibusdam et aut officiis debitis aut rerum necessitatibus saepe eveniet ut et voluptates repudiandae sint et molestiae non recusandae. Itaque earum rerum hic tenetur a sapiente delectus, ut aut reiciendis voluptatibus maiores alias consequatur aut perferendis doloribus asperiores repellat."; +} + +@end diff --git a/DBDebugToolkit/Classes/UserInterface/DBTextViewViewController.h b/DBDebugToolkit/Classes/UserInterface/DBTextViewViewController.h index fc06513..d9dfcec 100644 --- a/DBDebugToolkit/Classes/UserInterface/DBTextViewViewController.h +++ b/DBDebugToolkit/Classes/UserInterface/DBTextViewViewController.h @@ -32,7 +32,8 @@ @param title String containing a title for the view controller. @param text String with content of the text view. + @param isInConsoleMode Bool a flag determining whether the view will present the console output or not (affects font). */ -- (void)configureWithTitle:(NSString *)title text:(NSString *)text; +- (void)configureWithTitle:(NSString *)title text:(NSString *)text isInConsoleMode:(BOOL)isInConsoleMode; @end diff --git a/DBDebugToolkit/Classes/UserInterface/DBTextViewViewController.m b/DBDebugToolkit/Classes/UserInterface/DBTextViewViewController.m index 1bba358..39c7a15 100644 --- a/DBDebugToolkit/Classes/UserInterface/DBTextViewViewController.m +++ b/DBDebugToolkit/Classes/UserInterface/DBTextViewViewController.m @@ -25,7 +25,9 @@ @interface DBTextViewViewController () @property (nonatomic, weak) IBOutlet UITextView *textView; +@property (nonatomic, weak) IBOutlet NSLayoutConstraint *textViewWidthConstraint; @property (nonatomic, copy) NSString *text; +@property (nonatomic, assign) BOOL isInConsoleMode; @end @@ -35,10 +37,12 @@ - (void)viewDidLoad { [super viewDidLoad]; self.textView.layoutManager.allowsNonContiguousLayout = NO; [self updateText:self.text]; + [self modeSetup]; } -- (void)configureWithTitle:(NSString *)title text:(NSString *)text { +- (void)configureWithTitle:(NSString *)title text:(NSString *)text isInConsoleMode:(BOOL)isInConsoleMode { self.title = title; + self.isInConsoleMode = isInConsoleMode; [self updateText:text]; } @@ -49,4 +53,13 @@ - (void)updateText:(NSString *)text { self.textView.text = text; } +- (void)modeSetup { + if (self.isInConsoleMode) { + self.textView.font = [UIFont systemFontOfSize:11 weight:UIFontWeightSemibold]; + } else { + self.textView.font = [UIFont systemFontOfSize:14]; + self.textViewWidthConstraint.active = NO; + } +} + @end diff --git a/DBDebugToolkit/Classes/UserInterface/DBUserInterfaceTableViewController.m b/DBDebugToolkit/Classes/UserInterface/DBUserInterfaceTableViewController.m index 40f6fe0..9cb71f0 100644 --- a/DBDebugToolkit/Classes/UserInterface/DBUserInterfaceTableViewController.m +++ b/DBDebugToolkit/Classes/UserInterface/DBUserInterfaceTableViewController.m @@ -24,6 +24,7 @@ #import "NSBundle+DBDebugToolkit.h" #import "DBMenuSwitchTableViewCell.h" #import "DBTextViewViewController.h" +#import "DBFontFamiliesTableViewController.h" typedef NS_ENUM(NSUInteger, DBUserInterfaceTableViewControllerCell) { DBUserInterfaceTableViewControllerCellColorBorders, @@ -99,10 +100,17 @@ - (void)openTextViewViewControllerWithTitle:(NSString *)title text:(NSString *)t NSBundle *bundle = [NSBundle debugToolkitBundle]; UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"DBTextViewViewController" bundle:bundle]; DBTextViewViewController *textViewViewController = [storyboard instantiateInitialViewController]; - [textViewViewController configureWithTitle:title text:text]; + [textViewViewController configureWithTitle:title text:text isInConsoleMode:NO]; [self.navigationController pushViewController:textViewViewController animated:YES]; } +- (void)openFontFamiliesTableViewController { + NSBundle *bundle = [NSBundle debugToolkitBundle]; + UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"DBFontFamiliesTableViewController" bundle:bundle]; + DBFontFamiliesTableViewController *fontFamiliesTableViewController = [storyboard instantiateInitialViewController]; + [self.navigationController pushViewController:fontFamiliesTableViewController animated:YES]; +} + #pragma mark - UITableViewDataSource - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { @@ -163,7 +171,7 @@ - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath [self openTextViewViewControllerWithTitle:title text:[self.userInterfaceToolkit viewControllerHierarchy]]; break; case DBUserInterfaceTableViewControllerCellFontFamilies: - [self openTextViewViewControllerWithTitle:title text:[self.userInterfaceToolkit fontFamilies]]; + [self openFontFamiliesTableViewController]; break; case DBUserInterfaceTableViewControllerCellDebuggingInformationOverlay: [self.delegate userInterfaceTableViewControllerDidOpenDebuggingInformationOverlay:self]; diff --git a/DBDebugToolkit/Classes/UserInterface/DBUserInterfaceToolkit.h b/DBDebugToolkit/Classes/UserInterface/DBUserInterfaceToolkit.h index 6ae46a7..e4019c5 100644 --- a/DBDebugToolkit/Classes/UserInterface/DBUserInterfaceToolkit.h +++ b/DBDebugToolkit/Classes/UserInterface/DBUserInterfaceToolkit.h @@ -67,11 +67,6 @@ extern NSString *const DBUserInterfaceToolkitColorizedViewBordersChangedNotifica */ - (NSString *)viewControllerHierarchy; -/** - Returns the text containing all the available fonts. - */ -- (NSString *)fontFamilies; - ///------------------------------------ /// @name UIDebuggingInformationOverlay ///------------------------------------ diff --git a/DBDebugToolkit/Classes/UserInterface/DBUserInterfaceToolkit.m b/DBDebugToolkit/Classes/UserInterface/DBUserInterfaceToolkit.m index eae3bcf..71be42b 100644 --- a/DBDebugToolkit/Classes/UserInterface/DBUserInterfaceToolkit.m +++ b/DBDebugToolkit/Classes/UserInterface/DBUserInterfaceToolkit.m @@ -84,22 +84,6 @@ - (NSString *)viewControllerHierarchy { return ((NSString *(*)(id, SEL))[rootViewController methodForSelector:selector])(rootViewController, selector); } -- (NSString *)fontFamilies { - NSMutableArray *fontFamilyDescriptions = [NSMutableArray array]; - NSArray *sortedFontFamilyNames = [[UIFont familyNames] sortedArrayUsingSelector:@selector(localizedCaseInsensitiveCompare:)]; - for (NSString *fontFamilyName in sortedFontFamilyNames) { - NSMutableArray *fontDescriptions = [NSMutableArray array]; - NSArray *fontNames = [UIFont fontNamesForFamilyName:fontFamilyName]; - NSArray *sortedFontNames = [fontNames sortedArrayUsingSelector:@selector(localizedCaseInsensitiveCompare:)]; - for (NSString *fontName in sortedFontNames) { - [fontDescriptions addObject:[NSString stringWithFormat:@"\t- %@", fontName]]; - } - NSString *fontDescriptionsString = [fontDescriptions componentsJoinedByString:@"\n"]; - [fontFamilyDescriptions addObject:[NSString stringWithFormat:@" • %@:\n%@", fontFamilyName, fontDescriptionsString]]; - } - return [fontFamilyDescriptions componentsJoinedByString:@"\n\n"]; -} - #pragma mark - Handling flags - (void)setSlowAnimationsEnabled:(BOOL)slowAnimationsEnabled { diff --git a/DBDebugToolkit/Resources/DBCrashReportDetailsTableViewController.storyboard b/DBDebugToolkit/Resources/DBCrashReportDetailsTableViewController.storyboard new file mode 100644 index 0000000..546f739 --- /dev/null +++ b/DBDebugToolkit/Resources/DBCrashReportDetailsTableViewController.storyboard @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/DBDebugToolkit/Resources/DBCrashReportsTableViewController.storyboard b/DBDebugToolkit/Resources/DBCrashReportsTableViewController.storyboard new file mode 100644 index 0000000..8089df7 --- /dev/null +++ b/DBDebugToolkit/Resources/DBCrashReportsTableViewController.storyboard @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/DBDebugToolkit/Resources/DBFontFamiliesTableViewController.storyboard b/DBDebugToolkit/Resources/DBFontFamiliesTableViewController.storyboard new file mode 100644 index 0000000..d43bf4e --- /dev/null +++ b/DBDebugToolkit/Resources/DBFontFamiliesTableViewController.storyboard @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/DBDebugToolkit/Resources/DBFontPreviewViewController.storyboard b/DBDebugToolkit/Resources/DBFontPreviewViewController.storyboard new file mode 100644 index 0000000..0de50c6 --- /dev/null +++ b/DBDebugToolkit/Resources/DBFontPreviewViewController.storyboard @@ -0,0 +1,135 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Lorem ipsum dolor sit er elit lamet, consectetaur cillium adipisicing pecu, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Nam liber te conscient to factor tum poen legum odioque civiuda. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/DBDebugToolkit/Resources/DBImageViewViewController.storyboard b/DBDebugToolkit/Resources/DBImageViewViewController.storyboard new file mode 100644 index 0000000..0f2dba9 --- /dev/null +++ b/DBDebugToolkit/Resources/DBImageViewViewController.storyboard @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/DBDebugToolkit/Resources/DBMenuTableViewController.storyboard b/DBDebugToolkit/Resources/DBMenuTableViewController.storyboard index e4c1a30..b7e1576 100644 --- a/DBDebugToolkit/Resources/DBMenuTableViewController.storyboard +++ b/DBDebugToolkit/Resources/DBMenuTableViewController.storyboard @@ -1,11 +1,11 @@ - + - + @@ -21,10 +21,10 @@ - + - + - + - + - + - + - + - + - + - + - + - + + + + + + + + + + + + + + + + + + + - + - + - + - + - + - +