├── doorbell-Prefix.pch ├── .gitignore ├── Doorbell.h ├── classes ├── DoorbellDialog.h └── DoorbellDialog.m ├── LICENSE ├── README.md └── Doorbell.m /doorbell-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header for all source files of the 'Doorbell' target in the 'Doorbell' project 3 | // 4 | 5 | #ifdef __OBJC__ 6 | #import 7 | #endif 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | build/ 4 | *.pbxuser 5 | !default.pbxuser 6 | *.mode1v3 7 | !default.mode1v3 8 | *.mode2v3 9 | !default.mode2v3 10 | *.perspectivev3 11 | !default.perspectivev3 12 | xcuserdata 13 | *.xccheckout 14 | *.moved-aside 15 | DerivedData 16 | *.hmap 17 | *.ipa 18 | *.xcuserstate 19 | 20 | # CocoaPods 21 | # 22 | # We recommend against adding the Pods directory to your .gitignore. However 23 | # you should judge for yourself, the pros and cons are mentioned at: 24 | # http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control 25 | # 26 | # Pods/ 27 | -------------------------------------------------------------------------------- /Doorbell.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | 4 | typedef void (^DoorbellCompletionBlock)(NSError *error, BOOL isCancelled); 5 | 6 | 7 | @interface Doorbell : NSObject 8 | 9 | @property (strong, nonatomic) NSString *apiKey; 10 | @property (strong, nonatomic) NSString *appID; 11 | @property (strong, nonatomic) NSString *email; 12 | @property (assign, nonatomic) BOOL showEmail; 13 | @property (assign, nonatomic) BOOL showPoweredBy; 14 | 15 | - (id)initWithApiKey:(NSString *)apiKey appId:(NSString *)appID; 16 | 17 | + (Doorbell*)doorbellWithApiKey:(NSString *)apiKey appId:(NSString *)appID; 18 | 19 | - (void)showFeedbackDialogInViewController:(UIViewController *)vc completion:(DoorbellCompletionBlock)completion; 20 | 21 | @end 22 | -------------------------------------------------------------------------------- /classes/DoorbellDialog.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface DoorbellDialog : UIView 4 | 5 | @property (readonly, nonatomic) NSString *bodyText; 6 | @property (strong, nonatomic) NSString *email; 7 | @property (assign, nonatomic) BOOL showEmail; 8 | @property (assign, nonatomic) BOOL showPoweredBy; 9 | @property (assign, nonatomic) BOOL sending; 10 | 11 | @property (strong, nonatomic) id delegate; 12 | 13 | - (id)initWithViewController:(UIViewController *)vc; 14 | 15 | - (void)highlightEmailEmpty; 16 | - (void)highlightEmailInvalid; 17 | - (void)highlightMessageEmpty; 18 | 19 | @end 20 | 21 | 22 | @protocol DoorbellDialogDelegate 23 | 24 | - (void)dialogDidCancel:(DoorbellDialog*)dialog; 25 | - (void)dialogDidSend:(DoorbellDialog*)dialog; 26 | 27 | @end 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Doorbell iOS SDK 2 | 3 | The Doorbell iOS SDK. 4 | 5 | ## Full documentation 6 | 7 | You can view the full documentation here: https://doorbell.io/docs/ios 8 | 9 | ## Requirements 10 | 11 | Doorbell needs the ```QuartzCore.framework``` to be included in your project. 12 | 13 | You'll need [Automatic Reference Counting (ARC)](http://en.wikipedia.org/wiki/Automatic_Reference_Counting) enabled. 14 | 15 | ## Usage 16 | 17 | In the ViewController where you want to use Doorbell, you'll need to import the library using: 18 | 19 | ```objc 20 | #import "Doorbell.h" 21 | ``` 22 | 23 | Then when you want to show the dialog: 24 | 25 | ```objc 26 | NSString *appId = @"123"; 27 | NSString *appKey = @"xxxxxxxxxxxxxxxxxx"; 28 | 29 | Doorbell *feedback = [Doorbell doorbellWithApiKey:appKey appId:appId]; 30 | [feedback showFeedbackDialogInViewController:self completion:^(NSError *error, BOOL isCancelled) { 31 | if (error) { 32 | NSLog(@"%@", error.localizedDescription); 33 | } 34 | }]; 35 | ``` 36 | 37 | To pre-populate the email address (if the user is logged in for example): 38 | 39 | ```objc 40 | NSString *appId = @"123"; 41 | NSString *appKey = @"xxxxxxxxxxxxxxxxxx"; 42 | 43 | Doorbell *feedback = [Doorbell doorbellWithApiKey:appKey appId:appId]; 44 | feedback.showEmail = NO; 45 | feedback.email = @"email@example.com"; 46 | [feedback showFeedbackDialogInViewController:self completion:^(NSError *error, BOOL isCancelled) { 47 | if (error) { 48 | NSLog(@"%@", error.localizedDescription); 49 | } 50 | }]; 51 | ``` 52 | -------------------------------------------------------------------------------- /Doorbell.m: -------------------------------------------------------------------------------- 1 | #import "Doorbell.h" 2 | #import "DoorbellDialog.h" 3 | 4 | NSString * const EndpointTemplate = @"https://doorbell.io/api/applications/%@/%@?key=%@"; 5 | NSString * const UserAgent = @"Doorbell iOS SDK"; 6 | 7 | @interface Doorbell () 8 | 9 | @property (copy, nonatomic) DoorbellCompletionBlock block;//Block to give the result 10 | @property (strong, nonatomic) DoorbellDialog *dialog; 11 | 12 | @end 13 | 14 | @implementation Doorbell 15 | 16 | - (id)initWithApiKey:(NSString *)apiKey appId:(NSString *)appID 17 | { 18 | self = [super init]; 19 | if (self) { 20 | _showEmail = YES; 21 | _showPoweredBy = YES; 22 | self.apiKey = apiKey; 23 | self.appID = appID; 24 | } 25 | return self; 26 | } 27 | 28 | + (Doorbell*)doorbellWithApiKey:(NSString *)apiKey appId:(NSString *)appID 29 | { 30 | return [[[self class] alloc] initWithApiKey:apiKey appId:appID]; 31 | } 32 | 33 | - (BOOL)checkCredentials 34 | { 35 | if (self.appID.length == 0 || self.apiKey.length == 0) { 36 | NSError *error = [NSError errorWithDomain:@"doorbell.io" code:2 userInfo:@{NSLocalizedDescriptionKey: @"Doorbell. Credentials could not be founded (key, appID)."}]; 37 | self.block(error, YES); 38 | return NO; 39 | } 40 | 41 | return YES; 42 | } 43 | 44 | - (void)showFeedbackDialogInViewController:(UIViewController *)vc completion:(DoorbellCompletionBlock)completion 45 | { 46 | if (![self checkCredentials]) { 47 | return; 48 | } 49 | 50 | if (!vc || ![vc isKindOfClass:[UIViewController class]]) { 51 | NSError *error = [NSError errorWithDomain:@"doorbell.io" code:1 userInfo:@{NSLocalizedDescriptionKey: @"Doorbell needs a ViewController"}]; 52 | completion(error, YES); 53 | return; 54 | } 55 | 56 | self.block = completion; 57 | self.dialog = [[DoorbellDialog alloc] initWithViewController:vc]; 58 | self.dialog.delegate = self; 59 | self.dialog.showEmail = self.showEmail; 60 | self.dialog.email = self.email; 61 | self.dialog.showPoweredBy = self.showPoweredBy; 62 | [vc.view addSubview:self.dialog]; 63 | 64 | //Open - Request sent when the form is displayed to the user. 65 | [self sendOpen]; 66 | } 67 | 68 | - (void)showFeedbackDialogWithCompletionBlock:(DoorbellCompletionBlock)completion 69 | { 70 | self.block = completion; 71 | UIWindow *currentWindow = [UIApplication sharedApplication].keyWindow; 72 | self.dialog = [[DoorbellDialog alloc] initWithFrame:currentWindow.frame]; 73 | self.dialog.delegate = self; 74 | self.dialog.showEmail = self.showEmail; 75 | self.dialog.email = self.email; 76 | self.dialog.showPoweredBy = self.showPoweredBy; 77 | [currentWindow addSubview:self.dialog]; 78 | } 79 | 80 | #pragma mark - Selectors 81 | 82 | - (void)fieldError:(NSString*)validationError 83 | { 84 | if ([validationError hasPrefix:@"Your email address is required"]) { 85 | [self.dialog highlightEmailEmpty]; 86 | } 87 | else if ([validationError hasPrefix:@"Invalid email address"]) { 88 | [self.dialog highlightEmailInvalid]; 89 | } 90 | else { 91 | [self.dialog highlightMessageEmpty]; 92 | } 93 | 94 | self.dialog.sending = NO; 95 | } 96 | 97 | - (void)finish 98 | { 99 | [self.dialog removeFromSuperview]; 100 | self.block(nil, NO); 101 | } 102 | 103 | #pragma mark - Dialog delegate 104 | 105 | - (void)dialogDidCancel:(DoorbellDialog*)dialog 106 | { 107 | [dialog removeFromSuperview]; 108 | self.block(nil, YES); 109 | } 110 | 111 | - (void)dialogDidSend:(DoorbellDialog*)dialog 112 | { 113 | self.dialog.sending = YES; 114 | [self sendSubmit:dialog.bodyText email:dialog.email]; 115 | // [dialog removeFromSuperview]; 116 | // self.block(nil, YES); 117 | } 118 | 119 | #pragma mark - Endpoints 120 | 121 | - (void)sendOpen 122 | { 123 | if (![self checkCredentials]) { 124 | return; 125 | } 126 | 127 | NSString *query = [NSString stringWithFormat:EndpointTemplate, self.appID, @"open", self.apiKey]; 128 | NSURL *openURL = [NSURL URLWithString:query]; 129 | NSMutableURLRequest *openRequest = [NSMutableURLRequest requestWithURL:openURL]; 130 | [openRequest setHTTPMethod:@"POST"]; 131 | [openRequest addValue:UserAgent forHTTPHeaderField:@"User-Agent"]; 132 | [NSURLConnection sendAsynchronousRequest:openRequest 133 | queue:[NSOperationQueue mainQueue] 134 | completionHandler:^(NSURLResponse *r, NSData *d, NSError *e) { 135 | 136 | if ([r isKindOfClass:[NSHTTPURLResponse class]]) { 137 | NSHTTPURLResponse *httpResp = (id)r; 138 | if (httpResp.statusCode != 201) { 139 | NSLog(@"%d: There was an error trying to connect with doorbell. Open called failed", httpResp.statusCode); 140 | NSLog(@"%@", [NSString stringWithUTF8String:d.bytes]); 141 | } 142 | } 143 | }]; 144 | } 145 | 146 | - (void)sendSubmit:(NSString*)message email:(NSString*)email 147 | { 148 | NSString *query = [NSString stringWithFormat:EndpointTemplate, self.appID, @"submit", self.apiKey]; 149 | NSURL *submitURL = [NSURL URLWithString:query]; 150 | 151 | NSString *escapedEmail = (NSString *)CFBridgingRelease(CFURLCreateStringByAddingPercentEscapes( 152 | NULL, 153 | (CFStringRef)email, 154 | NULL, 155 | CFSTR("!*'();:@&=+$,/?%#[]"), 156 | kCFStringEncodingUTF8)); 157 | 158 | 159 | NSString *escapedMessage = (NSString *)CFBridgingRelease(CFURLCreateStringByAddingPercentEscapes( 160 | NULL, 161 | (CFStringRef)message, 162 | NULL, 163 | CFSTR("!*'();:@&=+$,/?%#[]"), 164 | kCFStringEncodingUTF8)); 165 | 166 | 167 | 168 | NSMutableURLRequest *submitRequest = [NSMutableURLRequest requestWithURL:submitURL]; 169 | [submitRequest setHTTPMethod:@"POST"]; 170 | [submitRequest addValue:UserAgent forHTTPHeaderField:@"User-Agent"]; 171 | NSString *postString = [NSString stringWithFormat:@"message=%@&email=%@", escapedMessage, escapedEmail]; 172 | [submitRequest setHTTPBody:[postString dataUsingEncoding:NSUTF8StringEncoding]]; 173 | 174 | [NSURLConnection sendAsynchronousRequest:submitRequest 175 | queue:[NSOperationQueue mainQueue] 176 | completionHandler:^(NSURLResponse *r, NSData *d, NSError *e) { 177 | if ([r isKindOfClass:[NSHTTPURLResponse class]]) { 178 | NSHTTPURLResponse *httpResp = (id)r; 179 | NSString *content = [NSString stringWithUTF8String:d.bytes]; 180 | NSLog(@"%d:%@", httpResp.statusCode, content); 181 | 182 | [self manageSubmitResponse:httpResp content:content]; 183 | } 184 | }]; 185 | 186 | } 187 | 188 | - (void)manageSubmitResponse:(NSHTTPURLResponse*)response content:(NSString*)content 189 | { 190 | switch (response.statusCode) { 191 | case 201: 192 | [self finish]; 193 | break; 194 | case 400: 195 | [self fieldError:content]; 196 | break; 197 | 198 | default: 199 | [self.dialog removeFromSuperview]; 200 | self.block([NSError errorWithDomain:@"doorbell.io" code:3 userInfo:@{NSLocalizedDescriptionKey:[NSString stringWithFormat:@"%d: HTTP unexpected\n%@", response.statusCode, content]}] , YES); 201 | break; 202 | } 203 | } 204 | @end 205 | -------------------------------------------------------------------------------- /classes/DoorbellDialog.m: -------------------------------------------------------------------------------- 1 | #import "DoorbellDialog.h" 2 | #import 3 | 4 | NSString * const DoorbellSite = @"http://doorbell.io"; 5 | 6 | @interface DoorbellDialog () 7 | 8 | @property (strong, nonatomic) UIView *boxView; 9 | @property (strong, nonatomic) UITextView *bodyView; 10 | @property (strong, nonatomic) UITextField *emailField; 11 | @property (strong, nonatomic) UIButton *cancelButton; 12 | @property (strong, nonatomic) UIButton *sendButton; 13 | @property (strong, nonatomic) UIView *poweredBy; 14 | @property (strong, nonatomic) UILabel *bodyPlaceHolderLabel; 15 | @property (strong, nonatomic) UILabel *sendingLabel; 16 | @property (strong, nonatomic) UIViewController *parentViewController; 17 | 18 | @property UIDeviceOrientation lastDeviceOrientation; 19 | 20 | @end 21 | 22 | @implementation DoorbellDialog 23 | 24 | - (id)initWithFrame:(CGRect)frame 25 | { 26 | self = [super initWithFrame:frame]; 27 | if (self) { 28 | _showEmail = YES; 29 | _showPoweredBy = YES; 30 | _sending = NO; 31 | // Initialization code 32 | self.backgroundColor = [UIColor colorWithWhite:0.2 alpha:0.4]; 33 | self.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleBottomMargin | UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleRightMargin; 34 | CGRect boxFrame; 35 | boxFrame.size = CGSizeMake(300, 255); 36 | boxFrame.origin.x = (frame.size.width/2) - boxFrame.size.width/2; 37 | boxFrame.origin.y = (frame.size.height - boxFrame.size.height) / 2; 38 | _boxView = [[UIView alloc] initWithFrame:boxFrame]; 39 | _boxView.backgroundColor = [UIColor whiteColor]; 40 | _boxView.layer.masksToBounds = NO; 41 | _boxView.layer.cornerRadius = 2.0f; 42 | //_boxView.layer.borderColor = [UIColor blackColor].CGColor; 43 | //_boxView.layer.borderWidth = 1.0f; 44 | _boxView.layer.shadowColor = [UIColor blackColor].CGColor; 45 | _boxView.layer.shadowRadius = 2.0f; 46 | _boxView.layer.shadowOffset = CGSizeMake(0, 1); 47 | _boxView.layer.shadowOpacity = 0.7f; 48 | 49 | [self createBoxSubviews]; 50 | 51 | [self addSubview:_boxView]; 52 | 53 | } 54 | return self; 55 | } 56 | 57 | - (id)initWithViewController:(UIViewController *)vc 58 | { 59 | CGRect frame = vc.view.bounds; 60 | self = [self initWithFrame:frame]; 61 | if (self) { 62 | self.parentViewController = vc; 63 | 64 | [[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications]; 65 | [[NSNotificationCenter defaultCenter] 66 | addObserver:self selector:@selector(orientationChanged:) 67 | name:UIDeviceOrientationDidChangeNotification 68 | object:[UIDevice currentDevice]]; 69 | } 70 | return self; 71 | } 72 | 73 | - (void)dealloc 74 | { 75 | [[UIDevice currentDevice] endGeneratingDeviceOrientationNotifications]; 76 | [[NSNotificationCenter defaultCenter] removeObserver:self name:UIDeviceOrientationDidChangeNotification object:nil]; 77 | } 78 | 79 | - (void) recalculateFrame 80 | { 81 | CGRect frame = self.parentViewController.view.bounds; 82 | 83 | CGRect boxFrame; 84 | boxFrame.size = CGSizeMake(300, 255); 85 | boxFrame.origin.x = (frame.size.width/2) - boxFrame.size.width/2; 86 | boxFrame.origin.y = (frame.size.height - boxFrame.size.height) / 2; 87 | 88 | _boxView.frame = boxFrame; 89 | } 90 | 91 | - (void) orientationChanged:(NSNotification *)note 92 | { 93 | // Hide the keyboard, so when the dialog is centered is looks OK 94 | UIDevice *device = [note object]; 95 | if ([device orientation] != UIDeviceOrientationFaceUp && 96 | [device orientation] != UIDeviceOrientationFaceDown && 97 | [device orientation] != UIDeviceOrientationUnknown && 98 | [device orientation] != self.lastDeviceOrientation ) 99 | { 100 | [[UIApplication sharedApplication] sendAction:@selector(resignFirstResponder) to:nil from:nil forEvent:nil]; 101 | self.lastDeviceOrientation = [device orientation]; 102 | [self recalculateFrame]; 103 | } 104 | } 105 | 106 | - (NSString*)bodyText 107 | { 108 | return [_bodyView.text stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; 109 | } 110 | 111 | - (void)setEmail:(NSString *)email 112 | { 113 | if (email.length > 0) { 114 | self.emailField.text = email; 115 | } 116 | } 117 | 118 | - (NSString*)email 119 | { 120 | return [self.emailField.text stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; 121 | } 122 | 123 | - (void)goToDoorbell:(id)sender 124 | { 125 | NSURL *doorbellURL = [NSURL URLWithString:DoorbellSite]; 126 | [[UIApplication sharedApplication] openURL:doorbellURL]; 127 | } 128 | 129 | - (void)send:(id)sender 130 | { 131 | if (self.bodyText.length == 0) { 132 | [self highlightMessageEmpty]; 133 | return; 134 | } 135 | 136 | if ([_delegate respondsToSelector:@selector(dialogDidSend:)]) { 137 | [_delegate dialogDidSend:self]; 138 | } 139 | } 140 | 141 | - (void)cancel:(id)sender 142 | { 143 | if ([_delegate respondsToSelector:@selector(dialogDidCancel:)]) { 144 | [_delegate dialogDidCancel:self]; 145 | } 146 | [[UIDevice currentDevice] endGeneratingDeviceOrientationNotifications]; 147 | [[NSNotificationCenter defaultCenter] removeObserver:self name:UIDeviceOrientationDidChangeNotification object:nil]; 148 | } 149 | 150 | - (void)highlightMessageEmpty 151 | { 152 | _bodyPlaceHolderLabel.text = NSLocalizedString(@"Please add some feedback", nil); 153 | _bodyPlaceHolderLabel.textColor = [UIColor redColor]; 154 | [_bodyView becomeFirstResponder]; 155 | } 156 | 157 | - (void)highlightEmailEmpty 158 | { 159 | _emailField.layer.borderColor = [UIColor redColor].CGColor; 160 | _emailField.placeholder = NSLocalizedString(@"Please add an email", nil); 161 | [_emailField becomeFirstResponder]; 162 | } 163 | 164 | - (void)highlightEmailInvalid 165 | { 166 | _emailField.layer.borderColor = [UIColor redColor].CGColor; 167 | [_emailField becomeFirstResponder]; 168 | } 169 | 170 | - (void)setShowEmail:(BOOL)showEmail 171 | { 172 | _showEmail = showEmail; 173 | _emailField.hidden = !showEmail; 174 | [self layoutSubviews]; 175 | } 176 | 177 | - (void)setShowPoweredBy:(BOOL)showPoweredBy 178 | { 179 | _showPoweredBy = showPoweredBy; 180 | _poweredBy.hidden = !showPoweredBy; 181 | [self layoutSubviews]; 182 | } 183 | 184 | - (UILabel*)sendingLabel 185 | { 186 | if (!_sendingLabel) { 187 | _sendingLabel = [[UILabel alloc] initWithFrame:_bodyView.frame]; 188 | _sendingLabel.font = [UIFont boldSystemFontOfSize:18.0f]; 189 | _sendingLabel.textAlignment = NSTextAlignmentCenter; 190 | _sendingLabel.textColor = [UIColor colorWithRed:91/255.0f green:192/255.0f blue:222/255.0f alpha:1.0f]; 191 | _sendingLabel.text = NSLocalizedString(@"Sending ...", nil); 192 | } 193 | 194 | return _sendingLabel; 195 | } 196 | 197 | - (void)setSending:(BOOL)sending 198 | { 199 | _sending = sending; 200 | _bodyView.hidden = sending; 201 | if (_showEmail) { 202 | _emailField.hidden = sending; 203 | } 204 | if (_showPoweredBy) { 205 | _poweredBy.hidden = sending; 206 | } 207 | _cancelButton.hidden = sending; 208 | _sendButton.hidden = sending; 209 | 210 | if (sending) { 211 | [_boxView addSubview:self.sendingLabel]; 212 | } 213 | else { 214 | [self.sendingLabel removeFromSuperview]; 215 | } 216 | } 217 | /* 218 | // Only override drawRect: if you perform custom drawing. 219 | // An empty implementation adversely affects performance during animation. 220 | - (void)drawRect:(CGRect)rect 221 | { 222 | // Drawing code 223 | } 224 | */ 225 | 226 | - (void)layoutSubviews 227 | { 228 | float offsetY = _bodyView.frame.origin.y + _bodyView.frame.size.height + 10.0f; 229 | if (_showEmail) { 230 | _emailField.frame = CGRectMake(10.0f, offsetY, 280.0f, 30.0f); 231 | offsetY += 40; 232 | } 233 | 234 | if (_showPoweredBy) { 235 | _poweredBy.frame = CGRectMake(10.0f, offsetY, 280.0f, 14.0f); 236 | offsetY += 24; 237 | } 238 | 239 | _cancelButton.frame = CGRectMake(0.0f, offsetY, 150.0f, 44.0f); 240 | _sendButton.frame = CGRectMake(150.0f, offsetY, 150.0f, 44.0f); 241 | 242 | CGRect frame = _boxView.frame; 243 | frame.size.height = offsetY + 44.0f; 244 | _boxView.frame = frame; 245 | 246 | [super layoutSubviews]; 247 | } 248 | 249 | - (void)createBoxSubviews 250 | { 251 | UIColor *brandColor = [UIColor colorWithRed:91/255.0f green:192/255.0f blue:222/255.0f alpha:1.0f]; 252 | 253 | UILabel *titleLabel = [[UILabel alloc] initWithFrame:CGRectMake(20.0f, 10.0f, 200.0f, 20.0f)]; 254 | titleLabel.text = NSLocalizedString(@"Feedback", nil); 255 | titleLabel.font = [UIFont boldSystemFontOfSize:18.0f]; 256 | titleLabel.textAlignment = NSTextAlignmentLeft; 257 | titleLabel.textColor = brandColor; 258 | 259 | [_boxView addSubview:titleLabel]; 260 | 261 | 262 | CALayer *line = [CALayer layer]; 263 | line.frame = CGRectMake(0, 35.0f, 300.0f, 2.0f); 264 | line.backgroundColor = brandColor.CGColor; 265 | [_boxView.layer addSublayer:line]; 266 | 267 | _bodyView = [[UITextView alloc] initWithFrame:CGRectMake(10.0f, 45.0f, 280.0f, 100)]; 268 | _bodyView.delegate = self; 269 | _bodyView.textColor = [UIColor darkTextColor]; 270 | _bodyView.font = [UIFont systemFontOfSize:16.0f]; 271 | _bodyView.dataDetectorTypes = UIDataDetectorTypeNone; 272 | _bodyView.layer.borderColor = brandColor.CGColor; 273 | _bodyView.layer.borderWidth = 1.0; 274 | _bodyView.keyboardAppearance = UIKeyboardAppearanceAlert; 275 | 276 | UIBarButtonItem *bodyDoneButton = [[UIBarButtonItem alloc] initWithTitle:NSLocalizedString(@"Done", nil) 277 | style:UIBarButtonItemStyleDone target:_bodyView action:@selector(resignFirstResponder)]; 278 | UIBarButtonItem *flexibleSpace = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:self action:nil]; 279 | UIToolbar *toolbar = [[UIToolbar alloc] initWithFrame:CGRectMake(0, 0, 320, 44)]; 280 | toolbar.items = [NSArray arrayWithObjects:flexibleSpace, bodyDoneButton, nil]; 281 | _bodyView.inputAccessoryView = toolbar; 282 | 283 | 284 | [_boxView addSubview:_bodyView]; 285 | 286 | _bodyPlaceHolderLabel = [[UILabel alloc] initWithFrame:CGRectMake(8.0f, 10.0f, 260.0f, 20.0f)]; 287 | _bodyPlaceHolderLabel.text = NSLocalizedString(@"What's on your mind", nil); 288 | _bodyPlaceHolderLabel.font = _bodyView.font; 289 | _bodyPlaceHolderLabel.textColor = [UIColor lightGrayColor]; 290 | _bodyPlaceHolderLabel.userInteractionEnabled = NO; 291 | [_bodyView addSubview:_bodyPlaceHolderLabel]; 292 | 293 | UIView *paddingView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 5, 20)]; 294 | 295 | 296 | _emailField = [[UITextField alloc] initWithFrame:CGRectMake(10.0f, 155.0f, 280.0f, 30.0f)]; 297 | _emailField.delegate = self; 298 | _emailField.placeholder = NSLocalizedString(@"Your email address", nil); 299 | _emailField.clearButtonMode = UITextFieldViewModeWhileEditing; 300 | _emailField.autocapitalizationType = UITextAutocapitalizationTypeNone; 301 | _emailField.borderStyle = UITextBorderStyleLine; 302 | _emailField.keyboardType = UIKeyboardTypeEmailAddress; 303 | _emailField.keyboardAppearance = UIKeyboardAppearanceAlert; 304 | _emailField.returnKeyType = UIReturnKeySend; 305 | _emailField.layer.masksToBounds = YES; 306 | _emailField.borderStyle = UITextBorderStyleNone; 307 | _emailField.leftView = paddingView; 308 | _emailField.leftViewMode = UITextFieldViewModeAlways; 309 | _emailField.layer.borderColor = brandColor.CGColor; 310 | _emailField.layer.borderWidth = 1.0; 311 | _emailField.contentVerticalAlignment = UIControlContentVerticalAlignmentCenter; 312 | 313 | 314 | UIBarButtonItem *emailDoneButton = [[UIBarButtonItem alloc] initWithTitle:NSLocalizedString(@"Done", nil) 315 | style:UIBarButtonItemStyleDone target:_emailField action:@selector(resignFirstResponder)]; 316 | UIBarButtonItem *emailFlexibleSpace = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:self action:nil]; 317 | UIToolbar *emailToolbar = [[UIToolbar alloc] initWithFrame:CGRectMake(0, 0, 320, 44)]; 318 | emailToolbar.items = [NSArray arrayWithObjects:emailFlexibleSpace, emailDoneButton, nil]; 319 | _emailField.inputAccessoryView = emailToolbar; 320 | 321 | [_boxView addSubview:_emailField]; 322 | 323 | _poweredBy = [[UIView alloc] initWithFrame:CGRectMake(10.0f, 193.0f, 280.0f, 14.0f)]; 324 | 325 | UILabel *powerByLabel = [[UILabel alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 80.0f, 14.0f)]; 326 | powerByLabel.text = NSLocalizedString(@"Powered by", nil); 327 | powerByLabel.font = [UIFont boldSystemFontOfSize:12.0f]; 328 | powerByLabel.textColor = [UIColor lightGrayColor]; 329 | [_poweredBy addSubview:powerByLabel]; 330 | 331 | UIButton *poweredByButton = [UIButton buttonWithType:UIButtonTypeCustom]; 332 | poweredByButton.frame = CGRectMake(42.0f, 0.0f, 120.0f, 14.0f); 333 | poweredByButton.titleLabel.font = [UIFont boldSystemFontOfSize:12.0f]; 334 | [poweredByButton setTitle:@"Doorbell.io" forState:UIControlStateNormal]; 335 | [poweredByButton setTitleColor:brandColor forState:UIControlStateNormal]; 336 | 337 | [poweredByButton addTarget:self action:@selector(goToDoorbell:) forControlEvents:UIControlEventTouchUpInside]; 338 | [_poweredBy addSubview:poweredByButton]; 339 | 340 | [_boxView addSubview:_poweredBy]; 341 | 342 | _cancelButton = [UIButton buttonWithType:UIButtonTypeCustom]; 343 | _cancelButton.frame = CGRectMake(0.0f, 211.0f, 150.0f, 44.0f); 344 | _cancelButton.titleLabel.font = [UIFont boldSystemFontOfSize:14.0f]; 345 | //_cancelButton.backgroundColor = [UIColor lightGrayColor]; 346 | [_cancelButton setTitle:NSLocalizedString(@"Cancel", nil) forState:UIControlStateNormal]; 347 | [_cancelButton setTitleColor:[UIColor grayColor] forState:UIControlStateNormal]; 348 | [_cancelButton setTitleColor:[UIColor whiteColor] forState:UIControlStateHighlighted]; 349 | 350 | [_cancelButton setBackgroundImage:[self imageWithColor:[UIColor lightGrayColor]] forState:UIControlStateHighlighted]; 351 | [_cancelButton addTarget:self action:@selector(cancel:) forControlEvents:UIControlEventTouchUpInside]; 352 | //_cancelButton.layer.borderColor = [UIColor lightGrayColor].CGColor; 353 | //_cancelButton.layer.borderWidth = .5f; 354 | 355 | 356 | _sendButton = [UIButton buttonWithType:UIButtonTypeCustom]; 357 | _sendButton.frame = CGRectMake(150.0f, 211.0f, 150.0f, 44.0f); 358 | _sendButton.titleLabel.font = [UIFont boldSystemFontOfSize:14.0f]; 359 | [_sendButton setTitle:NSLocalizedString(@"Send", nil) forState:UIControlStateNormal]; 360 | [_sendButton setTitleColor:[UIColor grayColor] forState:UIControlStateNormal]; 361 | [_sendButton setTitleColor:[UIColor whiteColor] forState:UIControlStateHighlighted]; 362 | 363 | [_sendButton setBackgroundImage:[self imageWithColor:[UIColor lightGrayColor]] forState:UIControlStateHighlighted]; 364 | [_sendButton addTarget:self action:@selector(send:) forControlEvents:UIControlEventTouchUpInside]; 365 | //_sendButton.layer.borderColor = [UIColor lightGrayColor].CGColor; 366 | //_sendButton.layer.borderWidth = .5f; 367 | 368 | [_boxView addSubview:_cancelButton]; 369 | [_boxView addSubview:_sendButton]; 370 | } 371 | 372 | - (UIImage *)imageWithColor:(UIColor *)color 373 | { 374 | CGRect rect = CGRectMake(0.0f, 0.0f, 1.0f, 1.0f); 375 | UIGraphicsBeginImageContext(rect.size); 376 | CGContextRef context = UIGraphicsGetCurrentContext(); 377 | 378 | CGContextSetFillColorWithColor(context, [color CGColor]); 379 | CGContextFillRect(context, rect); 380 | 381 | UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); 382 | UIGraphicsEndImageContext(); 383 | 384 | return image; 385 | } 386 | 387 | - (void)verticalOffsetBy:(NSInteger)y 388 | { 389 | _boxView.transform = CGAffineTransformIdentity; 390 | _boxView.transform = CGAffineTransformMakeTranslation(0, y); 391 | } 392 | 393 | #pragma mark - UITextView Delegate 394 | 395 | - (BOOL)textViewShouldBeginEditing:(UITextView *)textView 396 | { 397 | [self verticalOffsetBy:-80]; 398 | 399 | return YES; 400 | } 401 | 402 | -(void)textViewDidEndEditing:(UITextField *)textField 403 | { 404 | [self verticalOffsetBy:0]; 405 | } 406 | 407 | - (void)textViewDidChange:(UITextView *)textView 408 | { 409 | if (textView.text.length > 0) { 410 | self.bodyPlaceHolderLabel.hidden = YES; 411 | } 412 | else { 413 | self.bodyPlaceHolderLabel.hidden = NO; 414 | } 415 | } 416 | 417 | #pragma mark - UITextField Delegate 418 | 419 | - (BOOL)textFieldShouldBeginEditing:(UITextField *)textField 420 | { 421 | [self verticalOffsetBy:-150]; 422 | return YES; 423 | } 424 | 425 | -(void)textFieldDidEndEditing:(UITextField *)textField 426 | { 427 | [self verticalOffsetBy:0]; 428 | } 429 | 430 | - (BOOL)textFieldShouldReturn:(UITextField *)textField 431 | { 432 | [textField resignFirstResponder]; 433 | [self send:textField]; 434 | return NO; 435 | } 436 | 437 | @end 438 | --------------------------------------------------------------------------------