├── .gitignore ├── CHANGES.md ├── CYRKeyboardButton.podspec ├── CYRKeyboardButton ├── CYRKeyboardButton.h ├── CYRKeyboardButton.m ├── CYRKeyboardButtonView.h └── CYRKeyboardButtonView.m ├── Example ├── Example.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── Example.xccheckout │ └── xcuserdata │ │ └── GuestPW.xcuserdatad │ │ └── xcschemes │ │ ├── Example.xcscheme │ │ └── xcschememanagement.plist ├── Example │ ├── AppDelegate.h │ ├── AppDelegate.m │ ├── Base.lproj │ │ └── Main.storyboard │ ├── Example-Info.plist │ ├── Example-Prefix.pch │ ├── Images.xcassets │ │ ├── AppIcon.appiconset │ │ │ └── Contents.json │ │ └── LaunchImage.launchimage │ │ │ └── Contents.json │ ├── ViewController.h │ ├── ViewController.m │ ├── en.lproj │ │ └── InfoPlist.strings │ └── main.m ├── ExampleTests │ ├── ExampleTests-Info.plist │ ├── ExampleTests.m │ └── en.lproj │ │ └── InfoPlist.strings └── Vendor │ └── TurtleBezierPath │ ├── LICENSE │ ├── TurtleBezierPath.h │ └── TurtleBezierPath.m ├── LICENSE ├── README.md └── Screenshots └── CYRKeyboardButton.gif /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | .DS_Store 3 | build/ 4 | *.pbxuser 5 | !default.pbxuser 6 | *.mode1v3 7 | !default.mode1v3 8 | *.mode2v3 9 | !default.mode2v3 10 | *.perspectivev3 11 | !default.perspectivev3 12 | *.xcworkspace 13 | !default.xcworkspace 14 | xcuserdata 15 | profile 16 | *.moved-aside 17 | DerivedData 18 | .idea/ 19 | Seasonalysis.xcodeproj/project.xcworkspace/xcuserdata/roudeline.xcuserdatad/UserInterfaceState.xcuserstate 20 | -------------------------------------------------------------------------------- /CHANGES.md: -------------------------------------------------------------------------------- 1 | #CYRKeyboardButton Changelog 2 | 3 | ##0.5.3 (Monday, April 20th, 2015) 4 | * Added support for initialization via interface builder (Thanks @khaullen!) 5 | 6 | ##0.5.2 (Sunday, August 31st, 2014) 7 | * Adding notifications for when the expanded input view is shown or hidden. 8 | 9 | ##0.5.1 (Friday, August 1st, 2014) 10 | * Added support for UITextField (shouldChangeCharactersInRange:) and UITextView (shouldChangeTextInRange:) delegate methods. 11 | 12 | ##0.5.0 (Friday, August 1st, 2014) 13 | * Initial release 14 | -------------------------------------------------------------------------------- /CYRKeyboardButton.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = 'CYRKeyboardButton' 3 | s.version = '0.5.3' 4 | s.license = { :type => 'MIT', :file => 'LICENSE' } 5 | s.summary = 'A drop-in keyboard button that mimics the look, feel, and functionality of the native iOS keyboard buttons.' 6 | s.author = { 'Illya Busigin' => 'http://illyabusigin.com/' } 7 | s.source = { :git => 'https://github.com/illyabusigin/CYRKeyboardButton.git', :tag => '0.5.3' } 8 | s.homepage = 'https://github.com/illyabusigin/CYRKeyboardButton' 9 | s.platform = :ios 10 | s.source_files = 'CYRKeyboardButton' 11 | s.requires_arc = true 12 | s.ios.deployment_target = '7.0' 13 | s.dependency 'TurtleBezierPath' 14 | end 15 | -------------------------------------------------------------------------------- /CYRKeyboardButton/CYRKeyboardButton.h: -------------------------------------------------------------------------------- 1 | // 2 | // CYRKeyboardButton.h 3 | // 4 | // Created by Illya Busigin on 7/19/14. 5 | // Copyright (c) 2014 Cyrillian, Inc. 6 | // Portions Copyright (c) 2013 Nigel Timothy Barber (TurtleBezierPath) 7 | // 8 | // Distributed under MIT license. 9 | // Get the latest version from here: 10 | // 11 | // https://github.com/illyabusigin/CYRKeyboardButton 12 | // 13 | // The MIT License (MIT) 14 | // 15 | // Copyright (c) 2014 Cyrillian, Inc. 16 | // 17 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 18 | // this software and associated documentation files (the "Software"), to deal in 19 | // the Software without restriction, including without limitation the rights to 20 | // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 21 | // the Software, and to permit persons to whom the Software is furnished to do so, 22 | // subject to the following conditions: 23 | // 24 | // The above copyright notice and this permission notice shall be included in all 25 | // copies or substantial portions of the Software. 26 | // 27 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 28 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 29 | // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 30 | // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 31 | // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 32 | // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 33 | 34 | #import 35 | 36 | typedef NS_ENUM(NSUInteger, CYRKeyboardButtonPosition) { 37 | CYRKeyboardButtonPositionLeft, 38 | CYRKeyboardButtonPositionInner, 39 | CYRKeyboardButtonPositionRight, 40 | CYRKeyboardButtonPositionCount 41 | }; 42 | 43 | /** 44 | The style of the keyboard button. You use these constants to set the value of the keyboard button style. 45 | */ 46 | typedef NS_ENUM(NSUInteger, CYRKeyboardButtonStyle) { 47 | /** Keyboard buttons are styled like iPhone keyboard buttons. */ 48 | CYRKeyboardButtonStylePhone, 49 | /** Keyboard buttons are styled like iPad keyboard buttons. */ 50 | CYRKeyboardButtonStyleTablet 51 | }; 52 | 53 | /** 54 | Notifies observers that the keyboard button has been pressed. The affected button is stored in the object parameter of the notification. The userInfo dictionary contains the pressed key and can be accessed with the CYRKeyboardButtonKeyPressedKey key. 55 | */ 56 | extern NSString *const CYRKeyboardButtonPressedNotification; 57 | 58 | /** 59 | Notifies observers that the keyboard button has show the expanded input view. The affected button is stored in the object parameter of the notification. 60 | */ 61 | extern NSString *const CYRKeyboardButtonDidShowExpandedInputNotification; 62 | 63 | /** 64 | Notifies observers that the keyboard button has hidden the expanded input view. The affected button is stored in the object parameter of the notification. 65 | */ 66 | extern NSString *const CYRKeyboardButtonDidHideExpandedInputNotification; 67 | 68 | /** 69 | The key used to fetch the pressed key string from the userInfo dictionary returned when CYRKeyboardButtonPressedNotification is fired. 70 | */ 71 | extern NSString *const CYRKeyboardButtonKeyPressedKey; 72 | 73 | /** 74 | CYRKeyboardButton is a drop-in keyboard button that mimics the look, feel, and functionality of the native iOS keyboard buttons. This button is highly configurable via a variety of styling properties which conform to the UIAppearance protocol. 75 | */ 76 | @interface CYRKeyboardButton : UIControl 77 | 78 | /** 79 | The style of the keyboard button. This determines the basic visual appearance of the keyboard. 80 | @discussion The style value is automatically determined during initialization but can be overriden. 81 | */ 82 | @property (nonatomic, assign) CYRKeyboardButtonStyle style; 83 | 84 | 85 | // Styling 86 | 87 | /** 88 | The font associated with the keyboard button. 89 | @discussion This font only affects the keyboard button's standard view. 90 | */ 91 | @property (nonatomic, strong) UIFont *font UI_APPEARANCE_SELECTOR; 92 | 93 | /** 94 | The font associated with the keyboard button input options. 95 | */ 96 | @property (nonatomic, strong) UIFont *inputOptionsFont UI_APPEARANCE_SELECTOR; 97 | 98 | /** 99 | The default color of the keyboard button. 100 | */ 101 | @property (nonatomic, strong) UIColor *keyColor UI_APPEARANCE_SELECTOR; 102 | 103 | /** 104 | The text color of the keyboard button. 105 | @discussion This color affects both the standard and input option text. 106 | */ 107 | @property (nonatomic, strong) UIColor *keyTextColor UI_APPEARANCE_SELECTOR; 108 | 109 | /** 110 | The shadow color for the keyboard button. 111 | */ 112 | @property (nonatomic, strong) UIColor *keyShadowColor UI_APPEARANCE_SELECTOR; 113 | 114 | /** 115 | The highlighted background color of the keyboard button. 116 | */ 117 | @property (nonatomic, strong) UIColor *keyHighlightedColor UI_APPEARANCE_SELECTOR; 118 | 119 | /** 120 | The position of the keyboard button. This is used to determine where to place the popover key views and is automatically determined when the keyboard button is added to a view and update during layout changes. 121 | */ 122 | @property (nonatomic, readonly) CYRKeyboardButtonPosition position; 123 | 124 | // Configuration 125 | 126 | /** 127 | The string input for the keyboard button. This is the string that would be inserted upon a successful key press. 128 | */ 129 | @property (nonatomic, strong) NSString *input; 130 | 131 | /** 132 | An array of input option strings associated with the keybonard button. The user must tap and hold the keyboard button for 0.3 seconds before the input options will be displayed. 133 | @discussion Input options are automatically positioned based on the keyboard buttons position within its' superview. 134 | */ 135 | @property (nonatomic, strong) NSArray *inputOptions; 136 | 137 | /** 138 | An object that adopts the UITextInput protocol. When a key is pressed the key value is automatically inserted via the textInput object. 139 | @discussion If the textInput object is not the first responder no text will be inserted. 140 | */ 141 | @property (nonatomic, weak) id textInput; 142 | 143 | @end 144 | -------------------------------------------------------------------------------- /CYRKeyboardButton/CYRKeyboardButton.m: -------------------------------------------------------------------------------- 1 | // 2 | // CYRKeyboardButton.m 3 | // 4 | // Created by Illya Busigin on 7/19/14. 5 | // Copyright (c) 2014 Cyrillian, Inc. 6 | // Portions Copyright (c) 2013 Nigel Timothy Barber (TurtleBezierPath) 7 | // 8 | // Distributed under MIT license. 9 | // Get the latest version from here: 10 | // 11 | // https://github.com/illyabusigin/CYRKeyboardButton 12 | // 13 | // The MIT License (MIT) 14 | // 15 | // Copyright (c) 2014 Cyrillian, Inc. 16 | // 17 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 18 | // this software and associated documentation files (the "Software"), to deal in 19 | // the Software without restriction, including without limitation the rights to 20 | // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 21 | // the Software, and to permit persons to whom the Software is furnished to do so, 22 | // subject to the following conditions: 23 | // 24 | // The above copyright notice and this permission notice shall be included in all 25 | // copies or substantial portions of the Software. 26 | // 27 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 28 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 29 | // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 30 | // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 31 | // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 32 | // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 33 | 34 | #import "CYRKeyboardButton.h" 35 | #import "CYRKeyboardButtonView.h" 36 | 37 | NSString *const CYRKeyboardButtonPressedNotification = @"CYRKeyboardButtonPressedNotification"; 38 | NSString *const CYRKeyboardButtonDidShowExpandedInputNotification = @"CYRKeyboardButtonDidShowExpandedInputNotification"; 39 | NSString *const CYRKeyboardButtonDidHideExpandedInputNotification = @"CYRKeyboardButtonDidHideExpandedInputNotification"; 40 | NSString *const CYRKeyboardButtonKeyPressedKey = @"CYRKeyboardButtonKeyPressedKey"; 41 | 42 | @interface CYRKeyboardButton () 43 | 44 | @property (nonatomic, strong) UILabel *inputLabel; 45 | @property (nonatomic, strong) CYRKeyboardButtonView *buttonView; 46 | @property (nonatomic, strong) CYRKeyboardButtonView *expandedButtonView; 47 | 48 | @property (nonatomic, assign) CYRKeyboardButtonPosition position; 49 | 50 | // Input options state 51 | @property (nonatomic, strong) UILongPressGestureRecognizer *optionsViewRecognizer; 52 | @property (nonatomic, strong) UIPanGestureRecognizer *panGestureRecognizer; 53 | 54 | // Internal style 55 | @property (nonatomic, assign) CGFloat keyCornerRadius UI_APPEARANCE_SELECTOR; 56 | 57 | @end 58 | 59 | @implementation CYRKeyboardButton 60 | 61 | #pragma mark - UIView 62 | 63 | - (id)initWithFrame:(CGRect)frame 64 | { 65 | self = [super initWithFrame:frame]; 66 | if (self) { 67 | [self commonInit]; 68 | } 69 | 70 | return self; 71 | } 72 | 73 | - (instancetype)initWithCoder:(NSCoder *)aDecoder 74 | { 75 | self = [super initWithCoder:aDecoder]; 76 | if (self) { 77 | [self commonInit]; 78 | } 79 | return self; 80 | } 81 | 82 | - (void)commonInit 83 | { 84 | switch ([UIDevice currentDevice].userInterfaceIdiom) { 85 | case UIUserInterfaceIdiomPhone: 86 | _style = CYRKeyboardButtonStylePhone; 87 | break; 88 | 89 | case UIUserInterfaceIdiomPad: 90 | _style = CYRKeyboardButtonStyleTablet; 91 | break; 92 | 93 | default: 94 | break; 95 | } 96 | 97 | // Default appearance 98 | _font = [UIFont systemFontOfSize:22.f]; 99 | _inputOptionsFont = [UIFont systemFontOfSize:24.f]; 100 | _keyColor = [UIColor whiteColor]; 101 | _keyTextColor = [UIColor blackColor]; 102 | _keyShadowColor = [UIColor colorWithRed:136 / 255.f green:138 / 255.f blue:142 / 255.f alpha:1]; 103 | _keyHighlightedColor = [UIColor colorWithRed:213/255.f green:214/255.f blue:216/255.f alpha:1]; 104 | 105 | // Styling 106 | self.backgroundColor = [UIColor clearColor]; 107 | self.clipsToBounds = NO; 108 | self.layer.masksToBounds = NO; 109 | self.contentHorizontalAlignment = UIControlContentHorizontalAlignmentCenter; 110 | 111 | // State handling 112 | [self addTarget:self action:@selector(handleTouchDown) forControlEvents:UIControlEventTouchDown]; 113 | [self addTarget:self action:@selector(handleTouchUpInside) forControlEvents:UIControlEventTouchUpInside]; 114 | 115 | // Input label 116 | UILabel *inputLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, CGRectGetWidth(self.frame), CGRectGetHeight(self.frame))]; 117 | inputLabel.autoresizingMask = UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleHeight; 118 | inputLabel.textAlignment = NSTextAlignmentCenter; 119 | inputLabel.backgroundColor = [UIColor clearColor]; 120 | inputLabel.userInteractionEnabled = NO; 121 | inputLabel.textColor = _keyTextColor; 122 | inputLabel.font = _font; 123 | 124 | [self addSubview:inputLabel]; 125 | _inputLabel = inputLabel; 126 | 127 | [self updateDisplayStyle]; 128 | } 129 | 130 | - (void)didMoveToSuperview 131 | { 132 | [self updateButtonPosition]; 133 | } 134 | 135 | - (void)layoutSubviews 136 | { 137 | [super layoutSubviews]; 138 | 139 | [self setNeedsDisplay]; 140 | 141 | [self updateButtonPosition]; 142 | } 143 | 144 | #pragma mark - UIGestureRecognizerDelegate 145 | 146 | - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer 147 | { 148 | // Only allow simulateous recognition with our internal recognizers 149 | return (gestureRecognizer == _panGestureRecognizer || gestureRecognizer == _optionsViewRecognizer) && 150 | (otherGestureRecognizer == _panGestureRecognizer || otherGestureRecognizer == _optionsViewRecognizer); 151 | } 152 | 153 | #pragma mark - Overrides 154 | 155 | - (NSString *)description 156 | { 157 | NSString *description = [NSString stringWithFormat:@"<%@ %p>; frame = %@; input = %@; inputOptions = %@", 158 | NSStringFromClass([self class]), 159 | self, 160 | NSStringFromCGRect(self.frame), 161 | self.input, 162 | self.inputOptions]; 163 | 164 | return description; 165 | } 166 | 167 | - (void)setInput:(NSString *)input 168 | { 169 | [self willChangeValueForKey:NSStringFromSelector(@selector(input))]; 170 | _input = input; 171 | [self didChangeValueForKey:NSStringFromSelector(@selector(input))]; 172 | 173 | _inputLabel.text = _input; 174 | } 175 | 176 | - (void)setInputOptions:(NSArray *)inputOptions 177 | { 178 | [self willChangeValueForKey:NSStringFromSelector(@selector(inputOptions))]; 179 | _inputOptions = inputOptions; 180 | [self didChangeValueForKey:NSStringFromSelector(@selector(inputOptions))]; 181 | 182 | if (_inputOptions.count > 0) { 183 | [self setupInputOptionsConfiguration]; 184 | } else { 185 | [self tearDownInputOptionsConfiguration]; 186 | } 187 | } 188 | 189 | - (void)setStyle:(CYRKeyboardButtonStyle)style 190 | { 191 | [self willChangeValueForKey:NSStringFromSelector(@selector(style))]; 192 | _style = style; 193 | [self didChangeValueForKey:NSStringFromSelector(@selector(style))]; 194 | 195 | [self updateDisplayStyle]; 196 | } 197 | 198 | - (void)setKeyTextColor:(UIColor *)keyTextColor 199 | { 200 | if (_keyTextColor != keyTextColor) { 201 | [self willChangeValueForKey:NSStringFromSelector(@selector(keyTextColor))]; 202 | _keyTextColor = keyTextColor; 203 | [self didChangeValueForKey:NSStringFromSelector(@selector(keyTextColor))]; 204 | 205 | _inputLabel.textColor = keyTextColor; 206 | } 207 | } 208 | 209 | - (void)setFont:(UIFont *)font 210 | { 211 | if (_font != font) { 212 | [self willChangeValueForKey:NSStringFromSelector(@selector(font))]; 213 | _font = font; 214 | [self didChangeValueForKey:NSStringFromSelector(@selector(font))]; 215 | 216 | _inputLabel.font = font; 217 | } 218 | } 219 | 220 | - (void)setTextInput:(id)textInput 221 | { 222 | NSAssert([textInput conformsToProtocol:@protocol(UITextInput)], @" The text input object must conform to the UITextInput protocol!"); 223 | 224 | [self willChangeValueForKey:NSStringFromSelector(@selector(textInput))]; 225 | _textInput = textInput; 226 | [self didChangeValueForKey:NSStringFromSelector(@selector(textInput))]; 227 | } 228 | 229 | #pragma mark - Internal - UI 230 | 231 | - (void)showInputView 232 | { 233 | if (_style == CYRKeyboardButtonStylePhone) { 234 | [self hideInputView]; 235 | 236 | self.buttonView = [[CYRKeyboardButtonView alloc] initWithKeyboardButton:self type:CYRKeyboardButtonViewTypeInput]; 237 | 238 | [self.window addSubview:self.buttonView]; 239 | } else { 240 | [self setNeedsDisplay]; 241 | } 242 | 243 | } 244 | 245 | - (void)showExpandedInputView:(UILongPressGestureRecognizer *)recognizer 246 | { 247 | if (recognizer.state == UIGestureRecognizerStateBegan) { 248 | if (self.expandedButtonView == nil) { 249 | CYRKeyboardButtonView *expandedButtonView = [[CYRKeyboardButtonView alloc] initWithKeyboardButton:self type:CYRKeyboardButtonViewTypeExpanded]; 250 | 251 | [self.window addSubview:expandedButtonView]; 252 | self.expandedButtonView = expandedButtonView; 253 | 254 | [[NSNotificationCenter defaultCenter] postNotificationName:CYRKeyboardButtonDidShowExpandedInputNotification object:self]; 255 | 256 | [self hideInputView]; 257 | } 258 | } else if (recognizer.state == UIGestureRecognizerStateCancelled || recognizer.state == UIGestureRecognizerStateEnded) { 259 | if (self.panGestureRecognizer.state != UIGestureRecognizerStateRecognized) { 260 | [self handleTouchUpInside]; 261 | } 262 | } 263 | } 264 | 265 | - (void)hideInputView 266 | { 267 | [self.buttonView removeFromSuperview]; 268 | self.buttonView = nil; 269 | 270 | [self setNeedsDisplay]; 271 | } 272 | 273 | - (void)hideExpandedInputView 274 | { 275 | if (self.expandedButtonView.type == CYRKeyboardButtonViewTypeExpanded) { 276 | [[NSNotificationCenter defaultCenter] postNotificationName:CYRKeyboardButtonDidHideExpandedInputNotification object:self]; 277 | } 278 | 279 | [self.expandedButtonView removeFromSuperview]; 280 | self.expandedButtonView = nil; 281 | } 282 | 283 | - (void)updateDisplayStyle 284 | { 285 | switch (_style) { 286 | case CYRKeyboardButtonStylePhone: 287 | _keyCornerRadius = 4.f; 288 | break; 289 | 290 | case CYRKeyboardButtonStyleTablet: 291 | _keyCornerRadius = 6.f; 292 | break; 293 | 294 | default: 295 | break; 296 | } 297 | 298 | [self setNeedsDisplay]; 299 | } 300 | 301 | #pragma mark - Internal - Text Handling 302 | 303 | - (void)insertText:(NSString *)text 304 | { 305 | BOOL shouldInsertText = YES; 306 | 307 | if ([self.textInput isKindOfClass:[UITextView class]]) { 308 | // Call UITextViewDelegate methods if necessary 309 | UITextView *textView = (UITextView *)self.textInput; 310 | NSRange selectedRange = textView.selectedRange; 311 | 312 | if ([textView.delegate respondsToSelector:@selector(textView:shouldChangeTextInRange:replacementText:)]) { 313 | shouldInsertText = [textView.delegate textView:textView shouldChangeTextInRange:selectedRange replacementText:text]; 314 | } 315 | } else if ([self.textInput isKindOfClass:[UITextField class]]) { 316 | // Call UITextFieldDelgate methods if necessary 317 | UITextField *textField = (UITextField *)self.textInput; 318 | NSRange selectedRange = [self textInputSelectedRange]; 319 | 320 | if ([textField.delegate respondsToSelector:@selector(textField:shouldChangeCharactersInRange:replacementString:)]) { 321 | shouldInsertText = [textField.delegate textField:textField shouldChangeCharactersInRange:selectedRange replacementString:text]; 322 | } 323 | } 324 | 325 | if (shouldInsertText == YES) { 326 | [self.textInput insertText:text]; 327 | 328 | [[NSNotificationCenter defaultCenter] postNotificationName:CYRKeyboardButtonPressedNotification object:self 329 | userInfo:@{CYRKeyboardButtonKeyPressedKey : text}]; 330 | } 331 | } 332 | 333 | - (NSRange)textInputSelectedRange 334 | { 335 | UITextPosition *beginning = self.textInput.beginningOfDocument; 336 | 337 | UITextRange *selectedRange = self.textInput.selectedTextRange; 338 | UITextPosition *selectionStart = selectedRange.start; 339 | UITextPosition *selectionEnd = selectedRange.end; 340 | 341 | const NSInteger location = [self.textInput offsetFromPosition:beginning toPosition:selectionStart]; 342 | const NSInteger length = [self.textInput offsetFromPosition:selectionStart toPosition:selectionEnd]; 343 | 344 | return NSMakeRange(location, length); 345 | } 346 | 347 | #pragma mark - Internal - Configuration 348 | 349 | - (void)updateButtonPosition 350 | { 351 | // Determine the button sposition state based on the superview padding 352 | CGFloat leftPadding = CGRectGetMinX(self.frame); 353 | CGFloat rightPadding = CGRectGetMaxX(self.superview.frame) - CGRectGetMaxX(self.frame); 354 | CGFloat minimumClearance = CGRectGetWidth(self.frame) / 2 + 8; 355 | 356 | if (leftPadding >= minimumClearance && rightPadding >= minimumClearance) { 357 | self.position = CYRKeyboardButtonPositionInner; 358 | } else if (leftPadding > rightPadding) { 359 | self.position = CYRKeyboardButtonPositionLeft; 360 | } else { 361 | self.position = CYRKeyboardButtonPositionRight; 362 | } 363 | } 364 | 365 | - (void)setupInputOptionsConfiguration 366 | { 367 | [self tearDownInputOptionsConfiguration]; 368 | 369 | if (self.inputOptions.count > 0) { 370 | UILongPressGestureRecognizer *longPressGestureRecognizer = 371 | [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(showExpandedInputView:)]; 372 | longPressGestureRecognizer.minimumPressDuration = 0.3; 373 | longPressGestureRecognizer.delegate = self; 374 | 375 | [self addGestureRecognizer:longPressGestureRecognizer]; 376 | self.optionsViewRecognizer = longPressGestureRecognizer; 377 | 378 | UIPanGestureRecognizer *panGestureRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(_handlePanning:)]; 379 | panGestureRecognizer.delegate = self; 380 | 381 | [self addGestureRecognizer:panGestureRecognizer]; 382 | self.panGestureRecognizer = panGestureRecognizer; 383 | } 384 | } 385 | 386 | - (void)tearDownInputOptionsConfiguration 387 | { 388 | [self removeGestureRecognizer:self.optionsViewRecognizer]; 389 | [self removeGestureRecognizer:self.panGestureRecognizer]; 390 | } 391 | 392 | #pragma mark - Touch Actions 393 | 394 | - (void)handleTouchDown 395 | { 396 | [[UIDevice currentDevice] playInputClick]; 397 | 398 | [self showInputView]; 399 | } 400 | 401 | - (void)handleTouchUpInside 402 | { 403 | [self insertText:self.input]; 404 | 405 | [self hideInputView]; 406 | [self hideExpandedInputView]; 407 | } 408 | 409 | - (void)_handlePanning:(UIPanGestureRecognizer *)recognizer 410 | { 411 | if (recognizer.state == UIGestureRecognizerStateEnded || recognizer.state == UIGestureRecognizerStateCancelled) { 412 | if (self.expandedButtonView.selectedInputIndex != NSNotFound) { 413 | NSString *inputOption = self.inputOptions[self.expandedButtonView.selectedInputIndex]; 414 | 415 | [self insertText:inputOption]; 416 | } 417 | 418 | [self hideExpandedInputView]; 419 | } else { 420 | CGPoint location = [recognizer locationInView:self.superview]; 421 | [self.expandedButtonView updateSelectedInputIndexForPoint:location]; 422 | }; 423 | } 424 | 425 | #pragma mark - Touch Handling 426 | 427 | - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event 428 | { 429 | [super touchesEnded:touches withEvent:event]; 430 | 431 | [self hideInputView]; 432 | } 433 | 434 | - (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event 435 | { 436 | [super touchesCancelled:touches withEvent:event]; 437 | 438 | [self hideInputView]; 439 | } 440 | 441 | #pragma mark - Drawing 442 | 443 | - (void)drawRect:(CGRect)rect 444 | { 445 | CGContextRef context = UIGraphicsGetCurrentContext(); 446 | UIColor *color = self.keyColor; 447 | 448 | if (_style == CYRKeyboardButtonStyleTablet && self.state == UIControlStateHighlighted) { 449 | color = self.keyHighlightedColor; 450 | } 451 | 452 | UIColor *shadow = self.keyShadowColor; 453 | CGSize shadowOffset = CGSizeMake(0.1, 1.1); 454 | CGFloat shadowBlurRadius = 0; 455 | 456 | UIBezierPath *roundedRectanglePath = 457 | [UIBezierPath bezierPathWithRoundedRect:CGRectMake(0, 0, self.frame.size.width, self.frame.size.height - 1) cornerRadius:self.keyCornerRadius]; 458 | CGContextSaveGState(context); 459 | CGContextSetShadowWithColor(context, shadowOffset, shadowBlurRadius, shadow.CGColor); 460 | [color setFill]; 461 | [roundedRectanglePath fill]; 462 | CGContextRestoreGState(context); 463 | } 464 | 465 | @end 466 | -------------------------------------------------------------------------------- /CYRKeyboardButton/CYRKeyboardButtonView.h: -------------------------------------------------------------------------------- 1 | // 2 | // CYRKeyboardButtonView.h 3 | // 4 | // Created by Illya Busigin on 7/19/14. 5 | // Copyright (c) 2014 Cyrillian, Inc. 6 | // Portions Copyright (c) 2013 Nigel Timothy Barber (TurtleBezierPath) 7 | // 8 | // Distributed under MIT license. 9 | // Get the latest version from here: 10 | // 11 | // https://github.com/illyabusigin/CYRKeyboardButton 12 | // 13 | // The MIT License (MIT) 14 | // 15 | // Copyright (c) 2014 Cyrillian, Inc. 16 | // 17 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 18 | // this software and associated documentation files (the "Software"), to deal in 19 | // the Software without restriction, including without limitation the rights to 20 | // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 21 | // the Software, and to permit persons to whom the Software is furnished to do so, 22 | // subject to the following conditions: 23 | // 24 | // The above copyright notice and this permission notice shall be included in all 25 | // copies or substantial portions of the Software. 26 | // 27 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 28 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 29 | // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 30 | // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 31 | // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 32 | // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 33 | 34 | #import 35 | 36 | typedef NS_ENUM(NSUInteger, CYRKeyboardButtonViewType) { 37 | CYRKeyboardButtonViewTypeInput, 38 | CYRKeyboardButtonViewTypeExpanded 39 | }; 40 | 41 | @class CYRKeyboardButton; 42 | 43 | @interface CYRKeyboardButtonView : UIView 44 | 45 | @property (nonatomic, readonly) CYRKeyboardButtonViewType type; 46 | @property (nonatomic, readonly) NSInteger selectedInputIndex; 47 | 48 | - (instancetype)initWithKeyboardButton:(CYRKeyboardButton *)button type:(CYRKeyboardButtonViewType)type; 49 | - (void)updateSelectedInputIndexForPoint:(CGPoint)point; 50 | 51 | @end 52 | -------------------------------------------------------------------------------- /CYRKeyboardButton/CYRKeyboardButtonView.m: -------------------------------------------------------------------------------- 1 | // 2 | // CYRKeyboardButtonView.m 3 | // 4 | // Created by Illya Busigin on 7/19/14. 5 | // Copyright (c) 2014 Cyrillian, Inc. 6 | // Portions Copyright (c) 2013 Nigel Timothy Barber (TurtleBezierPath) 7 | // 8 | // Distributed under MIT license. 9 | // Get the latest version from here: 10 | // 11 | // https://github.com/illyabusigin/CYRKeyboardButton 12 | // 13 | // The MIT License (MIT) 14 | // 15 | // Copyright (c) 2014 Cyrillian, Inc. 16 | // 17 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 18 | // this software and associated documentation files (the "Software"), to deal in 19 | // the Software without restriction, including without limitation the rights to 20 | // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 21 | // the Software, and to permit persons to whom the Software is furnished to do so, 22 | // subject to the following conditions: 23 | // 24 | // The above copyright notice and this permission notice shall be included in all 25 | // copies or substantial portions of the Software. 26 | // 27 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 28 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 29 | // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 30 | // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 31 | // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 32 | // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 33 | 34 | #import "CYRKeyboardButtonView.h" 35 | #import "CYRKeyboardButton.h" 36 | #import "TurtleBezierPath.h" 37 | 38 | @interface CYRKeyboardButtonView () 39 | 40 | @property (nonatomic, weak) CYRKeyboardButton *button; 41 | @property (nonatomic, assign) CYRKeyboardButtonViewType type; 42 | @property (nonatomic, assign) CYRKeyboardButtonPosition expandedPosition; 43 | @property (nonatomic, strong) NSMutableArray *inputOptionRects; 44 | @property (nonatomic, assign) NSInteger selectedInputIndex; 45 | 46 | @end 47 | 48 | @implementation CYRKeyboardButtonView 49 | 50 | #pragma mark - UIView 51 | 52 | - (instancetype)initWithKeyboardButton:(CYRKeyboardButton *)button type:(CYRKeyboardButtonViewType)type; 53 | { 54 | CGRect frame = [UIScreen mainScreen].bounds; 55 | 56 | if (UIInterfaceOrientationIsLandscape([UIApplication sharedApplication].statusBarOrientation)) { 57 | frame = CGRectMake(0, 0, CGRectGetHeight(frame), CGRectGetWidth(frame)); 58 | } 59 | 60 | self = [super initWithFrame:frame]; 61 | 62 | if (self) { 63 | _button = button; 64 | _type = type; 65 | _selectedInputIndex = 0; 66 | 67 | self.backgroundColor = [UIColor clearColor]; 68 | self.userInteractionEnabled = NO; 69 | 70 | if (button.position != CYRKeyboardButtonPositionInner) { 71 | _expandedPosition = button.position; 72 | } else { 73 | // Determine the position 74 | CGFloat leftPadding = CGRectGetMinX(button.frame); 75 | CGFloat rightPadding = CGRectGetMaxX(button.superview.frame) - CGRectGetMaxX(button.frame); 76 | 77 | _expandedPosition = (leftPadding > rightPadding ? CYRKeyboardButtonPositionLeft : CYRKeyboardButtonPositionRight); 78 | } 79 | } 80 | 81 | return self; 82 | } 83 | 84 | - (void)didMoveToSuperview 85 | { 86 | if (_type == CYRKeyboardButtonViewTypeExpanded) { 87 | [self determineExpandedKeyGeometries]; 88 | } 89 | } 90 | 91 | #pragma mark - Public 92 | 93 | - (void)updateSelectedInputIndexForPoint:(CGPoint)point 94 | { 95 | __block NSInteger selectedInputIndex = NSNotFound; 96 | 97 | CGRect testRect = CGRectMake(point.x, point.y, 0, 0); 98 | 99 | CGPoint location = [self convertRect:testRect fromView:self.button.superview].origin; 100 | 101 | [self.inputOptionRects enumerateObjectsUsingBlock:^(NSValue *rectValue, NSUInteger idx, BOOL *stop) { 102 | CGRect keyRect = [rectValue CGRectValue]; 103 | CGRect infiniteKeyRect = CGRectMake(CGRectGetMinX(keyRect), 0, CGRectGetWidth(keyRect), NSIntegerMax); 104 | infiniteKeyRect = CGRectInset(infiniteKeyRect, -3, 0); 105 | 106 | if (CGRectContainsPoint(infiniteKeyRect, location)) { 107 | selectedInputIndex = idx; 108 | *stop = YES; 109 | } 110 | }]; 111 | 112 | if (self.selectedInputIndex != selectedInputIndex) { 113 | self.selectedInputIndex = selectedInputIndex; 114 | [self setNeedsDisplay]; 115 | } 116 | } 117 | 118 | #pragma mark - Drawing 119 | 120 | - (void)drawRect:(CGRect)rect 121 | { 122 | switch (_type) { 123 | case CYRKeyboardButtonViewTypeInput: 124 | [self drawInputView:rect]; 125 | break; 126 | 127 | case CYRKeyboardButtonViewTypeExpanded: 128 | [self drawExpandedInputView:rect]; 129 | break; 130 | 131 | default: 132 | break; 133 | } 134 | } 135 | 136 | - (void)drawInputView:(CGRect)rect 137 | { 138 | // Generate the overlay 139 | UIBezierPath *bezierPath = [self inputViewPath]; 140 | NSString *inputString = self.button.input; 141 | 142 | // Position the overlay 143 | CGRect keyRect = [self convertRect:self.button.frame fromView:self.button.superview]; 144 | 145 | CGContextRef context = UIGraphicsGetCurrentContext(); 146 | 147 | // Overlay path & shadow 148 | { 149 | //// Shadow Declarations 150 | UIColor* shadow = [[UIColor blackColor] colorWithAlphaComponent: 0.5]; 151 | CGSize shadowOffset = CGSizeMake(0, 0.5); 152 | CGFloat shadowBlurRadius = 2; 153 | 154 | //// Rounded Rectangle Drawing 155 | CGContextSaveGState(context); 156 | CGContextSetShadowWithColor(context, shadowOffset, shadowBlurRadius, shadow.CGColor); 157 | [self.button.keyColor setFill]; 158 | [bezierPath fill]; 159 | CGContextRestoreGState(context); 160 | } 161 | 162 | // Draw the key shadow sliver 163 | { 164 | //// Color Declarations 165 | UIColor *color = self.button.keyColor; 166 | 167 | //// Shadow Declarations 168 | UIColor *shadow = self.button.keyShadowColor; 169 | CGSize shadowOffset = CGSizeMake(0.1, 1.1); 170 | CGFloat shadowBlurRadius = 0; 171 | 172 | //// Rounded Rectangle Drawing 173 | UIBezierPath *roundedRectanglePath = 174 | [UIBezierPath bezierPathWithRoundedRect:CGRectMake(keyRect.origin.x, keyRect.origin.y, keyRect.size.width, keyRect.size.height - 1) cornerRadius:4]; 175 | CGContextSaveGState(context); 176 | CGContextSetShadowWithColor(context, shadowOffset, shadowBlurRadius, shadow.CGColor); 177 | [color setFill]; 178 | [roundedRectanglePath fill]; 179 | 180 | CGContextRestoreGState(context); 181 | } 182 | 183 | // Text drawing 184 | { 185 | UIColor *stringColor = self.button.keyTextColor; 186 | 187 | CGRect stringRect = bezierPath.bounds; 188 | 189 | NSMutableParagraphStyle *p = [NSMutableParagraphStyle new]; 190 | p.alignment = NSTextAlignmentCenter; 191 | 192 | NSAttributedString *attributedString = [[NSAttributedString alloc] 193 | initWithString:inputString 194 | attributes: 195 | @{NSFontAttributeName : [UIFont fontWithName:@"HelveticaNeue-Light" size:44], NSForegroundColorAttributeName : stringColor, NSParagraphStyleAttributeName : p}]; 196 | [attributedString drawInRect:stringRect]; 197 | } 198 | } 199 | 200 | - (void)drawExpandedInputView:(CGRect)rect 201 | { 202 | // Generate the overlay 203 | UIBezierPath *bezierPath = [self expandedInputViewPath]; 204 | 205 | // Position the overlay 206 | CGRect keyRect = [self convertRect:self.button.frame fromView:self.button.superview]; 207 | 208 | CGContextRef context = UIGraphicsGetCurrentContext(); 209 | 210 | // Overlay path & shadow 211 | { 212 | CGFloat shadowAlpha = 0; 213 | CGSize shadowOffset; 214 | 215 | switch ([UIDevice currentDevice].userInterfaceIdiom) { 216 | case UIUserInterfaceIdiomPhone: 217 | shadowAlpha = 0.5; 218 | shadowOffset = CGSizeMake(0, 0.5); 219 | break; 220 | 221 | case UIUserInterfaceIdiomPad: 222 | shadowAlpha = 0.25; 223 | shadowOffset = CGSizeZero; 224 | break; 225 | 226 | default: 227 | break; 228 | } 229 | 230 | //// Shadow Declarations 231 | UIColor* shadow = [[UIColor blackColor] colorWithAlphaComponent: shadowAlpha]; 232 | CGFloat shadowBlurRadius = 2; 233 | 234 | //// Rounded Rectangle Drawing 235 | CGContextSaveGState(context); 236 | CGContextSetShadowWithColor(context, shadowOffset, shadowBlurRadius, shadow.CGColor); 237 | [self.button.keyColor setFill]; 238 | [bezierPath fill]; 239 | CGContextRestoreGState(context); 240 | } 241 | 242 | // Draw the key shadow sliver 243 | if (self.button.style == CYRKeyboardButtonStylePhone) { 244 | UIColor *color = self.button.keyColor; 245 | 246 | //// Shadow Declarations 247 | UIColor *shadow = self.button.keyShadowColor; 248 | CGSize shadowOffset = CGSizeMake(0.1, 1.1); 249 | CGFloat shadowBlurRadius = 0; 250 | 251 | //// Rounded Rectangle Drawing 252 | UIBezierPath *roundedRectanglePath = 253 | [UIBezierPath bezierPathWithRoundedRect:CGRectMake(keyRect.origin.x, keyRect.origin.y, keyRect.size.width, keyRect.size.height - 1) cornerRadius:4]; 254 | CGContextSaveGState(context); 255 | CGContextSetShadowWithColor(context, shadowOffset, shadowBlurRadius, shadow.CGColor); 256 | [color setFill]; 257 | [roundedRectanglePath fill]; 258 | 259 | CGContextRestoreGState(context); 260 | } 261 | 262 | [self drawExpandedInputViewOptions]; 263 | } 264 | 265 | - (void)drawExpandedInputViewOptions 266 | { 267 | CGContextRef context = UIGraphicsGetCurrentContext(); 268 | CGContextSetShadowWithColor(context, CGSizeZero, 0, [[UIColor clearColor] CGColor]); 269 | CGContextSaveGState(context); 270 | 271 | NSArray *inputOptions = self.button.inputOptions; 272 | 273 | [inputOptions enumerateObjectsUsingBlock:^(NSString *optionString, NSUInteger idx, BOOL *stop) { 274 | CGRect optionRect = [self.inputOptionRects[idx] CGRectValue]; 275 | 276 | BOOL selected = (idx == self.selectedInputIndex); 277 | 278 | if (selected) { 279 | // Draw selection background 280 | UIBezierPath *roundedRectanglePath = [UIBezierPath bezierPathWithRoundedRect:optionRect cornerRadius:4]; 281 | 282 | [self.tintColor setFill]; 283 | [roundedRectanglePath fill]; 284 | } 285 | 286 | // Draw the text 287 | UIColor *stringColor = (selected ? [UIColor whiteColor] : self.button.keyTextColor); 288 | CGSize stringSize = [optionString sizeWithAttributes:@{NSFontAttributeName : self.button.inputOptionsFont}]; 289 | CGRect stringRect = CGRectMake( 290 | CGRectGetMidX(optionRect) - stringSize.width / 2, CGRectGetMidY(optionRect) - stringSize.height / 2, stringSize.width, stringSize.height); 291 | 292 | NSMutableParagraphStyle *p = [NSMutableParagraphStyle new]; 293 | p.alignment = NSTextAlignmentCenter; 294 | 295 | NSAttributedString *attributedString = [[NSAttributedString alloc] 296 | initWithString:optionString 297 | attributes: 298 | @{NSFontAttributeName : self.button.inputOptionsFont, NSForegroundColorAttributeName : stringColor, NSParagraphStyleAttributeName : p}]; 299 | [attributedString drawInRect:stringRect]; 300 | }]; 301 | 302 | CGContextRestoreGState(context); 303 | } 304 | 305 | #pragma mark - Internal 306 | 307 | - (UIBezierPath *)inputViewPath 308 | { 309 | CGRect keyRect = [self convertRect:self.button.frame fromView:self.button.superview]; 310 | 311 | UIEdgeInsets insets = UIEdgeInsetsMake(7, 13, 7, 13); 312 | CGFloat upperWidth = CGRectGetWidth(_button.frame) + insets.left + insets.right; 313 | CGFloat lowerWidth = CGRectGetWidth(_button.frame); 314 | CGFloat majorRadius = 10.f; 315 | CGFloat minorRadius = 4.f; 316 | 317 | TurtleBezierPath *path = [TurtleBezierPath new]; 318 | [path home]; 319 | path.lineWidth = 0; 320 | path.lineCapStyle = kCGLineCapRound; 321 | 322 | switch (self.button.position) { 323 | case CYRKeyboardButtonPositionInner: 324 | { 325 | [path rightArc:majorRadius turn:90]; // #1 326 | [path forward:upperWidth - 2 * majorRadius]; // #2 top 327 | [path rightArc:majorRadius turn:90]; // #3 328 | [path forward:CGRectGetHeight(keyRect) - 2 * majorRadius + insets.top + insets.bottom]; // #4 right big 329 | [path rightArc:majorRadius turn:48]; // #5 330 | [path forward:8.5f]; 331 | [path leftArc:majorRadius turn:48]; // #6 332 | [path forward:CGRectGetHeight(keyRect) - 8.5f + 1]; 333 | [path rightArc:minorRadius turn:90]; 334 | [path forward:lowerWidth - 2 * minorRadius]; // lowerWidth - 2 * minorRadius + 0.5f 335 | [path rightArc:minorRadius turn:90]; 336 | [path forward:CGRectGetHeight(keyRect) - 2 * minorRadius]; 337 | [path leftArc:majorRadius turn:48]; 338 | [path forward:8.5f]; 339 | [path rightArc:majorRadius turn:48]; 340 | 341 | CGFloat offsetX = 0, offsetY = 0; 342 | CGRect pathBoundingBox = path.bounds; 343 | 344 | offsetX = CGRectGetMidX(keyRect) - CGRectGetMidX(path.bounds); 345 | offsetY = CGRectGetMaxY(keyRect) - CGRectGetHeight(pathBoundingBox) + 10; 346 | 347 | [path applyTransform:CGAffineTransformMakeTranslation(offsetX, offsetY)]; 348 | } 349 | break; 350 | 351 | case CYRKeyboardButtonPositionLeft: 352 | { 353 | [path rightArc:majorRadius turn:90]; // #1 354 | [path forward:upperWidth - 2 * majorRadius]; // #2 top 355 | [path rightArc:majorRadius turn:90]; // #3 356 | [path forward:CGRectGetHeight(keyRect) - 2 * majorRadius + insets.top + insets.bottom]; // #4 right big 357 | [path rightArc:majorRadius turn:45]; // #5 358 | [path forward:28]; // 6 359 | [path leftArc:majorRadius turn:45]; // #7 360 | [path forward:CGRectGetHeight(keyRect) - 26 + (insets.left + insets.right) / 4]; // #8 361 | [path rightArc:minorRadius turn:90]; // 9 362 | [path forward:path.currentPoint.x - minorRadius]; // 10 363 | [path rightArc:minorRadius turn:90]; // 11 364 | 365 | 366 | CGFloat offsetX = 0, offsetY = 0; 367 | CGRect pathBoundingBox = path.bounds; 368 | 369 | offsetX = CGRectGetMaxX(keyRect) - CGRectGetWidth(path.bounds); 370 | offsetY = CGRectGetMaxY(keyRect) - CGRectGetHeight(pathBoundingBox) - CGRectGetMinY(path.bounds); 371 | 372 | [path applyTransform:CGAffineTransformTranslate(CGAffineTransformMakeScale(-1, 1), -offsetX - CGRectGetWidth(path.bounds), offsetY)]; 373 | } 374 | break; 375 | 376 | case CYRKeyboardButtonPositionRight: 377 | { 378 | [path rightArc:majorRadius turn:90]; // #1 379 | [path forward:upperWidth - 2 * majorRadius]; // #2 top 380 | [path rightArc:majorRadius turn:90]; // #3 381 | [path forward:CGRectGetHeight(keyRect) - 2 * majorRadius + insets.top + insets.bottom]; // #4 right big 382 | [path rightArc:majorRadius turn:45]; // #5 383 | [path forward:28]; // 6 384 | [path leftArc:majorRadius turn:45]; // #7 385 | [path forward:CGRectGetHeight(keyRect) - 26 + (insets.left + insets.right) / 4]; // #8 386 | [path rightArc:minorRadius turn:90]; // 9 387 | [path forward:path.currentPoint.x - minorRadius]; // 10 388 | [path rightArc:minorRadius turn:90]; // 11 389 | 390 | CGFloat offsetX = 0, offsetY = 0; 391 | CGRect pathBoundingBox = path.bounds; 392 | 393 | offsetX = CGRectGetMinX(keyRect); 394 | offsetY = CGRectGetMaxY(keyRect) - CGRectGetHeight(pathBoundingBox) - CGRectGetMinY(path.bounds); 395 | 396 | [path applyTransform:CGAffineTransformMakeTranslation(offsetX, offsetY)]; 397 | } 398 | break; 399 | 400 | default: 401 | break; 402 | } 403 | 404 | return path; 405 | } 406 | 407 | - (UIBezierPath *)expandedInputViewPath 408 | { 409 | CGRect keyRect = [self convertRect:self.button.frame fromView:self.button.superview]; 410 | 411 | UIEdgeInsets insets = UIEdgeInsetsMake(7, 13, 7, 13); 412 | CGFloat margin = 7.f; 413 | CGFloat upperWidth = insets.left + insets.right + self.button.inputOptions.count * CGRectGetWidth(keyRect) + margin * (self.button.inputOptions.count - 1) - margin/2; 414 | CGFloat lowerWidth = CGRectGetWidth(_button.frame); 415 | CGFloat majorRadius = 10.f; 416 | CGFloat minorRadius = 4.f; 417 | 418 | TurtleBezierPath *path = [TurtleBezierPath new]; 419 | [path home]; 420 | path.lineWidth = 0; 421 | path.lineCapStyle = kCGLineCapRound; 422 | 423 | CGFloat offsetX = 0, offsetY = 0; 424 | 425 | switch (_expandedPosition) { 426 | case CYRKeyboardButtonPositionRight: 427 | { 428 | switch (self.button.style) { 429 | case CYRKeyboardButtonStylePhone: 430 | { 431 | [path rightArc:majorRadius turn:90]; // #1 432 | [path forward:upperWidth - 2 * majorRadius]; // #2 top 433 | [path rightArc:majorRadius turn:90]; // #3 434 | [path forward:CGRectGetHeight(keyRect) - 2 * majorRadius + insets.top + insets.bottom - 3]; // #4 right big 435 | [path rightArc:majorRadius turn:90]; // #5 436 | [path forward:path.currentPoint.x - (CGRectGetWidth(keyRect) + 2 * majorRadius + 3)]; 437 | [path leftArc:majorRadius turn:90]; // #6 438 | [path forward:CGRectGetHeight(keyRect) - minorRadius]; 439 | [path rightArc:minorRadius turn:90]; 440 | [path forward:lowerWidth - 2 * minorRadius]; // lowerWidth - 2 * minorRadius + 0.5f 441 | [path rightArc:minorRadius turn:90]; 442 | [path forward:CGRectGetHeight(keyRect) - 2 * minorRadius]; 443 | [path leftArc:majorRadius turn:48]; 444 | [path forward:8.5f]; 445 | [path rightArc:majorRadius turn:48]; 446 | 447 | offsetX = CGRectGetMaxX(keyRect) - CGRectGetWidth(keyRect) - insets.left; 448 | offsetY = CGRectGetMaxY(keyRect) - CGRectGetHeight(path.bounds) + 10; 449 | 450 | [path applyTransform:CGAffineTransformMakeTranslation(offsetX, offsetY)]; 451 | } 452 | break; 453 | 454 | case CYRKeyboardButtonStyleTablet: 455 | { 456 | CGRect firstRect = [self.inputOptionRects[0] CGRectValue]; 457 | 458 | path = (id)[UIBezierPath bezierPathWithRoundedRect:CGRectMake(0, 0, CGRectGetWidth(firstRect) * self.button.inputOptions.count + 12, CGRectGetHeight(firstRect) + 12) 459 | cornerRadius:6]; 460 | 461 | offsetX = CGRectGetMinX(keyRect); 462 | offsetY = CGRectGetMinY(firstRect) - 6; 463 | 464 | [path applyTransform:CGAffineTransformMakeTranslation(offsetX, offsetY)]; 465 | 466 | } 467 | break; 468 | 469 | default: 470 | break; 471 | } 472 | 473 | 474 | } 475 | break; 476 | 477 | case CYRKeyboardButtonPositionLeft: 478 | { 479 | switch (self.button.style) { 480 | case CYRKeyboardButtonStylePhone: 481 | { 482 | [path rightArc:majorRadius turn:90]; // #1 483 | [path forward:upperWidth - 2 * majorRadius]; // #2 top 484 | [path rightArc:majorRadius turn:90]; // #3 485 | [path forward:CGRectGetHeight(keyRect) - 2 * majorRadius + insets.top + insets.bottom - 3]; // #4 right big 486 | 487 | [path rightArc:majorRadius turn:48]; 488 | [path forward:8.5f]; 489 | [path leftArc:majorRadius turn:48]; 490 | 491 | [path forward:CGRectGetHeight(keyRect) - minorRadius]; 492 | [path rightArc:minorRadius turn:90]; 493 | [path forward:lowerWidth - 2 * minorRadius]; // lowerWidth - 2 * minorRadius + 0.5f 494 | [path rightArc:minorRadius turn:90]; 495 | [path forward:CGRectGetHeight(keyRect) - 2 * minorRadius]; 496 | 497 | [path leftArc:majorRadius turn:90]; // #5 498 | [path forward:path.currentPoint.x - majorRadius]; 499 | [path rightArc:majorRadius turn:90]; // #6 500 | 501 | offsetX = CGRectGetMaxX(keyRect) - CGRectGetWidth(path.bounds) + insets.left; 502 | offsetY = CGRectGetMaxY(keyRect) - CGRectGetHeight(path.bounds) + 10; 503 | 504 | [path applyTransform:CGAffineTransformMakeTranslation(offsetX, offsetY)]; 505 | 506 | } 507 | break; 508 | 509 | case CYRKeyboardButtonStyleTablet: 510 | { 511 | CGRect firstRect = [self.inputOptionRects[0] CGRectValue]; 512 | 513 | path = (id)[UIBezierPath bezierPathWithRoundedRect:CGRectMake(0, 0, CGRectGetWidth(firstRect) * self.button.inputOptions.count + 12, CGRectGetHeight(firstRect) + 12) 514 | cornerRadius:6]; 515 | 516 | offsetX = CGRectGetMaxX(keyRect) - CGRectGetWidth(path.bounds); 517 | offsetY = CGRectGetMinY(firstRect) - 6; 518 | 519 | [path applyTransform:CGAffineTransformMakeTranslation(offsetX, offsetY)]; 520 | } 521 | break; 522 | 523 | default: 524 | break; 525 | } 526 | } 527 | break; 528 | 529 | default: 530 | break; 531 | } 532 | 533 | return path; 534 | } 535 | 536 | - (void)determineExpandedKeyGeometries 537 | { 538 | CGRect keyRect = [self convertRect:self.button.frame fromView:self.button.superview]; 539 | 540 | __block NSMutableArray *inputOptionRects = [NSMutableArray arrayWithCapacity:self.button.inputOptions.count]; 541 | 542 | CGFloat offset = 0; 543 | CGFloat spacing = 0; 544 | 545 | __block CGRect optionRect = CGRectZero; 546 | 547 | switch (self.button.style) { 548 | case CYRKeyboardButtonStylePhone: 549 | offset = CGRectGetWidth(keyRect); 550 | spacing = 6; 551 | optionRect = CGRectOffset(CGRectInset(keyRect, 0, 0.5), 0, -(CGRectGetHeight(keyRect) + 15)); 552 | break; 553 | 554 | case CYRKeyboardButtonStyleTablet: 555 | spacing = 0; 556 | optionRect = CGRectOffset(CGRectInset(keyRect, 6, 6), 0, -(CGRectGetHeight(keyRect) + 3)); 557 | offset = CGRectGetWidth(optionRect); 558 | break; 559 | 560 | default: 561 | break; 562 | } 563 | 564 | [self.button.inputOptions enumerateObjectsUsingBlock:^(NSString *option, NSUInteger idx, BOOL *stop) { 565 | 566 | [inputOptionRects addObject:[NSValue valueWithCGRect:optionRect]]; 567 | 568 | // Offset the option rect 569 | switch (_expandedPosition) { 570 | case CYRKeyboardButtonPositionRight: 571 | optionRect = CGRectOffset(optionRect, +(offset + spacing), 0); 572 | break; 573 | 574 | case CYRKeyboardButtonPositionLeft: 575 | optionRect = CGRectOffset(optionRect, -(offset + spacing), 0); 576 | break; 577 | 578 | default: 579 | break; 580 | } 581 | }]; 582 | 583 | self.inputOptionRects = inputOptionRects; 584 | } 585 | 586 | @end 587 | -------------------------------------------------------------------------------- /Example/Example.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 37EFED44197B44A600437A0F /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 37EFED43197B44A600437A0F /* Foundation.framework */; }; 11 | 37EFED46197B44A600437A0F /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 37EFED45197B44A600437A0F /* CoreGraphics.framework */; }; 12 | 37EFED48197B44A600437A0F /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 37EFED47197B44A600437A0F /* UIKit.framework */; }; 13 | 37EFED4E197B44A600437A0F /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 37EFED4C197B44A600437A0F /* InfoPlist.strings */; }; 14 | 37EFED50197B44A600437A0F /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 37EFED4F197B44A600437A0F /* main.m */; }; 15 | 37EFED54197B44A600437A0F /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 37EFED53197B44A600437A0F /* AppDelegate.m */; }; 16 | 37EFED57197B44A600437A0F /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 37EFED55197B44A600437A0F /* Main.storyboard */; }; 17 | 37EFED5A197B44A600437A0F /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 37EFED59197B44A600437A0F /* ViewController.m */; }; 18 | 37EFED5C197B44A600437A0F /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 37EFED5B197B44A600437A0F /* Images.xcassets */; }; 19 | 37EFED63197B44A600437A0F /* XCTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 37EFED62197B44A600437A0F /* XCTest.framework */; }; 20 | 37EFED64197B44A600437A0F /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 37EFED43197B44A600437A0F /* Foundation.framework */; }; 21 | 37EFED65197B44A600437A0F /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 37EFED47197B44A600437A0F /* UIKit.framework */; }; 22 | 37EFED6D197B44A600437A0F /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 37EFED6B197B44A600437A0F /* InfoPlist.strings */; }; 23 | 37EFED6F197B44A600437A0F /* ExampleTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 37EFED6E197B44A600437A0F /* ExampleTests.m */; }; 24 | 37EFED87197B487E00437A0F /* LICENSE in Resources */ = {isa = PBXBuildFile; fileRef = 37EFED84197B487E00437A0F /* LICENSE */; }; 25 | 37EFED88197B487E00437A0F /* TurtleBezierPath.m in Sources */ = {isa = PBXBuildFile; fileRef = 37EFED86197B487E00437A0F /* TurtleBezierPath.m */; }; 26 | 37EFED8B197B594000437A0F /* CYRKeyboardButton.m in Sources */ = {isa = PBXBuildFile; fileRef = 37EFED8A197B594000437A0F /* CYRKeyboardButton.m */; }; 27 | 37EFED8F197B596000437A0F /* CYRKeyboardButtonView.m in Sources */ = {isa = PBXBuildFile; fileRef = 37EFED8E197B596000437A0F /* CYRKeyboardButtonView.m */; }; 28 | /* End PBXBuildFile section */ 29 | 30 | /* Begin PBXContainerItemProxy section */ 31 | 37EFED66197B44A600437A0F /* PBXContainerItemProxy */ = { 32 | isa = PBXContainerItemProxy; 33 | containerPortal = 37EFED38197B44A600437A0F /* Project object */; 34 | proxyType = 1; 35 | remoteGlobalIDString = 37EFED3F197B44A600437A0F; 36 | remoteInfo = Example; 37 | }; 38 | /* End PBXContainerItemProxy section */ 39 | 40 | /* Begin PBXFileReference section */ 41 | 37EFED40197B44A600437A0F /* Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Example.app; sourceTree = BUILT_PRODUCTS_DIR; }; 42 | 37EFED43197B44A600437A0F /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; 43 | 37EFED45197B44A600437A0F /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; 44 | 37EFED47197B44A600437A0F /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; 45 | 37EFED4B197B44A600437A0F /* Example-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Example-Info.plist"; sourceTree = ""; }; 46 | 37EFED4D197B44A600437A0F /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 47 | 37EFED4F197B44A600437A0F /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 48 | 37EFED51197B44A600437A0F /* Example-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Example-Prefix.pch"; sourceTree = ""; }; 49 | 37EFED52197B44A600437A0F /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 50 | 37EFED53197B44A600437A0F /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 51 | 37EFED56197B44A600437A0F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 52 | 37EFED58197B44A600437A0F /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; 53 | 37EFED59197B44A600437A0F /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; 54 | 37EFED5B197B44A600437A0F /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; 55 | 37EFED61197B44A600437A0F /* ExampleTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ExampleTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 56 | 37EFED62197B44A600437A0F /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; }; 57 | 37EFED6A197B44A600437A0F /* ExampleTests-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "ExampleTests-Info.plist"; sourceTree = ""; }; 58 | 37EFED6C197B44A600437A0F /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 59 | 37EFED6E197B44A600437A0F /* ExampleTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ExampleTests.m; sourceTree = ""; }; 60 | 37EFED84197B487E00437A0F /* LICENSE */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = LICENSE; sourceTree = ""; }; 61 | 37EFED85197B487E00437A0F /* TurtleBezierPath.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TurtleBezierPath.h; sourceTree = ""; }; 62 | 37EFED86197B487E00437A0F /* TurtleBezierPath.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TurtleBezierPath.m; sourceTree = ""; }; 63 | 37EFED89197B594000437A0F /* CYRKeyboardButton.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CYRKeyboardButton.h; sourceTree = ""; }; 64 | 37EFED8A197B594000437A0F /* CYRKeyboardButton.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CYRKeyboardButton.m; sourceTree = ""; }; 65 | 37EFED8D197B596000437A0F /* CYRKeyboardButtonView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CYRKeyboardButtonView.h; sourceTree = ""; }; 66 | 37EFED8E197B596000437A0F /* CYRKeyboardButtonView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CYRKeyboardButtonView.m; sourceTree = ""; }; 67 | /* End PBXFileReference section */ 68 | 69 | /* Begin PBXFrameworksBuildPhase section */ 70 | 37EFED3D197B44A600437A0F /* Frameworks */ = { 71 | isa = PBXFrameworksBuildPhase; 72 | buildActionMask = 2147483647; 73 | files = ( 74 | 37EFED46197B44A600437A0F /* CoreGraphics.framework in Frameworks */, 75 | 37EFED48197B44A600437A0F /* UIKit.framework in Frameworks */, 76 | 37EFED44197B44A600437A0F /* Foundation.framework in Frameworks */, 77 | ); 78 | runOnlyForDeploymentPostprocessing = 0; 79 | }; 80 | 37EFED5E197B44A600437A0F /* Frameworks */ = { 81 | isa = PBXFrameworksBuildPhase; 82 | buildActionMask = 2147483647; 83 | files = ( 84 | 37EFED63197B44A600437A0F /* XCTest.framework in Frameworks */, 85 | 37EFED65197B44A600437A0F /* UIKit.framework in Frameworks */, 86 | 37EFED64197B44A600437A0F /* Foundation.framework in Frameworks */, 87 | ); 88 | runOnlyForDeploymentPostprocessing = 0; 89 | }; 90 | /* End PBXFrameworksBuildPhase section */ 91 | 92 | /* Begin PBXGroup section */ 93 | 37D9AD32197E08F400431AB2 /* iPad */ = { 94 | isa = PBXGroup; 95 | children = ( 96 | ); 97 | name = iPad; 98 | sourceTree = ""; 99 | }; 100 | 37EFED37197B44A600437A0F = { 101 | isa = PBXGroup; 102 | children = ( 103 | 37EFED82197B487E00437A0F /* Vendor */, 104 | 37EFED78197B44C700437A0F /* CYRKeyboardButton */, 105 | 37EFED49197B44A600437A0F /* Example */, 106 | 37EFED68197B44A600437A0F /* ExampleTests */, 107 | 37EFED42197B44A600437A0F /* Frameworks */, 108 | 37EFED41197B44A600437A0F /* Products */, 109 | ); 110 | sourceTree = ""; 111 | }; 112 | 37EFED41197B44A600437A0F /* Products */ = { 113 | isa = PBXGroup; 114 | children = ( 115 | 37EFED40197B44A600437A0F /* Example.app */, 116 | 37EFED61197B44A600437A0F /* ExampleTests.xctest */, 117 | ); 118 | name = Products; 119 | sourceTree = ""; 120 | }; 121 | 37EFED42197B44A600437A0F /* Frameworks */ = { 122 | isa = PBXGroup; 123 | children = ( 124 | 37EFED43197B44A600437A0F /* Foundation.framework */, 125 | 37EFED45197B44A600437A0F /* CoreGraphics.framework */, 126 | 37EFED47197B44A600437A0F /* UIKit.framework */, 127 | 37EFED62197B44A600437A0F /* XCTest.framework */, 128 | ); 129 | name = Frameworks; 130 | sourceTree = ""; 131 | }; 132 | 37EFED49197B44A600437A0F /* Example */ = { 133 | isa = PBXGroup; 134 | children = ( 135 | 37D9AD32197E08F400431AB2 /* iPad */, 136 | 37EFED52197B44A600437A0F /* AppDelegate.h */, 137 | 37EFED53197B44A600437A0F /* AppDelegate.m */, 138 | 37EFED55197B44A600437A0F /* Main.storyboard */, 139 | 37EFED58197B44A600437A0F /* ViewController.h */, 140 | 37EFED59197B44A600437A0F /* ViewController.m */, 141 | 37EFED5B197B44A600437A0F /* Images.xcassets */, 142 | 37EFED4A197B44A600437A0F /* Supporting Files */, 143 | ); 144 | path = Example; 145 | sourceTree = ""; 146 | }; 147 | 37EFED4A197B44A600437A0F /* Supporting Files */ = { 148 | isa = PBXGroup; 149 | children = ( 150 | 37EFED4B197B44A600437A0F /* Example-Info.plist */, 151 | 37EFED4C197B44A600437A0F /* InfoPlist.strings */, 152 | 37EFED4F197B44A600437A0F /* main.m */, 153 | 37EFED51197B44A600437A0F /* Example-Prefix.pch */, 154 | ); 155 | name = "Supporting Files"; 156 | sourceTree = ""; 157 | }; 158 | 37EFED68197B44A600437A0F /* ExampleTests */ = { 159 | isa = PBXGroup; 160 | children = ( 161 | 37EFED6E197B44A600437A0F /* ExampleTests.m */, 162 | 37EFED69197B44A600437A0F /* Supporting Files */, 163 | ); 164 | path = ExampleTests; 165 | sourceTree = ""; 166 | }; 167 | 37EFED69197B44A600437A0F /* Supporting Files */ = { 168 | isa = PBXGroup; 169 | children = ( 170 | 37EFED6A197B44A600437A0F /* ExampleTests-Info.plist */, 171 | 37EFED6B197B44A600437A0F /* InfoPlist.strings */, 172 | ); 173 | name = "Supporting Files"; 174 | sourceTree = ""; 175 | }; 176 | 37EFED78197B44C700437A0F /* CYRKeyboardButton */ = { 177 | isa = PBXGroup; 178 | children = ( 179 | 37EFED89197B594000437A0F /* CYRKeyboardButton.h */, 180 | 37EFED8A197B594000437A0F /* CYRKeyboardButton.m */, 181 | 37EFED8D197B596000437A0F /* CYRKeyboardButtonView.h */, 182 | 37EFED8E197B596000437A0F /* CYRKeyboardButtonView.m */, 183 | ); 184 | name = CYRKeyboardButton; 185 | path = ../CYRKeyboardButton; 186 | sourceTree = ""; 187 | }; 188 | 37EFED82197B487E00437A0F /* Vendor */ = { 189 | isa = PBXGroup; 190 | children = ( 191 | 37EFED83197B487E00437A0F /* TurtleBezierPath */, 192 | ); 193 | path = Vendor; 194 | sourceTree = ""; 195 | }; 196 | 37EFED83197B487E00437A0F /* TurtleBezierPath */ = { 197 | isa = PBXGroup; 198 | children = ( 199 | 37EFED84197B487E00437A0F /* LICENSE */, 200 | 37EFED85197B487E00437A0F /* TurtleBezierPath.h */, 201 | 37EFED86197B487E00437A0F /* TurtleBezierPath.m */, 202 | ); 203 | path = TurtleBezierPath; 204 | sourceTree = ""; 205 | }; 206 | /* End PBXGroup section */ 207 | 208 | /* Begin PBXNativeTarget section */ 209 | 37EFED3F197B44A600437A0F /* Example */ = { 210 | isa = PBXNativeTarget; 211 | buildConfigurationList = 37EFED72197B44A600437A0F /* Build configuration list for PBXNativeTarget "Example" */; 212 | buildPhases = ( 213 | 37EFED3C197B44A600437A0F /* Sources */, 214 | 37EFED3D197B44A600437A0F /* Frameworks */, 215 | 37EFED3E197B44A600437A0F /* Resources */, 216 | ); 217 | buildRules = ( 218 | ); 219 | dependencies = ( 220 | ); 221 | name = Example; 222 | productName = Example; 223 | productReference = 37EFED40197B44A600437A0F /* Example.app */; 224 | productType = "com.apple.product-type.application"; 225 | }; 226 | 37EFED60197B44A600437A0F /* ExampleTests */ = { 227 | isa = PBXNativeTarget; 228 | buildConfigurationList = 37EFED75197B44A600437A0F /* Build configuration list for PBXNativeTarget "ExampleTests" */; 229 | buildPhases = ( 230 | 37EFED5D197B44A600437A0F /* Sources */, 231 | 37EFED5E197B44A600437A0F /* Frameworks */, 232 | 37EFED5F197B44A600437A0F /* Resources */, 233 | ); 234 | buildRules = ( 235 | ); 236 | dependencies = ( 237 | 37EFED67197B44A600437A0F /* PBXTargetDependency */, 238 | ); 239 | name = ExampleTests; 240 | productName = ExampleTests; 241 | productReference = 37EFED61197B44A600437A0F /* ExampleTests.xctest */; 242 | productType = "com.apple.product-type.bundle.unit-test"; 243 | }; 244 | /* End PBXNativeTarget section */ 245 | 246 | /* Begin PBXProject section */ 247 | 37EFED38197B44A600437A0F /* Project object */ = { 248 | isa = PBXProject; 249 | attributes = { 250 | LastUpgradeCheck = 0510; 251 | ORGANIZATIONNAME = "Cyrillian, Inc."; 252 | TargetAttributes = { 253 | 37EFED60197B44A600437A0F = { 254 | TestTargetID = 37EFED3F197B44A600437A0F; 255 | }; 256 | }; 257 | }; 258 | buildConfigurationList = 37EFED3B197B44A600437A0F /* Build configuration list for PBXProject "Example" */; 259 | compatibilityVersion = "Xcode 3.2"; 260 | developmentRegion = English; 261 | hasScannedForEncodings = 0; 262 | knownRegions = ( 263 | en, 264 | Base, 265 | ); 266 | mainGroup = 37EFED37197B44A600437A0F; 267 | productRefGroup = 37EFED41197B44A600437A0F /* Products */; 268 | projectDirPath = ""; 269 | projectRoot = ""; 270 | targets = ( 271 | 37EFED3F197B44A600437A0F /* Example */, 272 | 37EFED60197B44A600437A0F /* ExampleTests */, 273 | ); 274 | }; 275 | /* End PBXProject section */ 276 | 277 | /* Begin PBXResourcesBuildPhase section */ 278 | 37EFED3E197B44A600437A0F /* Resources */ = { 279 | isa = PBXResourcesBuildPhase; 280 | buildActionMask = 2147483647; 281 | files = ( 282 | 37EFED5C197B44A600437A0F /* Images.xcassets in Resources */, 283 | 37EFED4E197B44A600437A0F /* InfoPlist.strings in Resources */, 284 | 37EFED87197B487E00437A0F /* LICENSE in Resources */, 285 | 37EFED57197B44A600437A0F /* Main.storyboard in Resources */, 286 | ); 287 | runOnlyForDeploymentPostprocessing = 0; 288 | }; 289 | 37EFED5F197B44A600437A0F /* Resources */ = { 290 | isa = PBXResourcesBuildPhase; 291 | buildActionMask = 2147483647; 292 | files = ( 293 | 37EFED6D197B44A600437A0F /* InfoPlist.strings in Resources */, 294 | ); 295 | runOnlyForDeploymentPostprocessing = 0; 296 | }; 297 | /* End PBXResourcesBuildPhase section */ 298 | 299 | /* Begin PBXSourcesBuildPhase section */ 300 | 37EFED3C197B44A600437A0F /* Sources */ = { 301 | isa = PBXSourcesBuildPhase; 302 | buildActionMask = 2147483647; 303 | files = ( 304 | 37EFED8F197B596000437A0F /* CYRKeyboardButtonView.m in Sources */, 305 | 37EFED88197B487E00437A0F /* TurtleBezierPath.m in Sources */, 306 | 37EFED5A197B44A600437A0F /* ViewController.m in Sources */, 307 | 37EFED54197B44A600437A0F /* AppDelegate.m in Sources */, 308 | 37EFED50197B44A600437A0F /* main.m in Sources */, 309 | 37EFED8B197B594000437A0F /* CYRKeyboardButton.m in Sources */, 310 | ); 311 | runOnlyForDeploymentPostprocessing = 0; 312 | }; 313 | 37EFED5D197B44A600437A0F /* Sources */ = { 314 | isa = PBXSourcesBuildPhase; 315 | buildActionMask = 2147483647; 316 | files = ( 317 | 37EFED6F197B44A600437A0F /* ExampleTests.m in Sources */, 318 | ); 319 | runOnlyForDeploymentPostprocessing = 0; 320 | }; 321 | /* End PBXSourcesBuildPhase section */ 322 | 323 | /* Begin PBXTargetDependency section */ 324 | 37EFED67197B44A600437A0F /* PBXTargetDependency */ = { 325 | isa = PBXTargetDependency; 326 | target = 37EFED3F197B44A600437A0F /* Example */; 327 | targetProxy = 37EFED66197B44A600437A0F /* PBXContainerItemProxy */; 328 | }; 329 | /* End PBXTargetDependency section */ 330 | 331 | /* Begin PBXVariantGroup section */ 332 | 37EFED4C197B44A600437A0F /* InfoPlist.strings */ = { 333 | isa = PBXVariantGroup; 334 | children = ( 335 | 37EFED4D197B44A600437A0F /* en */, 336 | ); 337 | name = InfoPlist.strings; 338 | sourceTree = ""; 339 | }; 340 | 37EFED55197B44A600437A0F /* Main.storyboard */ = { 341 | isa = PBXVariantGroup; 342 | children = ( 343 | 37EFED56197B44A600437A0F /* Base */, 344 | ); 345 | name = Main.storyboard; 346 | sourceTree = ""; 347 | }; 348 | 37EFED6B197B44A600437A0F /* InfoPlist.strings */ = { 349 | isa = PBXVariantGroup; 350 | children = ( 351 | 37EFED6C197B44A600437A0F /* en */, 352 | ); 353 | name = InfoPlist.strings; 354 | sourceTree = ""; 355 | }; 356 | /* End PBXVariantGroup section */ 357 | 358 | /* Begin XCBuildConfiguration section */ 359 | 37EFED70197B44A600437A0F /* Debug */ = { 360 | isa = XCBuildConfiguration; 361 | buildSettings = { 362 | ALWAYS_SEARCH_USER_PATHS = NO; 363 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 364 | CLANG_CXX_LIBRARY = "libc++"; 365 | CLANG_ENABLE_MODULES = YES; 366 | CLANG_ENABLE_OBJC_ARC = YES; 367 | CLANG_WARN_BOOL_CONVERSION = YES; 368 | CLANG_WARN_CONSTANT_CONVERSION = YES; 369 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 370 | CLANG_WARN_EMPTY_BODY = YES; 371 | CLANG_WARN_ENUM_CONVERSION = YES; 372 | CLANG_WARN_INT_CONVERSION = YES; 373 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 374 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 375 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 376 | COPY_PHASE_STRIP = NO; 377 | GCC_C_LANGUAGE_STANDARD = gnu99; 378 | GCC_DYNAMIC_NO_PIC = NO; 379 | GCC_OPTIMIZATION_LEVEL = 0; 380 | GCC_PREPROCESSOR_DEFINITIONS = ( 381 | "DEBUG=1", 382 | "$(inherited)", 383 | ); 384 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 385 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 386 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 387 | GCC_WARN_UNDECLARED_SELECTOR = YES; 388 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 389 | GCC_WARN_UNUSED_FUNCTION = YES; 390 | GCC_WARN_UNUSED_VARIABLE = YES; 391 | IPHONEOS_DEPLOYMENT_TARGET = 7.1; 392 | ONLY_ACTIVE_ARCH = YES; 393 | SDKROOT = iphoneos; 394 | }; 395 | name = Debug; 396 | }; 397 | 37EFED71197B44A600437A0F /* Release */ = { 398 | isa = XCBuildConfiguration; 399 | buildSettings = { 400 | ALWAYS_SEARCH_USER_PATHS = NO; 401 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 402 | CLANG_CXX_LIBRARY = "libc++"; 403 | CLANG_ENABLE_MODULES = YES; 404 | CLANG_ENABLE_OBJC_ARC = YES; 405 | CLANG_WARN_BOOL_CONVERSION = YES; 406 | CLANG_WARN_CONSTANT_CONVERSION = YES; 407 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 408 | CLANG_WARN_EMPTY_BODY = YES; 409 | CLANG_WARN_ENUM_CONVERSION = YES; 410 | CLANG_WARN_INT_CONVERSION = YES; 411 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 412 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 413 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 414 | COPY_PHASE_STRIP = YES; 415 | ENABLE_NS_ASSERTIONS = NO; 416 | GCC_C_LANGUAGE_STANDARD = gnu99; 417 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 418 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 419 | GCC_WARN_UNDECLARED_SELECTOR = YES; 420 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 421 | GCC_WARN_UNUSED_FUNCTION = YES; 422 | GCC_WARN_UNUSED_VARIABLE = YES; 423 | IPHONEOS_DEPLOYMENT_TARGET = 7.1; 424 | SDKROOT = iphoneos; 425 | VALIDATE_PRODUCT = YES; 426 | }; 427 | name = Release; 428 | }; 429 | 37EFED73197B44A600437A0F /* Debug */ = { 430 | isa = XCBuildConfiguration; 431 | buildSettings = { 432 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 433 | ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; 434 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 435 | GCC_PREFIX_HEADER = "Example/Example-Prefix.pch"; 436 | INFOPLIST_FILE = "Example/Example-Info.plist"; 437 | IPHONEOS_DEPLOYMENT_TARGET = 7.1; 438 | PRODUCT_NAME = "$(TARGET_NAME)"; 439 | TARGETED_DEVICE_FAMILY = "1,2"; 440 | WRAPPER_EXTENSION = app; 441 | }; 442 | name = Debug; 443 | }; 444 | 37EFED74197B44A600437A0F /* Release */ = { 445 | isa = XCBuildConfiguration; 446 | buildSettings = { 447 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 448 | ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; 449 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 450 | GCC_PREFIX_HEADER = "Example/Example-Prefix.pch"; 451 | INFOPLIST_FILE = "Example/Example-Info.plist"; 452 | IPHONEOS_DEPLOYMENT_TARGET = 7.1; 453 | PRODUCT_NAME = "$(TARGET_NAME)"; 454 | TARGETED_DEVICE_FAMILY = "1,2"; 455 | WRAPPER_EXTENSION = app; 456 | }; 457 | name = Release; 458 | }; 459 | 37EFED76197B44A600437A0F /* Debug */ = { 460 | isa = XCBuildConfiguration; 461 | buildSettings = { 462 | BUNDLE_LOADER = "$(BUILT_PRODUCTS_DIR)/Example.app/Example"; 463 | FRAMEWORK_SEARCH_PATHS = ( 464 | "$(SDKROOT)/Developer/Library/Frameworks", 465 | "$(inherited)", 466 | "$(DEVELOPER_FRAMEWORKS_DIR)", 467 | ); 468 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 469 | GCC_PREFIX_HEADER = "Example/Example-Prefix.pch"; 470 | GCC_PREPROCESSOR_DEFINITIONS = ( 471 | "DEBUG=1", 472 | "$(inherited)", 473 | ); 474 | INFOPLIST_FILE = "ExampleTests/ExampleTests-Info.plist"; 475 | PRODUCT_NAME = "$(TARGET_NAME)"; 476 | TEST_HOST = "$(BUNDLE_LOADER)"; 477 | WRAPPER_EXTENSION = xctest; 478 | }; 479 | name = Debug; 480 | }; 481 | 37EFED77197B44A600437A0F /* Release */ = { 482 | isa = XCBuildConfiguration; 483 | buildSettings = { 484 | BUNDLE_LOADER = "$(BUILT_PRODUCTS_DIR)/Example.app/Example"; 485 | FRAMEWORK_SEARCH_PATHS = ( 486 | "$(SDKROOT)/Developer/Library/Frameworks", 487 | "$(inherited)", 488 | "$(DEVELOPER_FRAMEWORKS_DIR)", 489 | ); 490 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 491 | GCC_PREFIX_HEADER = "Example/Example-Prefix.pch"; 492 | INFOPLIST_FILE = "ExampleTests/ExampleTests-Info.plist"; 493 | PRODUCT_NAME = "$(TARGET_NAME)"; 494 | TEST_HOST = "$(BUNDLE_LOADER)"; 495 | WRAPPER_EXTENSION = xctest; 496 | }; 497 | name = Release; 498 | }; 499 | /* End XCBuildConfiguration section */ 500 | 501 | /* Begin XCConfigurationList section */ 502 | 37EFED3B197B44A600437A0F /* Build configuration list for PBXProject "Example" */ = { 503 | isa = XCConfigurationList; 504 | buildConfigurations = ( 505 | 37EFED70197B44A600437A0F /* Debug */, 506 | 37EFED71197B44A600437A0F /* Release */, 507 | ); 508 | defaultConfigurationIsVisible = 0; 509 | defaultConfigurationName = Release; 510 | }; 511 | 37EFED72197B44A600437A0F /* Build configuration list for PBXNativeTarget "Example" */ = { 512 | isa = XCConfigurationList; 513 | buildConfigurations = ( 514 | 37EFED73197B44A600437A0F /* Debug */, 515 | 37EFED74197B44A600437A0F /* Release */, 516 | ); 517 | defaultConfigurationIsVisible = 0; 518 | defaultConfigurationName = Release; 519 | }; 520 | 37EFED75197B44A600437A0F /* Build configuration list for PBXNativeTarget "ExampleTests" */ = { 521 | isa = XCConfigurationList; 522 | buildConfigurations = ( 523 | 37EFED76197B44A600437A0F /* Debug */, 524 | 37EFED77197B44A600437A0F /* Release */, 525 | ); 526 | defaultConfigurationIsVisible = 0; 527 | defaultConfigurationName = Release; 528 | }; 529 | /* End XCConfigurationList section */ 530 | }; 531 | rootObject = 37EFED38197B44A600437A0F /* Project object */; 532 | } 533 | -------------------------------------------------------------------------------- /Example/Example.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Example/Example.xcodeproj/project.xcworkspace/xcshareddata/Example.xccheckout: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDESourceControlProjectFavoriteDictionaryKey 6 | 7 | IDESourceControlProjectIdentifier 8 | 74625640-C73B-49E5-9FFD-23D1617F9A9A 9 | IDESourceControlProjectName 10 | Example 11 | IDESourceControlProjectOriginsDictionary 12 | 13 | 81FBBA09-7A3A-46BC-ABCF-7796099701F5 14 | https://github.com/illyabusigin/CYRKeyboardButton.git 15 | 16 | IDESourceControlProjectPath 17 | Example/Example/Example.xcodeproj/project.xcworkspace 18 | IDESourceControlProjectRelativeInstallPathDictionary 19 | 20 | 81FBBA09-7A3A-46BC-ABCF-7796099701F5 21 | ../../../.. 22 | 23 | IDESourceControlProjectURL 24 | https://github.com/illyabusigin/CYRKeyboardButton.git 25 | IDESourceControlProjectVersion 26 | 110 27 | IDESourceControlProjectWCCIdentifier 28 | 81FBBA09-7A3A-46BC-ABCF-7796099701F5 29 | IDESourceControlProjectWCConfigurations 30 | 31 | 32 | IDESourceControlRepositoryExtensionIdentifierKey 33 | public.vcs.git 34 | IDESourceControlWCCIdentifierKey 35 | 81FBBA09-7A3A-46BC-ABCF-7796099701F5 36 | IDESourceControlWCCName 37 | CYRKeyboardButton 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /Example/Example.xcodeproj/xcuserdata/GuestPW.xcuserdatad/xcschemes/Example.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 49 | 50 | 51 | 52 | 61 | 62 | 68 | 69 | 70 | 71 | 72 | 73 | 79 | 80 | 86 | 87 | 88 | 89 | 91 | 92 | 95 | 96 | 97 | -------------------------------------------------------------------------------- /Example/Example.xcodeproj/xcuserdata/GuestPW.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | Example.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | 37EFED3F197B44A600437A0F 16 | 17 | primary 18 | 19 | 20 | 37EFED60197B44A600437A0F 21 | 22 | primary 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /Example/Example/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // Example 4 | // 5 | // Created by Illya Busigin on 7/19/14. 6 | // Copyright (c) 2014 Cyrillian, Inc. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface AppDelegate : UIResponder 12 | 13 | @property (strong, nonatomic) UIWindow *window; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /Example/Example/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // Example 4 | // 5 | // Created by Illya Busigin on 7/19/14. 6 | // Copyright (c) 2014 Cyrillian, Inc. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | 11 | @implementation AppDelegate 12 | 13 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 14 | { 15 | // Override point for customization after application launch. 16 | return YES; 17 | } 18 | 19 | - (void)applicationWillResignActive:(UIApplication *)application 20 | { 21 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 22 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 23 | } 24 | 25 | - (void)applicationDidEnterBackground:(UIApplication *)application 26 | { 27 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 28 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 29 | } 30 | 31 | - (void)applicationWillEnterForeground:(UIApplication *)application 32 | { 33 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 34 | } 35 | 36 | - (void)applicationDidBecomeActive:(UIApplication *)application 37 | { 38 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 39 | } 40 | 41 | - (void)applicationWillTerminate:(UIApplication *)application 42 | { 43 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 44 | } 45 | 46 | @end 47 | -------------------------------------------------------------------------------- /Example/Example/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | CYRKeyboard button is a near pixel perfect control that replicates apples keyboard button functionality. This control supports styling, UIAppearance, auto layout, and much more. 39 | 40 | This sample has a custom keyboard toolbar CYRKeyboardButton. Go ahead and give it a try! 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /Example/Example/Example-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | ${PRODUCT_NAME} 9 | CFBundleExecutable 10 | ${EXECUTABLE_NAME} 11 | CFBundleIdentifier 12 | com.cyrillian.${PRODUCT_NAME:rfc1034identifier} 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | ${PRODUCT_NAME} 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1.0 25 | LSRequiresIPhoneOS 26 | 27 | NSMainNibFile~ipad 28 | Main-iPad 29 | UIMainStoryboardFile 30 | Main 31 | UIRequiredDeviceCapabilities 32 | 33 | armv7 34 | 35 | UISupportedInterfaceOrientations 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationLandscapeLeft 39 | UIInterfaceOrientationLandscapeRight 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /Example/Example/Example-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header 3 | // 4 | // The contents of this file are implicitly included at the beginning of every source file. 5 | // 6 | 7 | #import 8 | 9 | #ifndef __IPHONE_5_0 10 | #warning "This project uses features only available in iOS SDK 5.0 and later." 11 | #endif 12 | 13 | #ifdef __OBJC__ 14 | #import 15 | #import 16 | #endif 17 | -------------------------------------------------------------------------------- /Example/Example/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "40x40", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "60x60", 16 | "scale" : "2x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Example/Example/Images.xcassets/LaunchImage.launchimage/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "orientation" : "portrait", 5 | "idiom" : "iphone", 6 | "extent" : "full-screen", 7 | "minimum-system-version" : "7.0", 8 | "scale" : "2x" 9 | }, 10 | { 11 | "orientation" : "portrait", 12 | "idiom" : "iphone", 13 | "subtype" : "retina4", 14 | "extent" : "full-screen", 15 | "minimum-system-version" : "7.0", 16 | "scale" : "2x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Example/Example/ViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.h 3 | // Example 4 | // 5 | // Created by Illya Busigin on 7/19/14. 6 | // Copyright (c) 2014 Cyrillian, Inc. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface ViewController : UIViewController 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /Example/Example/ViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.m 3 | // Example 4 | // 5 | // Created by Illya Busigin on 7/19/14. 6 | // Copyright (c) 2014 Cyrillian, Inc. All rights reserved. 7 | // 8 | 9 | #import "ViewController.h" 10 | #import "CYRKeyboardButton.h" 11 | 12 | @interface ViewController () 13 | 14 | @property (nonatomic, strong) NSMutableArray *keyboardButtons; 15 | @property (nonatomic, strong) UIInputView *numberView; 16 | 17 | @property (nonatomic, weak) IBOutlet UITextView *textView; 18 | 19 | @end 20 | 21 | @implementation ViewController 22 | 23 | #pragma mark - UIViewController 24 | 25 | - (void)viewDidLoad 26 | { 27 | [super viewDidLoad]; 28 | 29 | // Our keyboard keys 30 | NSArray *keys = @[@"1", @"2", @"3", @"4", @"5", @"6", @"7", @"8", @"9", @"0"]; 31 | self.keyboardButtons = [NSMutableArray arrayWithCapacity:keys.count]; 32 | 33 | self.numberView = [[UIInputView alloc] initWithFrame:CGRectMake(0, 0, CGRectGetWidth(self.view.frame), 45) inputViewStyle:UIInputViewStyleKeyboard]; 34 | 35 | [keys enumerateObjectsUsingBlock:^(NSString *keyString, NSUInteger idx, BOOL *stop) { 36 | CYRKeyboardButton *keyboardButton = [CYRKeyboardButton new]; 37 | keyboardButton.translatesAutoresizingMaskIntoConstraints = NO; 38 | keyboardButton.input = keyString; 39 | keyboardButton.inputOptions = @[@"A", @"B", @"C", @"D"]; 40 | keyboardButton.textInput = self.textView; 41 | [self.numberView addSubview:keyboardButton]; 42 | 43 | [self.keyboardButtons addObject:keyboardButton]; 44 | }]; 45 | 46 | [self updateConstraintsForOrientation:self.interfaceOrientation]; 47 | self.textView.inputAccessoryView = self.numberView; 48 | 49 | // Subscribe to keyboard notifications 50 | [[NSNotificationCenter defaultCenter] addObserver:self 51 | selector:@selector(keyboardWillShow:) 52 | name:UIKeyboardWillShowNotification 53 | object:nil]; 54 | 55 | [[NSNotificationCenter defaultCenter] addObserver:self 56 | selector:@selector(keyboardWillHide:) 57 | name:UIKeyboardWillHideNotification 58 | object:nil]; 59 | 60 | } 61 | 62 | - (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration 63 | { 64 | [self updateConstraintsForOrientation:toInterfaceOrientation]; 65 | } 66 | 67 | - (void)dealloc 68 | { 69 | [[NSNotificationCenter defaultCenter] removeObserver:self]; 70 | } 71 | 72 | #pragma mark - Constraint Management 73 | 74 | - (void)updateConstraintsForOrientation:(UIInterfaceOrientation)orientation 75 | { 76 | // Remove any existing constraints 77 | [self.numberView removeConstraints:self.numberView.constraints]; 78 | 79 | // Create our constraints 80 | NSMutableDictionary *bindings = [NSMutableDictionary dictionary]; 81 | NSMutableString *visualFormatConstants = [NSMutableString string]; 82 | NSDictionary *metrics = nil; 83 | 84 | // Setup our metrics based on orientation 85 | if (UIInterfaceOrientationIsPortrait(orientation)) { 86 | metrics = @{ 87 | @"margin" : @(3), 88 | @"spacing" : @(6) 89 | }; 90 | } else { 91 | metrics = @{ 92 | @"margin" : @(22), 93 | @"spacing" : @(5) 94 | }; 95 | } 96 | 97 | // Build the visual format string 98 | [self.keyboardButtons enumerateObjectsUsingBlock:^(CYRKeyboardButton *button, NSUInteger idx, BOOL *stop) { 99 | NSString *binding = [NSString stringWithFormat:@"keyboardButton%i", idx]; 100 | [bindings setObject:button forKey:binding]; 101 | 102 | if (idx == 0) { 103 | [visualFormatConstants appendString:[NSString stringWithFormat:@"H:|-margin-[%@]", binding]]; 104 | } else if (idx < self.keyboardButtons.count - 1) { 105 | [visualFormatConstants appendString:[NSString stringWithFormat:@"-spacing-[%@]", binding]]; 106 | } else { 107 | [visualFormatConstants appendString:[NSString stringWithFormat:@"-spacing-[%@]-margin-|", binding]]; 108 | } 109 | }]; 110 | 111 | // Apply horizontal constraints 112 | [self.numberView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:visualFormatConstants options:0 metrics:metrics views:bindings]]; 113 | 114 | // Apply vertical constraints 115 | [bindings enumerateKeysAndObjectsUsingBlock:^(NSString *binding, id obj, BOOL *stop) { 116 | NSString *format = [NSString stringWithFormat:@"V:|-6-[%@]|", binding]; 117 | [self.numberView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:format options:0 metrics:nil views:bindings]]; 118 | }]; 119 | 120 | // Add width constraint 121 | [self.keyboardButtons enumerateObjectsUsingBlock:^(CYRKeyboardButton *button, NSUInteger idx, BOOL *stop) { 122 | if (idx > 0) { 123 | CYRKeyboardButton *previousButton = self.keyboardButtons[idx - 1]; 124 | 125 | [self.numberView addConstraint:[NSLayoutConstraint constraintWithItem:button attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:previousButton attribute:NSLayoutAttributeWidth multiplier:1 constant:0]]; 126 | } 127 | }]; 128 | } 129 | 130 | #pragma mark - UITextViewDelegate 131 | 132 | - (void)textViewDidBeginEditing:(UITextView *)textView 133 | { 134 | UIBarButtonItem *doneButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone target:textView action:@selector(resignFirstResponder)]; 135 | 136 | [self.navigationItem setRightBarButtonItem:doneButton animated:YES]; 137 | } 138 | 139 | - (void)textViewDidEndEditing:(UITextView *)textView 140 | { 141 | [self.navigationItem setRightBarButtonItem:nil animated:YES]; 142 | } 143 | 144 | #pragma mark - UIKeyboard 145 | 146 | - (void)keyboardWillShow:(NSNotification *)notification 147 | { 148 | if ([self.textView isFirstResponder]) { 149 | NSDictionary *info = [notification userInfo]; 150 | CGSize kbSize = [[info objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].size; 151 | CGFloat duration = [[info objectForKey:UIKeyboardAnimationDurationUserInfoKey] doubleValue]; 152 | 153 | [UIView animateWithDuration:duration 154 | animations:^{ 155 | self.textView.contentInset = UIEdgeInsetsMake(self.textView.contentInset.top, self.textView.contentInset.left, kbSize.height, 0); 156 | self.textView.scrollIndicatorInsets = UIEdgeInsetsMake(self.textView.contentInset.top, self.textView.scrollIndicatorInsets.left, kbSize.height, 0); 157 | }]; 158 | } 159 | } 160 | 161 | - (void)keyboardWillHide:(NSNotification *)notification 162 | { 163 | if ([self.textView isFirstResponder]) { 164 | NSDictionary *info = [notification userInfo]; 165 | CGFloat duration = [[info objectForKey:UIKeyboardAnimationDurationUserInfoKey] doubleValue]; 166 | 167 | [UIView animateWithDuration:duration 168 | animations:^{ 169 | self.textView.contentInset = UIEdgeInsetsMake(self.textView.contentInset.top, self.textView.contentInset.left, 0, 0); 170 | self.textView.scrollIndicatorInsets = UIEdgeInsetsMake(self.textView.contentInset.top, self.textView.scrollIndicatorInsets.left, 0, 0); 171 | }]; 172 | } 173 | } 174 | 175 | @end 176 | -------------------------------------------------------------------------------- /Example/Example/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /Example/Example/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // Example 4 | // 5 | // Created by Illya Busigin on 7/19/14. 6 | // Copyright (c) 2014 Cyrillian, Inc. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | #import "AppDelegate.h" 12 | 13 | int main(int argc, char * argv[]) 14 | { 15 | @autoreleasepool { 16 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Example/ExampleTests/ExampleTests-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | com.cyrillian.${PRODUCT_NAME:rfc1034identifier} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundlePackageType 14 | BNDL 15 | CFBundleShortVersionString 16 | 1.0 17 | CFBundleSignature 18 | ???? 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /Example/ExampleTests/ExampleTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // ExampleTests.m 3 | // ExampleTests 4 | // 5 | // Created by Illya Busigin on 7/19/14. 6 | // Copyright (c) 2014 Cyrillian, Inc. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface ExampleTests : XCTestCase 12 | 13 | @end 14 | 15 | @implementation ExampleTests 16 | 17 | - (void)setUp 18 | { 19 | [super setUp]; 20 | // Put setup code here. This method is called before the invocation of each test method in the class. 21 | } 22 | 23 | - (void)tearDown 24 | { 25 | // Put teardown code here. This method is called after the invocation of each test method in the class. 26 | [super tearDown]; 27 | } 28 | 29 | - (void)testExample 30 | { 31 | XCTFail(@"No implementation for \"%s\"", __PRETTY_FUNCTION__); 32 | } 33 | 34 | @end 35 | -------------------------------------------------------------------------------- /Example/ExampleTests/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /Example/Vendor/TurtleBezierPath/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 Nigel Timothy Barber 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | 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, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /Example/Vendor/TurtleBezierPath/TurtleBezierPath.h: -------------------------------------------------------------------------------- 1 | // 2 | // TurtleBezierPath.h 3 | // TurtleBezierPath demo 4 | // 5 | // Created by Nigel Barber on 09/12/2013. 6 | // Copyright (c) 2013 Nigel Barber. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface TurtleBezierPath : UIBezierPath 12 | 13 | @property( nonatomic, assign ) CGFloat bearing; 14 | @property( nonatomic, assign ) BOOL penUp; 15 | 16 | -(CGRect)boundsWithStroke; 17 | -(CGRect)boundsForView; 18 | 19 | -(void)home; 20 | -(void)forward:(CGFloat)distance; 21 | -(void)turn:(CGFloat)angle; 22 | -(void)leftArc:(CGFloat)radius turn:(CGFloat)angle; 23 | -(void)rightArc:(CGFloat)radius turn:(CGFloat)angle; 24 | -(void)down; 25 | -(void)up; 26 | 27 | -(void)centreInBounds:(CGRect)bounds; 28 | 29 | @end 30 | -------------------------------------------------------------------------------- /Example/Vendor/TurtleBezierPath/TurtleBezierPath.m: -------------------------------------------------------------------------------- 1 | // 2 | // TurtleBezierPath.m 3 | // TurtleBezierPath demo 4 | // 5 | // Created by Nigel Barber on 09/12/2013. 6 | // Copyright (c) 2013 Nigel Barber. All rights reserved. 7 | // 8 | 9 | #import "TurtleBezierPath.h" 10 | 11 | @implementation TurtleBezierPath 12 | 13 | 14 | #pragma mark - NSCoding 15 | 16 | -(void)encodeWithCoder:(NSCoder *)aCoder 17 | { 18 | [ super encodeWithCoder:aCoder ]; 19 | 20 | [ aCoder encodeFloat:self.bearing forKey:@"bearing" ]; 21 | [ aCoder encodeBool:self.penUp forKey:@"penUp" ]; 22 | } 23 | 24 | - (id)initWithCoder:(NSCoder *)aDecoder 25 | { 26 | if( self = [ super initWithCoder:aDecoder ]) 27 | { 28 | self.bearing = [ aDecoder decodeFloatForKey:@"bearing" ]; 29 | self.penUp = [ aDecoder decodeBoolForKey:@"penUp" ]; 30 | } 31 | 32 | return self; 33 | } 34 | 35 | 36 | #pragma mark - NSCopying 37 | 38 | -(id)copyWithZone:(NSZone *)zone 39 | { 40 | TurtleBezierPath *clone = [[ TurtleBezierPath allocWithZone:zone ] init ]; 41 | clone.CGPath = self.CGPath; 42 | clone.lineCapStyle = self.lineCapStyle; 43 | clone.lineJoinStyle = self.lineJoinStyle; 44 | clone.lineWidth = self.lineWidth; 45 | clone.miterLimit = self.miterLimit; 46 | clone.flatness = self.flatness; 47 | clone.usesEvenOddFillRule = self.usesEvenOddFillRule; 48 | 49 | CGFloat phase; 50 | NSInteger count; 51 | [ self getLineDash:nil count:&count phase:&phase ]; 52 | CGFloat *lineDash = malloc( count * sizeof( CGFloat )); 53 | [ self getLineDash:lineDash count:&count phase:&phase ]; 54 | [ clone setLineDash:lineDash count:count phase:phase ]; 55 | free( lineDash ); 56 | 57 | clone.bearing = self.bearing; 58 | clone.penUp = self.penUp; 59 | 60 | return clone; 61 | } 62 | 63 | 64 | #pragma mark - Private methods 65 | 66 | -(void)arc:(CGFloat)radius turn:(CGFloat)angle clockwise:(BOOL)clockwise 67 | { 68 | CGFloat radiusTurn = ( clockwise ) ? 90.0f : -90.0f; 69 | CGFloat cgAngleBias = ( clockwise ) ? 180.0f : 0.0f; 70 | angle = ( clockwise ) ? angle : -angle; 71 | 72 | CGPoint centre = [ self toCartesian:radius bearing:self.bearing + radiusTurn origin:self.currentPoint ]; 73 | 74 | CGFloat cgStartAngle = cgAngleBias + self.bearing; 75 | CGFloat cgEndAngle = cgAngleBias + ( self.bearing + angle ); 76 | 77 | self.bearing += angle; 78 | CGPoint endPoint = [ self toCartesian:radius bearing:( self.bearing -radiusTurn ) origin:centre ]; 79 | 80 | if( self.penUp ) 81 | { 82 | [ self moveToPoint:endPoint ]; 83 | } 84 | else 85 | { 86 | [ self addArcWithCenter:centre radius:radius startAngle:radians( cgStartAngle ) endAngle:radians( cgEndAngle ) clockwise:clockwise ]; 87 | } 88 | } 89 | 90 | 91 | #pragma mark - Public methods 92 | 93 | -(CGRect)boundsWithStroke 94 | { 95 | return CGRectIntegral( CGRectInset( self.bounds, -self.lineWidth * 0.5f, -self.lineWidth * 0.5f )); 96 | } 97 | 98 | -(CGRect)boundsForView 99 | { 100 | CGRect bounds = self.boundsWithStroke; 101 | CGFloat maxWidth = MAX( fabsf( CGRectGetMinX( bounds )), fabsf( CGRectGetMaxX( bounds ))); 102 | CGFloat maxHeight = MAX( fabsf( CGRectGetMinY( bounds )), fabsf( CGRectGetMaxY( bounds ))); 103 | 104 | return CGRectMake( 0.0f, 0.0f, maxWidth * 2.0f, maxHeight * 2.0f ); 105 | } 106 | 107 | -(BOOL)isEqual:(TurtleBezierPath *)aPath 108 | { 109 | return [[ NSKeyedArchiver archivedDataWithRootObject:self ] isEqualToData:[ NSKeyedArchiver archivedDataWithRootObject:aPath ]]; 110 | } 111 | 112 | -(void)home 113 | { 114 | [ self moveToPoint:CGPointZero ]; 115 | self.bearing = 0.0f; 116 | } 117 | 118 | -(void)forward:(CGFloat)distance 119 | { 120 | CGPoint endPoint = [ self toCartesian:distance bearing:self.bearing origin:self.currentPoint ]; 121 | 122 | if( self.penUp ) 123 | { 124 | [ self moveToPoint:endPoint ]; 125 | } 126 | else 127 | { 128 | [ self addLineToPoint:endPoint ]; 129 | } 130 | } 131 | 132 | -(void)turn:(CGFloat)angle 133 | { 134 | self.bearing += angle; 135 | } 136 | 137 | -(void)leftArc:(CGFloat)radius turn:(CGFloat)angle 138 | { 139 | [ self arc:radius turn:angle clockwise:NO ]; 140 | } 141 | 142 | -(void)rightArc:(CGFloat)radius turn:(CGFloat)angle 143 | { 144 | [ self arc:radius turn:angle clockwise:YES ]; 145 | } 146 | 147 | -(void)down 148 | { 149 | self.penUp = NO; 150 | } 151 | 152 | -(void)up 153 | { 154 | self.penUp = YES; 155 | } 156 | 157 | -(void)centreInBounds:(CGRect)bounds 158 | { 159 | [ self applyTransform:CGAffineTransformMakeTranslation( bounds.size.width / 2.0f, bounds.size.height / 2.0f )]; 160 | } 161 | 162 | 163 | #pragma mark - Maths 164 | 165 | static inline CGFloat radians (CGFloat degrees) {return degrees * M_PI / 180.0;} 166 | 167 | -(CGPoint)toCartesian:(CGFloat)radius bearing:(CGFloat)bearing origin:(CGPoint)origin 168 | { 169 | CGFloat bearingInRadians = radians( bearing ); 170 | 171 | CGPoint vector = CGPointMake( radius * sinf( bearingInRadians ), -radius * cosf( bearingInRadians )); 172 | 173 | return CGPointMake( origin.x + vector.x, origin.y + vector.y ); 174 | } 175 | 176 | 177 | @end 178 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Illya Busigin 4 | Portions Copyright (c) 2013 Nigel Timothy Barber (TurtleBezierPath) 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CYRKeyboardButton 2 | 3 | [![Version](https://img.shields.io/cocoapods/v/CYRKeyboardButton.svg?style=flat)](https://github.com/illyabusigin/CYRKeyboardButton) 4 | [![License](https://img.shields.io/cocoapods/l/CYRKeyboardButton.svg?style=flat)](https://github.com/illyabusigin/CYRKeyboardButton) 5 | [![Platform](https://img.shields.io/cocoapods/p/CYRKeyboardButton.svg?style=flat)](https://github.com/illyabusigin/CYRKeyboardButton) 6 | 7 | by **Illya Busigin** 8 | 9 | - Visit my blog at [http://illyabusigin.com/](http://illyabusigin.com/) 10 | - Follow [@illyabusigin on Twitter](http://twitter.com/illyabusigin) 11 | 12 | Purpose 13 | -------------- 14 | 15 | CYRKeyboardButton is a drop-in keyboard button that mimics the look, feel, and functionality of the native iOS keyboard buttons. When building QED Solver for iOS I needed to replicate the look and feel of the native keyboard buttons. CYRKeyboardButton aims to be the definitive keyboard button control for those looking to replicate the standard keyboard functionality. Features include: 16 | - Ridiculously simple configuration 17 | - UIAppearance protocol support 18 | - Extended input options support 19 | - Robust documentation 20 | 21 | 22 | Screenshot 23 | -------------- 24 | 25 | 26 | 27 | Requirements 28 | ----------------------------- 29 | 30 | iOS 7.0 or later (**with ARC**) for iPhone, iPad and iPod touch 31 | 32 | 33 | Installation 34 | --------------- 35 | 36 | To use CYRKeyboardButton, just drag the class files into your project.. You can create CYRKeyboardButton instances programatically, or create them in Interface Builder by dragging an ordinary UIView into your view and setting its class to CYRKeyboardButton. 37 | 38 | If you are using Interface Builder, to set the custom properties of CYRKeyboardButton (ones that are not supported by regular UIViews) either create an IBOutlet for your view and set the properties in code, or use the User Defined Runtime Attributes feature in Interface Builder (introduced in Xcode 4.2 for iOS 5+). 39 | 40 | Usage 41 | --------------- 42 | 43 | ``` objective-c 44 | CYRKeyboardButton *keyboardButton = [CYRKeyboardButton new]; 45 | keyboardButton.translatesAutoresizingMaskIntoConstraints = NO; 46 | keyboardButton.input = @"A"; 47 | keyboardButton.inputOptions = @[@"A", @"B", @"C", @"D"]; 48 | keyboardButton.textInput = self.textView; 49 | [self.view addSubview:keyboardButton]; 50 | ``` 51 | 52 | Example 53 | --------------- 54 | 55 | CYRKeyboardButton includes an iPhone example project that demonstrates how to use CYRKeyboardButtons in an input accessory view with nifty autolayout sizing/spacing. 56 | 57 | Bugs & Feature Requests 58 | --------------- 59 | 60 | There is **no support** offered with this component. If you would like a feature or find a bug, please submit a feature request through the [GitHub issue tracker](http://github.com/illyabusigin/CYRKeyboardButton/issues). 61 | 62 | Pull-requests for bug-fixes and features are welcome! 63 | 64 | Attribution 65 | -------------- 66 | 67 | CYRKeyboardButton uses portions of code from the following sources. 68 | 69 | | Component | Description | License | 70 | | ------------- |:-------------:| -----:| 71 | | [TurtleBezierPath](https://github.com/mindbrix/TurtleBezierPath) | UIBezierPath subclass for Turtle Graphics | [MIT](https://github.com/mindbrix/TurtleBezierPath/blob/master/LICENSE) | 72 | -------------------------------------------------------------------------------- /Screenshots/CYRKeyboardButton.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/illyabusigin/CYRKeyboardButton/730bc6898afedd8c0fe8a802bf68704d0bf09ee4/Screenshots/CYRKeyboardButton.gif --------------------------------------------------------------------------------