Add a UIActivity subclass and the option to invoke compose view from anywhere modally

This commit is contained in:
Andreas Linde 2012-10-16 01:50:21 +02:00
parent 795381d72f
commit ca08b982c3
12 changed files with 247 additions and 30 deletions

View File

@ -0,0 +1,18 @@
//
// BITFeedbackActivity.h
// HockeySDK
//
// Created by Andreas Linde on 15.10.12.
//
//
#import <UIKit/UIKit.h>
#import "BITFeedbackComposeViewControllerDelegate.h"
@interface BITFeedbackActivity : UIActivity <BITFeedbackComposeViewControllerDelegate>
@property (nonatomic, retain) UIImage *shareImage;
@property (nonatomic, retain) NSString *shareString;
@end

View File

@ -0,0 +1,79 @@
//
// BITFeedbackActivity.m
// HockeySDK
//
// Created by Andreas Linde on 15.10.12.
//
//
#import "BITFeedbackActivity.h"
#import "HockeySDKPrivate.h"
#import "HockeySDK.h"
#import "BITFeedbackManagerPrivate.h"
@implementation BITFeedbackActivity
- (NSString *)activityType {
return @"UIActivityTypePostToHockeySDKFeedback";
}
- (NSString *)activityTitle {
return @"Feedback";
}
- (UIImage *)activityImage {
return [UIImage imageNamed:@"instagram.png"];
}
- (BOOL)canPerformWithActivityItems:(NSArray *)activityItems {
if ([BITHockeyManager sharedHockeyManager].disableFeedbackManager) return NO;
// we can present the user data screen on top of the compose screen
// so for now only allow this if all required user data is available
BITFeedbackManager *feedbackManager = [BITHockeyManager sharedHockeyManager].feedbackManager;
if ([feedbackManager askManualUserDataAvailable] &&
([feedbackManager requireManualUserDataMissing])
)
return NO;
for (UIActivityItemProvider *item in activityItems) {
if ([item isKindOfClass:[UIImage class]]) {
return YES;
} else if ([item isKindOfClass:[NSString class]]) {
return YES;
} else if ([item isKindOfClass:[NSString class]]) {
return YES;
}
}
return NO;
}
- (void)prepareWithActivityItems:(NSArray *)activityItems {
for (id item in activityItems) {
if ([item isKindOfClass:[UIImage class]]) {
self.shareImage = item;
} else if ([item isKindOfClass:[NSString class]]) {
self.shareString = [(self.shareString ? self.shareString : @"") stringByAppendingFormat:@"%@%@",(self.shareString ? @" " : @""),item];
} else if ([item isKindOfClass:[NSURL class]]) {
self.shareString = [(self.shareString ? self.shareString : @"") stringByAppendingFormat:@"%@%@",(self.shareString ? @" " : @""),[(NSURL *)item absoluteString]];
} else {
BITHockeyLog(@"Unknown item type %@", item);
}
}
}
- (UIViewController *)activityViewController {
// TODO: return compose controller with activity content added
BITFeedbackComposeViewController *composeViewController = [[BITHockeyManager sharedHockeyManager].feedbackManager feedbackComposeViewControllerWithDelegate:self];
composeViewController.modalPresentationStyle = UIModalPresentationFormSheet;
return composeViewController;
}
-(void)feedbackComposeViewControllerDidFinish:(BITFeedbackComposeViewController *)composeViewController {
[self activityDidFinish:YES];
}
@end

View File

@ -29,8 +29,14 @@
#import <UIKit/UIKit.h>
#import "BITFeedbackComposeViewControllerDelegate.h"
@interface BITFeedbackComposeViewController : UIViewController <UITextViewDelegate>
@property (nonatomic, assign) id<BITFeedbackComposeViewControllerDelegate> delegate;
- (id)init;
- (id)initWithDelegate:(id<BITFeedbackComposeViewControllerDelegate>)delegate;
@end

View File

@ -57,33 +57,70 @@
if (self) {
self.title = BITHockeyLocalizedString(@"HockeyFeedbackComposeTitle");
blockUserDataScreen = NO;
_delegate = nil;
_manager = [BITHockeyManager sharedHockeyManager].feedbackManager;
}
return self;
}
- (id)initWithDelegate:(id<BITFeedbackComposeViewControllerDelegate>)delegate {
self = [self init];
if (self) {
_delegate = delegate;
}
return self;
}
#pragma mark - View lifecycle
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor whiteColor];
CGFloat yPos = 0;
// Do any additional setup after loading the view.
self.navigationItem.leftBarButtonItem = [[[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemCancel
target:self
action:@selector(dismissAction:)] autorelease];
self.navigationItem.rightBarButtonItem = [[[UIBarButtonItem alloc] initWithTitle:BITHockeyLocalizedString(@"HockeyFeedbackComposeSend")
style:UIBarButtonItemStyleDone
target:self
action:@selector(sendAction:)] autorelease];
// when being used inside an activity, we don't have a navigation controller embedded
if (!self.navigationController) {
UINavigationBar *navigationBar = [[[UINavigationBar alloc] initWithFrame:CGRectMake(self.view.bounds.origin.x, self.view.bounds.origin.y, self.view.bounds.size.width, 44)] autorelease];
navigationBar.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleBottomMargin;
[self.view addSubview:navigationBar];
[navigationBar sizeToFit];
yPos = navigationBar.frame.size.height;
UIBarButtonItem *cancelItem = [[[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemCancel
target:self
action:@selector(dismissAction:)] autorelease];
UIBarButtonItem *saveItem = [[[UIBarButtonItem alloc] initWithTitle:BITHockeyLocalizedString(@"HockeyFeedbackComposeSend")
style:UIBarButtonItemStyleDone
target:self
action:@selector(sendAction:)] autorelease];
UINavigationItem *navigationItem = [[[UINavigationItem alloc] initWithTitle:BITHockeyLocalizedString(@"HockeyFeedbackComposeTitle")] autorelease];
navigationItem.leftBarButtonItem = cancelItem;
navigationItem.rightBarButtonItem = saveItem;
[navigationBar pushNavigationItem:navigationItem animated:NO];
} else {
// Do any additional setup after loading the view.
self.navigationItem.leftBarButtonItem = [[[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemCancel
target:self
action:@selector(dismissAction:)] autorelease];
self.navigationItem.rightBarButtonItem = [[[UIBarButtonItem alloc] initWithTitle:BITHockeyLocalizedString(@"HockeyFeedbackComposeSend")
style:UIBarButtonItemStyleDone
target:self
action:@selector(sendAction:)] autorelease];
}
// message input textfield
CGRect frame = CGRectZero;
if (UI_USER_INTERFACE_IDIOM() != UIUserInterfaceIdiomPad) {
frame = CGRectMake(0, 0, self.view.bounds.size.width, 200);
frame = CGRectMake(0, yPos, self.view.bounds.size.width, 200-yPos);
} else {
frame = CGRectMake(0, 0, self.view.bounds.size.width, self.view.bounds.size.height);
frame = CGRectMake(0, yPos, self.view.bounds.size.width, self.view.bounds.size.height-yPos);
}
self.textView = [[[UITextView alloc] initWithFrame:frame] autorelease];
self.textView.font = [UIFont systemFontOfSize:17];
@ -100,6 +137,8 @@
[super viewWillAppear:animated];
self.navigationItem.rightBarButtonItem.enabled = NO;
[[UIApplication sharedApplication] setStatusBarStyle:(self.navigationController.navigationBar.barStyle == UIBarStyleDefault) ? UIStatusBarStyleDefault : UIStatusBarStyleBlackOpaque];
}
- (void)viewDidAppear:(BOOL)animated {
@ -129,6 +168,14 @@
#pragma mark - Private methods
- (void)dismiss {
if (self.delegate && [self.delegate respondsToSelector:@selector(feedbackComposeViewControllerDidFinish:)]) {
[self.delegate feedbackComposeViewControllerDidFinish:self];
} else {
[self dismissModalViewControllerAnimated:YES];
}
}
- (void)setUserDataAction {
BITFeedbackUserDataViewController *userController = [[[BITFeedbackUserDataViewController alloc] initWithStyle:UITableViewStyleGrouped] autorelease];
userController.delegate = self;
@ -139,7 +186,7 @@
}
- (void)dismissAction:(id)sender {
[self dismissModalViewControllerAnimated:YES];
[self dismiss];
}
- (void)sendAction:(id)sender {
@ -150,7 +197,7 @@
[self.manager submitMessageWithText:text];
[self dismissModalViewControllerAnimated:YES];
[self dismiss];
}

View File

@ -0,0 +1,31 @@
//
// BITFeedbackComposeViewControllerDelegate.h
// HockeySDK
//
// Created by Andreas Linde on 15.10.12.
//
//
#import <Foundation/Foundation.h>
@class BITFeedbackComposeViewController;
@protocol BITFeedbackComposeViewControllerDelegate <NSObject>
@optional
///-----------------------------------------------------------------------------
/// @name View Controller Management
///-----------------------------------------------------------------------------
/**
Invoked once the compose screen is finished via send or cancel
If this is implemented, it's the responsibility of this method to dismiss the presented
`BITFeedbackComposeViewController`
@param composeViewController The `BITFeedbackComposeViewController` instance invoking this delegate
*/
- (void)feedbackComposeViewControllerDidFinish:(BITFeedbackComposeViewController *)composeViewController;
@end

View File

@ -72,9 +72,25 @@ typedef enum {
Create an feedback list view
@param modal Return a view ready for modal presentation with integrated navigation bar
@return BITFeedbackListViewController The update user interface view controller,
@return `BITFeedbackListViewController` The feedback list view controller,
e.g. to push it onto a navigation stack.
*/
- (BITFeedbackListViewController *)feedbackListViewController:(BOOL)modal;
/**
Present the modal feedback compose message user interface.
*/
- (void)showFeedbackComposeView;
/**
Create an feedback compose view
@param modal Return a view ready for modal presentation with integrated navigation bar
@return `BITFeedbackComposeViewController` The compose feedback view controller,
e.g. to push it onto a navigation stack.
*/
- (BITFeedbackComposeViewController *)feedbackComposeViewControllerWithDelegate:(id<BITFeedbackComposeViewControllerDelegate>)delegate;
@end

View File

@ -177,6 +177,20 @@
}
- (BITFeedbackComposeViewController *)feedbackComposeViewControllerWithDelegate:(id<BITFeedbackComposeViewControllerDelegate>)delegate {
return [[[BITFeedbackComposeViewController alloc] initWithDelegate:delegate] autorelease];
}
- (void)showFeedbackComposeView {
if (_currentFeedbackComposeViewController) {
BITHockeyLog(@"INFO: update view already visible, aborting");
return;
}
[self showView:[self feedbackComposeViewControllerWithDelegate:nil]];
}
#pragma mark - Manager Control
- (void)startManager {

View File

@ -137,7 +137,7 @@
return visibleWindow;
}
- (void)showView:(BITHockeyBaseViewController *)viewController {
- (void)showView:(UIViewController *)viewController {
UIViewController *parentViewController = nil;
if ([[BITHockeyManager sharedHockeyManager].delegate respondsToSelector:@selector(viewControllerForHockeyManager:componentManager:)]) {
@ -179,14 +179,16 @@
_navController.modalPresentationStyle = UIModalPresentationFormSheet;
}
viewController.modalAnimated = YES;
if ([viewController isKindOfClass:[BITHockeyBaseViewController class]])
[(BITHockeyBaseViewController *)viewController setModalAnimated:YES];
[parentViewController presentModalViewController:_navController animated:YES];
} else {
// if not, we add a subview to the window. A bit hacky but should work in most circumstances.
// Also, we don't get a nice animation for free, but hey, this is for beta not production users ;)
BITHockeyLog(@"INFO: No rootViewController found, using UIWindow-approach: %@", visibleWindow);
viewController.modalAnimated = NO;
if ([viewController isKindOfClass:[BITHockeyBaseViewController class]])
[(BITHockeyBaseViewController *)viewController setModalAnimated:NO];
[visibleWindow addSubview:_navController.view];
}
}

View File

@ -26,7 +26,7 @@
- (NSString *)executableUUID;
- (UIWindow *)findVisibleWindow;
- (void)showView:(BITHockeyBaseViewController *)viewController;
- (void)showView:(UIViewController *)viewController;
- (NSData *)appendPostValue:(NSString *)value forKey:(NSString *)key;

View File

@ -50,17 +50,7 @@
- (void)onDismissModal:(id)sender {
if (self.modal) {
// Note that as of 5.0, parentViewController will no longer return the presenting view controller
SEL presentingViewControllerSelector = NSSelectorFromString(@"presentingViewController");
UIViewController *presentingViewController = nil;
if ([self respondsToSelector:presentingViewControllerSelector]) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
presentingViewController = [self performSelector:presentingViewControllerSelector];
#pragma clang diagnostic pop
} else {
presentingViewController = [self parentViewController];
}
UIViewController *presentingViewController = [self presentingViewController];
// If there is no presenting view controller just remove view
if (presentingViewController && self.modalAnimated) {

View File

@ -41,7 +41,9 @@
#import "BITUpdateViewController.h"
#import "BITFeedbackManager.h"
#import "BITFeedbackActivity.h"
#import "BITFeedbackComposeViewController.h"
#import "BITFeedbackComposeViewControllerDelegate.h"
#import "BITFeedbackListViewController.h"

View File

@ -34,7 +34,7 @@
/* Begin PBXBuildFile section */
1E27EF2515BB5033000AE995 /* HockeySDK.strings in Resources */ = {isa = PBXBuildFile; fileRef = 1E59555F15B6F80E00A03429 /* HockeySDK.strings */; };
1E49A43C1612223B00463151 /* BITFeedbackComposeViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = 1E49A42D1612223B00463151 /* BITFeedbackComposeViewController.h */; };
1E49A43C1612223B00463151 /* BITFeedbackComposeViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = 1E49A42D1612223B00463151 /* BITFeedbackComposeViewController.h */; settings = {ATTRIBUTES = (Public, ); }; };
1E49A43F1612223B00463151 /* BITFeedbackComposeViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 1E49A42E1612223B00463151 /* BITFeedbackComposeViewController.m */; };
1E49A4421612223B00463151 /* BITFeedbackListViewCell.h in Headers */ = {isa = PBXBuildFile; fileRef = 1E49A42F1612223B00463151 /* BITFeedbackListViewCell.h */; };
1E49A4451612223B00463151 /* BITFeedbackListViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 1E49A4301612223B00463151 /* BITFeedbackListViewCell.m */; };
@ -93,6 +93,9 @@
1E754E601621FBB70070AB92 /* BITCrashReportTextFormatter.h in Headers */ = {isa = PBXBuildFile; fileRef = 1E754E5A1621FBB70070AB92 /* BITCrashReportTextFormatter.h */; };
1E754E611621FBB70070AB92 /* BITCrashReportTextFormatter.m in Sources */ = {isa = PBXBuildFile; fileRef = 1E754E5B1621FBB70070AB92 /* BITCrashReportTextFormatter.m */; };
1EC69F601615001500808FD9 /* BITHockeyManagerPrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = 1EC69F5D1615001500808FD9 /* BITHockeyManagerPrivate.h */; };
1EF95CA6162CB037000AE3AD /* BITFeedbackActivity.h in Headers */ = {isa = PBXBuildFile; fileRef = 1EF95CA4162CB036000AE3AD /* BITFeedbackActivity.h */; settings = {ATTRIBUTES = (Public, ); }; };
1EF95CA7162CB037000AE3AD /* BITFeedbackActivity.m in Sources */ = {isa = PBXBuildFile; fileRef = 1EF95CA5162CB036000AE3AD /* BITFeedbackActivity.m */; };
1EF95CAA162CB314000AE3AD /* BITFeedbackComposeViewControllerDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = 1EF95CA9162CB313000AE3AD /* BITFeedbackComposeViewControllerDelegate.h */; settings = {ATTRIBUTES = (Public, ); }; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@ -187,6 +190,9 @@
1E754E5B1621FBB70070AB92 /* BITCrashReportTextFormatter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BITCrashReportTextFormatter.m; sourceTree = "<group>"; };
1EC69F5D1615001500808FD9 /* BITHockeyManagerPrivate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BITHockeyManagerPrivate.h; sourceTree = "<group>"; };
1EDA60CF15C2C1450032D10B /* HockeySDK-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "HockeySDK-Info.plist"; sourceTree = "<group>"; };
1EF95CA4162CB036000AE3AD /* BITFeedbackActivity.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BITFeedbackActivity.h; sourceTree = "<group>"; };
1EF95CA5162CB036000AE3AD /* BITFeedbackActivity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BITFeedbackActivity.m; sourceTree = "<group>"; };
1EF95CA9162CB313000AE3AD /* BITFeedbackComposeViewControllerDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BITFeedbackComposeViewControllerDelegate.h; sourceTree = "<group>"; };
E400561D148D79B500EB22B9 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; };
E41EB465148D7BF50015DEDC /* BITHockeyManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BITHockeyManager.h; sourceTree = "<group>"; };
E41EB466148D7BF50015DEDC /* BITHockeyManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BITHockeyManager.m; sourceTree = "<group>"; };
@ -267,12 +273,15 @@
1E49A4371612223B00463151 /* BITFeedbackMessage.m */,
1E49A42D1612223B00463151 /* BITFeedbackComposeViewController.h */,
1E49A42E1612223B00463151 /* BITFeedbackComposeViewController.m */,
1EF95CA9162CB313000AE3AD /* BITFeedbackComposeViewControllerDelegate.h */,
1E49A4381612223B00463151 /* BITFeedbackUserDataViewController.h */,
1E49A4391612223B00463151 /* BITFeedbackUserDataViewController.m */,
1E49A42F1612223B00463151 /* BITFeedbackListViewCell.h */,
1E49A4301612223B00463151 /* BITFeedbackListViewCell.m */,
1E49A4311612223B00463151 /* BITFeedbackListViewController.h */,
1E49A4321612223B00463151 /* BITFeedbackListViewController.m */,
1EF95CA4162CB036000AE3AD /* BITFeedbackActivity.h */,
1EF95CA5162CB036000AE3AD /* BITFeedbackActivity.m */,
1E49A4331612223B00463151 /* BITFeedbackManager.h */,
1E49A4341612223B00463151 /* BITFeedbackManager.m */,
1E49A4351612223B00463151 /* BITFeedbackManagerPrivate.h */,
@ -390,6 +399,8 @@
1E49A4481612223B00463151 /* BITFeedbackListViewController.h in Headers */,
1E49A47F1612226D00463151 /* BITUpdateViewController.h in Headers */,
1E49A43C1612223B00463151 /* BITFeedbackComposeViewController.h in Headers */,
1EF95CAA162CB314000AE3AD /* BITFeedbackComposeViewControllerDelegate.h in Headers */,
1EF95CA6162CB037000AE3AD /* BITFeedbackActivity.h in Headers */,
1E49A4421612223B00463151 /* BITFeedbackListViewCell.h in Headers */,
1E49A4541612223B00463151 /* BITFeedbackManagerPrivate.h in Headers */,
1E49A4571612223B00463151 /* BITFeedbackMessage.h in Headers */,
@ -568,6 +579,7 @@
1E49A4DB161222D400463151 /* HockeySDKPrivate.m in Sources */,
1E754E5D1621FBB70070AB92 /* BITCrashManager.m in Sources */,
1E754E611621FBB70070AB92 /* BITCrashReportTextFormatter.m in Sources */,
1EF95CA7162CB037000AE3AD /* BITFeedbackActivity.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};