OLD | NEW |
1 // Copyright 2014 The Chromium Authors. All rights reserved. | 1 // Copyright 2014 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #import "ios/chrome/browser/memory/memory_debugger.h" | 5 #import "ios/chrome/browser/memory/memory_debugger.h" |
6 | 6 |
7 #include <stdint.h> | 7 #include <stdint.h> |
8 | 8 |
9 #include <memory> | 9 #include <memory> |
10 | 10 |
11 #import "base/mac/scoped_nsobject.h" | |
12 #import "ios/chrome/browser/memory/memory_metrics.h" | 11 #import "ios/chrome/browser/memory/memory_metrics.h" |
13 #include "ios/chrome/browser/ui/ui_util.h" | 12 #include "ios/chrome/browser/ui/ui_util.h" |
14 #import "ios/chrome/browser/ui/uikit_ui_util.h" | 13 #import "ios/chrome/browser/ui/uikit_ui_util.h" |
15 | 14 |
| 15 #if !defined(__has_feature) || !__has_feature(objc_arc) |
| 16 #error "This file requires ARC support." |
| 17 #endif |
| 18 |
16 namespace { | 19 namespace { |
17 // The number of bytes in a megabyte. | 20 // The number of bytes in a megabyte. |
18 const CGFloat kNumBytesInMB = 1024 * 1024; | 21 const CGFloat kNumBytesInMB = 1024 * 1024; |
19 // The horizontal and vertical padding between subviews. | 22 // The horizontal and vertical padding between subviews. |
20 const CGFloat kPadding = 10; | 23 const CGFloat kPadding = 10; |
21 } // namespace | 24 } // namespace |
22 | 25 |
23 @implementation MemoryDebugger { | 26 @implementation MemoryDebugger { |
24 // A timer to trigger refreshes. | 27 // A timer to trigger refreshes. |
25 base::scoped_nsobject<NSTimer> _refreshTimer; | 28 NSTimer* _refreshTimer; |
26 | 29 |
27 // A timer to trigger continuous memory warnings. | 30 // A timer to trigger continuous memory warnings. |
28 base::scoped_nsobject<NSTimer> _memoryWarningTimer; | 31 NSTimer* _memoryWarningTimer; |
29 | 32 |
30 // The font to use. | 33 // The font to use. |
31 base::scoped_nsobject<UIFont> _font; | 34 UIFont* _font; |
32 | 35 |
33 // Labels for memory metrics. | 36 // Labels for memory metrics. |
34 base::scoped_nsobject<UILabel> _physicalFreeMemoryLabel; | 37 UILabel* _physicalFreeMemoryLabel; |
35 base::scoped_nsobject<UILabel> _realMemoryUsedLabel; | 38 UILabel* _realMemoryUsedLabel; |
36 base::scoped_nsobject<UILabel> _xcodeGaugeLabel; | 39 UILabel* _xcodeGaugeLabel; |
37 base::scoped_nsobject<UILabel> _dirtyVirtualMemoryLabel; | 40 UILabel* _dirtyVirtualMemoryLabel; |
38 | 41 |
39 // Inputs for memory commands. | 42 // Inputs for memory commands. |
40 base::scoped_nsobject<UITextField> _bloatField; | 43 UITextField* _bloatField; |
41 base::scoped_nsobject<UITextField> _refreshField; | 44 UITextField* _refreshField; |
42 base::scoped_nsobject<UITextField> _continuousMemoryWarningField; | 45 UITextField* _continuousMemoryWarningField; |
43 | 46 |
44 // A place to store the artifical memory bloat. | 47 // A place to store the artifical memory bloat. |
45 std::unique_ptr<uint8_t> _bloat; | 48 std::unique_ptr<uint8_t> _bloat; |
46 | 49 |
47 // Distance the view was pushed up to accomodate the keyboard. | 50 // Distance the view was pushed up to accomodate the keyboard. |
48 CGFloat _keyboardOffset; | 51 CGFloat _keyboardOffset; |
49 | 52 |
50 // The current orientation of the device. | 53 // The current orientation of the device. |
51 BOOL _currentOrientation; | 54 BOOL _currentOrientation; |
52 } | 55 } |
53 | 56 |
54 - (instancetype)init { | 57 - (instancetype)init { |
55 self = [super initWithFrame:CGRectZero]; | 58 self = [super initWithFrame:CGRectZero]; |
56 if (self) { | 59 if (self) { |
57 _font.reset([[UIFont systemFontOfSize:14] retain]); | 60 _font = [UIFont systemFontOfSize:14]; |
58 self.backgroundColor = [UIColor colorWithWhite:0.8f alpha:0.9f]; | 61 self.backgroundColor = [UIColor colorWithWhite:0.8f alpha:0.9f]; |
59 self.opaque = NO; | 62 self.opaque = NO; |
60 | 63 |
61 [self addSubviews]; | 64 [self addSubviews]; |
62 [self sizeToFit]; | 65 [self sizeToFit]; |
63 [self registerForNotifications]; | 66 [self registerForNotifications]; |
64 } | 67 } |
65 return self; | 68 return self; |
66 } | 69 } |
67 | 70 |
68 // NSTimers create a retain cycle so they must be invalidated before this | 71 // NSTimers create a retain cycle so they must be invalidated before this |
69 // instance can be deallocated. | 72 // instance can be deallocated. |
70 - (void)invalidateTimers { | 73 - (void)invalidateTimers { |
71 [_refreshTimer invalidate]; | 74 [_refreshTimer invalidate]; |
72 [_memoryWarningTimer invalidate]; | 75 [_memoryWarningTimer invalidate]; |
73 } | 76 } |
74 | 77 |
75 - (void)dealloc { | 78 - (void)dealloc { |
76 [[NSNotificationCenter defaultCenter] removeObserver:self]; | 79 [[NSNotificationCenter defaultCenter] removeObserver:self]; |
77 [super dealloc]; | |
78 } | 80 } |
79 | 81 |
80 #pragma mark UIView methods | 82 #pragma mark UIView methods |
81 | 83 |
82 - (CGSize)sizeThatFits:(CGSize)size { | 84 - (CGSize)sizeThatFits:(CGSize)size { |
83 CGFloat width = 0; | 85 CGFloat width = 0; |
84 CGFloat height = 0; | 86 CGFloat height = 0; |
85 for (UIView* subview in self.subviews) { | 87 for (UIView* subview in self.subviews) { |
86 width = MAX(width, CGRectGetMaxX(subview.frame)); | 88 width = MAX(width, CGRectGetMaxX(subview.frame)); |
87 height = MAX(height, CGRectGetMaxY(subview.frame)); | 89 height = MAX(height, CGRectGetMaxY(subview.frame)); |
88 } | 90 } |
89 return CGSizeMake(width + kPadding, height + kPadding); | 91 return CGSizeMake(width + kPadding, height + kPadding); |
90 } | 92 } |
91 | 93 |
92 #pragma mark initialization helpers | 94 #pragma mark initialization helpers |
93 | 95 |
94 - (void)addSubviews { | 96 - (void)addSubviews { |
95 // |index| is used to calculate the vertical position of each element in | 97 // |index| is used to calculate the vertical position of each element in |
96 // the debugger view. | 98 // the debugger view. |
97 NSUInteger index = 0; | 99 NSUInteger index = 0; |
98 | 100 |
99 // Display some metrics. | 101 // Display some metrics. |
100 _physicalFreeMemoryLabel.reset([[UILabel alloc] initWithFrame:CGRectZero]); | 102 _physicalFreeMemoryLabel = [[UILabel alloc] initWithFrame:CGRectZero]; |
101 [self addMetricWithName:@"Physical Free" | 103 [self addMetricWithName:@"Physical Free" |
102 atIndex:index++ | 104 atIndex:index++ |
103 usingLabel:_physicalFreeMemoryLabel]; | 105 usingLabel:_physicalFreeMemoryLabel]; |
104 _realMemoryUsedLabel.reset([[UILabel alloc] initWithFrame:CGRectZero]); | 106 _realMemoryUsedLabel = [[UILabel alloc] initWithFrame:CGRectZero]; |
105 [self addMetricWithName:@"Real Memory Used" | 107 [self addMetricWithName:@"Real Memory Used" |
106 atIndex:index++ | 108 atIndex:index++ |
107 usingLabel:_realMemoryUsedLabel]; | 109 usingLabel:_realMemoryUsedLabel]; |
108 _xcodeGaugeLabel.reset([[UILabel alloc] initWithFrame:CGRectZero]); | 110 _xcodeGaugeLabel = [[UILabel alloc] initWithFrame:CGRectZero]; |
109 [self addMetricWithName:@"Xcode Gauge" | 111 [self addMetricWithName:@"Xcode Gauge" |
110 atIndex:index++ | 112 atIndex:index++ |
111 usingLabel:_xcodeGaugeLabel]; | 113 usingLabel:_xcodeGaugeLabel]; |
112 _dirtyVirtualMemoryLabel.reset([[UILabel alloc] initWithFrame:CGRectZero]); | 114 _dirtyVirtualMemoryLabel = [[UILabel alloc] initWithFrame:CGRectZero]; |
113 [self addMetricWithName:@"Dirty VM" | 115 [self addMetricWithName:@"Dirty VM" |
114 atIndex:index++ | 116 atIndex:index++ |
115 usingLabel:_dirtyVirtualMemoryLabel]; | 117 usingLabel:_dirtyVirtualMemoryLabel]; |
116 | 118 |
117 // Since _performMemoryWarning is a private API it can't be compiled into | 119 // Since _performMemoryWarning is a private API it can't be compiled into |
118 // official builds. | 120 // official builds. |
119 // TODO(lliabraa): Figure out how to support memory warnings (or something | 121 // TODO(lliabraa): Figure out how to support memory warnings (or something |
120 // like them) in official builds. | 122 // like them) in official builds. |
121 #if CHROMIUM_BUILD | 123 #if CHROMIUM_BUILD |
122 #pragma clang diagnostic push | 124 #pragma clang diagnostic push |
123 #pragma clang diagnostic ignored "-Wundeclared-selector" | 125 #pragma clang diagnostic ignored "-Wundeclared-selector" |
124 [self addButtonWithTitle:@"Trigger Memory Warning" | 126 [self addButtonWithTitle:@"Trigger Memory Warning" |
125 target:[UIApplication sharedApplication] | 127 target:[UIApplication sharedApplication] |
126 action:@selector(_performMemoryWarning) | 128 action:@selector(_performMemoryWarning) |
127 withOrigin:[self originForSubviewAtIndex:index++]]; | 129 withOrigin:[self originForSubviewAtIndex:index++]]; |
128 #pragma clang diagnostic pop | 130 #pragma clang diagnostic pop |
129 #endif // CHROMIUM_BUILD | 131 #endif // CHROMIUM_BUILD |
130 | 132 |
131 // Display a text input to set the amount of artificial memory bloat and a | 133 // Display a text input to set the amount of artificial memory bloat and a |
132 // button to reset the bloat to zero. | 134 // button to reset the bloat to zero. |
133 _bloatField.reset([[UITextField alloc] initWithFrame:CGRectZero]); | 135 _bloatField = [[UITextField alloc] initWithFrame:CGRectZero]; |
134 [self addLabelWithText:@"Set bloat (MB)" | 136 [self addLabelWithText:@"Set bloat (MB)" |
135 input:_bloatField | 137 input:_bloatField |
136 inputTarget:self | 138 inputTarget:self |
137 inputAction:@selector(updateBloat) | 139 inputAction:@selector(updateBloat) |
138 buttonWithTitle:@"Clear" | 140 buttonWithTitle:@"Clear" |
139 buttonTarget:self | 141 buttonTarget:self |
140 buttonAction:@selector(clearBloat) | 142 buttonAction:@selector(clearBloat) |
141 atIndex:index++]; | 143 atIndex:index++]; |
142 [_bloatField setText:@"0"]; | 144 [_bloatField setText:@"0"]; |
143 [self updateBloat]; | 145 [self updateBloat]; |
144 | 146 |
145 // Since _performMemoryWarning is a private API it can't be compiled into | 147 // Since _performMemoryWarning is a private API it can't be compiled into |
146 // official builds. | 148 // official builds. |
147 // TODO(lliabraa): Figure out how to support memory warnings (or something | 149 // TODO(lliabraa): Figure out how to support memory warnings (or something |
148 // like them) in official builds. | 150 // like them) in official builds. |
149 #if CHROMIUM_BUILD | 151 #if CHROMIUM_BUILD |
150 // Display a text input to control the rate of continuous memory warnings. | 152 // Display a text input to control the rate of continuous memory warnings. |
151 _continuousMemoryWarningField.reset( | 153 _continuousMemoryWarningField = |
152 [[UITextField alloc] initWithFrame:CGRectZero]); | 154 [[UITextField alloc] initWithFrame:CGRectZero]; |
153 [self addLabelWithText:@"Set memory warning interval (secs)" | 155 [self addLabelWithText:@"Set memory warning interval (secs)" |
154 input:_continuousMemoryWarningField | 156 input:_continuousMemoryWarningField |
155 inputTarget:self | 157 inputTarget:self |
156 inputAction:@selector(updateMemoryWarningInterval) | 158 inputAction:@selector(updateMemoryWarningInterval) |
157 atIndex:index++]; | 159 atIndex:index++]; |
158 [_continuousMemoryWarningField setText:@"0.0"]; | 160 [_continuousMemoryWarningField setText:@"0.0"]; |
159 #endif // CHROMIUM_BUILD | 161 #endif // CHROMIUM_BUILD |
160 | 162 |
161 // Display a text input to control the refresh rate of the memory debugger. | 163 // Display a text input to control the refresh rate of the memory debugger. |
162 _refreshField.reset([[UITextField alloc] initWithFrame:CGRectZero]); | 164 _refreshField = [[UITextField alloc] initWithFrame:CGRectZero]; |
163 [self addLabelWithText:@"Set refresh interval (secs)" | 165 [self addLabelWithText:@"Set refresh interval (secs)" |
164 input:_refreshField | 166 input:_refreshField |
165 inputTarget:self | 167 inputTarget:self |
166 inputAction:@selector(updateRefreshInterval) | 168 inputAction:@selector(updateRefreshInterval) |
167 atIndex:index++]; | 169 atIndex:index++]; |
168 [_refreshField setText:@"0.5"]; | 170 [_refreshField setText:@"0.5"]; |
169 [self updateRefreshInterval]; | 171 [self updateRefreshInterval]; |
170 } | 172 } |
171 | 173 |
172 - (void)registerForNotifications { | 174 - (void)registerForNotifications { |
(...skipping 24 matching lines...) Expand all Loading... |
197 - (void)addMetricWithName:(NSString*)name | 199 - (void)addMetricWithName:(NSString*)name |
198 atIndex:(NSUInteger)index | 200 atIndex:(NSUInteger)index |
199 usingLabel:(UILabel*)label { | 201 usingLabel:(UILabel*)label { |
200 // The width of the view for the metric's name. | 202 // The width of the view for the metric's name. |
201 const CGFloat kNameWidth = 150; | 203 const CGFloat kNameWidth = 150; |
202 // The width of the view for each metric. | 204 // The width of the view for each metric. |
203 const CGFloat kMetricWidth = 100; | 205 const CGFloat kMetricWidth = 100; |
204 CGPoint nameOrigin = [self originForSubviewAtIndex:index]; | 206 CGPoint nameOrigin = [self originForSubviewAtIndex:index]; |
205 CGRect nameFrame = | 207 CGRect nameFrame = |
206 CGRectMake(nameOrigin.x, nameOrigin.y, kNameWidth, [_font lineHeight]); | 208 CGRectMake(nameOrigin.x, nameOrigin.y, kNameWidth, [_font lineHeight]); |
207 base::scoped_nsobject<UILabel> nameLabel( | 209 UILabel* nameLabel = [[UILabel alloc] initWithFrame:nameFrame]; |
208 [[UILabel alloc] initWithFrame:nameFrame]); | |
209 [nameLabel setText:[NSString stringWithFormat:@"%@: ", name]]; | 210 [nameLabel setText:[NSString stringWithFormat:@"%@: ", name]]; |
210 [nameLabel setFont:_font]; | 211 [nameLabel setFont:_font]; |
211 [self addSubview:nameLabel]; | 212 [self addSubview:nameLabel]; |
212 label.frame = CGRectMake(CGRectGetMaxX(nameFrame), nameFrame.origin.y, | 213 label.frame = CGRectMake(CGRectGetMaxX(nameFrame), nameFrame.origin.y, |
213 kMetricWidth, [_font lineHeight]); | 214 kMetricWidth, [_font lineHeight]); |
214 [label setFont:_font]; | 215 [label setFont:_font]; |
215 [label setTextAlignment:NSTextAlignmentRight]; | 216 [label setTextAlignment:NSTextAlignmentRight]; |
216 [self addSubview:label]; | 217 [self addSubview:label]; |
217 } | 218 } |
218 | 219 |
219 // Adds a subview for a button with the given title and target/action. | 220 // Adds a subview for a button with the given title and target/action. |
220 - (void)addButtonWithTitle:(NSString*)title | 221 - (void)addButtonWithTitle:(NSString*)title |
221 target:(id)target | 222 target:(id)target |
222 action:(SEL)action | 223 action:(SEL)action |
223 withOrigin:(CGPoint)origin { | 224 withOrigin:(CGPoint)origin { |
224 base::scoped_nsobject<UIButton> button( | 225 UIButton* button = [UIButton buttonWithType:UIButtonTypeSystem]; |
225 [[UIButton buttonWithType:UIButtonTypeSystem] retain]); | |
226 [button setTitle:title forState:UIControlStateNormal]; | 226 [button setTitle:title forState:UIControlStateNormal]; |
227 [button titleLabel].font = _font; | 227 [button titleLabel].font = _font; |
228 [[button titleLabel] setTextAlignment:NSTextAlignmentCenter]; | 228 [[button titleLabel] setTextAlignment:NSTextAlignmentCenter]; |
229 [button sizeToFit]; | 229 [button sizeToFit]; |
230 [button setFrame:CGRectMake(origin.x, origin.y, [button frame].size.width, | 230 [button setFrame:CGRectMake(origin.x, origin.y, [button frame].size.width, |
231 [_font lineHeight])]; | 231 [_font lineHeight])]; |
232 [button addTarget:target | 232 [button addTarget:target |
233 action:action | 233 action:action |
234 forControlEvents:UIControlEventTouchUpInside]; | 234 forControlEvents:UIControlEventTouchUpInside]; |
235 [self addSubview:button]; | 235 [self addSubview:button]; |
(...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
267 // The inputTarget/inputAction will be invoked when the user finishes editing | 267 // The inputTarget/inputAction will be invoked when the user finishes editing |
268 // in |input|. | 268 // in |input|. |
269 - (void)addLabelWithText:(NSString*)labelText | 269 - (void)addLabelWithText:(NSString*)labelText |
270 input:(UITextField*)input | 270 input:(UITextField*)input |
271 inputTarget:(id)inputTarget | 271 inputTarget:(id)inputTarget |
272 inputAction:(SEL)inputAction | 272 inputAction:(SEL)inputAction |
273 buttonWithTitle:(NSString*)buttonTitle | 273 buttonWithTitle:(NSString*)buttonTitle |
274 buttonTarget:(id)buttonTarget | 274 buttonTarget:(id)buttonTarget |
275 buttonAction:(SEL)buttonAction | 275 buttonAction:(SEL)buttonAction |
276 atIndex:(NSUInteger)index { | 276 atIndex:(NSUInteger)index { |
277 base::scoped_nsobject<UILabel> label( | 277 UILabel* label = [[UILabel alloc] initWithFrame:CGRectZero]; |
278 [[UILabel alloc] initWithFrame:CGRectZero]); | |
279 if (labelText) { | 278 if (labelText) { |
280 [label setText:[NSString stringWithFormat:@"%@: ", labelText]]; | 279 [label setText:[NSString stringWithFormat:@"%@: ", labelText]]; |
281 } | 280 } |
282 [label setFont:_font]; | 281 [label setFont:_font]; |
283 [label sizeToFit]; | 282 [label sizeToFit]; |
284 CGPoint labelOrigin = [self originForSubviewAtIndex:index]; | 283 CGPoint labelOrigin = [self originForSubviewAtIndex:index]; |
285 [label setFrame:CGRectOffset([label frame], labelOrigin.x, labelOrigin.y)]; | 284 [label setFrame:CGRectOffset([label frame], labelOrigin.x, labelOrigin.y)]; |
286 [self addSubview:label]; | 285 [self addSubview:label]; |
287 if (input) { | 286 if (input) { |
288 // The width of the views for each input text field. | 287 // The width of the views for each input text field. |
(...skipping 171 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
460 NSString* errorMessage = [NSString | 459 NSString* errorMessage = [NSString |
461 stringWithFormat:@"Invalid value \"%@\" for refresh interval.\n" | 460 stringWithFormat:@"Invalid value \"%@\" for refresh interval.\n" |
462 @"Must be a positive number.\n" @"Resetting to %.1f", | 461 @"Must be a positive number.\n" @"Resetting to %.1f", |
463 [_refreshField text], refreshTimerValue]; | 462 [_refreshField text], refreshTimerValue]; |
464 [self alert:errorMessage]; | 463 [self alert:errorMessage]; |
465 [_refreshField | 464 [_refreshField |
466 setText:[NSString stringWithFormat:@"%.1f", refreshTimerValue]]; | 465 setText:[NSString stringWithFormat:@"%.1f", refreshTimerValue]]; |
467 return; | 466 return; |
468 } | 467 } |
469 [_refreshTimer invalidate]; | 468 [_refreshTimer invalidate]; |
470 _refreshTimer.reset( | 469 _refreshTimer = [NSTimer scheduledTimerWithTimeInterval:refreshTimerValue |
471 [[NSTimer scheduledTimerWithTimeInterval:refreshTimerValue | 470 target:self |
472 target:self | 471 selector:@selector(refresh:) |
473 selector:@selector(refresh:) | 472 userInfo:nil |
474 userInfo:nil | 473 repeats:YES]; |
475 repeats:YES] retain]); | |
476 } | 474 } |
477 | 475 |
478 #pragma mark Memory warning interval methods | 476 #pragma mark Memory warning interval methods |
479 | 477 |
480 // Since _performMemoryWarning is a private API it can't be compiled into | 478 // Since _performMemoryWarning is a private API it can't be compiled into |
481 // official builds. | 479 // official builds. |
482 // TODO(lliabraa): Figure out how to support memory warnings (or something | 480 // TODO(lliabraa): Figure out how to support memory warnings (or something |
483 // like them) in official builds. | 481 // like them) in official builds. |
484 #if CHROMIUM_BUILD | 482 #if CHROMIUM_BUILD |
485 - (void)updateMemoryWarningInterval { | 483 - (void)updateMemoryWarningInterval { |
(...skipping 16 matching lines...) Expand all Loading... |
502 @"Turning off continuous memory warnings", | 500 @"Turning off continuous memory warnings", |
503 text]; | 501 text]; |
504 [self alert:errorMessage]; | 502 [self alert:errorMessage]; |
505 [_continuousMemoryWarningField setText:@""]; | 503 [_continuousMemoryWarningField setText:@""]; |
506 return; | 504 return; |
507 } | 505 } |
508 // If a valid value was found have the timer start triggering continuous | 506 // If a valid value was found have the timer start triggering continuous |
509 // memory warnings. | 507 // memory warnings. |
510 #pragma clang diagnostic push | 508 #pragma clang diagnostic push |
511 #pragma clang diagnostic ignored "-Wundeclared-selector" | 509 #pragma clang diagnostic ignored "-Wundeclared-selector" |
512 _memoryWarningTimer.reset( | 510 _memoryWarningTimer = |
513 [[NSTimer scheduledTimerWithTimeInterval:timerValue | 511 [NSTimer scheduledTimerWithTimeInterval:timerValue |
514 target:[UIApplication sharedApplication] | 512 target:[UIApplication sharedApplication] |
515 selector:@selector(_performMemoryWarning) | 513 selector:@selector(_performMemoryWarning) |
516 userInfo:nil | 514 userInfo:nil |
517 repeats:YES] retain]); | 515 repeats:YES]; |
518 #pragma clang diagnostic push | 516 #pragma clang diagnostic push |
519 } | 517 } |
520 #endif // CHROMIUM_BUILD | 518 #endif // CHROMIUM_BUILD |
521 | 519 |
522 #pragma mark UITextViewDelegate methods | 520 #pragma mark UITextViewDelegate methods |
523 | 521 |
524 // Dismisses the keyboard if the user hits return. | 522 // Dismisses the keyboard if the user hits return. |
525 - (BOOL)textFieldShouldReturn:(UITextField*)textField { | 523 - (BOOL)textFieldShouldReturn:(UITextField*)textField { |
526 [textField resignFirstResponder]; | 524 [textField resignFirstResponder]; |
527 return YES; | 525 return YES; |
(...skipping 22 matching lines...) Expand all Loading... |
550 [alert addAction:[UIAlertAction actionWithTitle:@"OK" | 548 [alert addAction:[UIAlertAction actionWithTitle:@"OK" |
551 style:UIAlertActionStyleDefault | 549 style:UIAlertActionStyleDefault |
552 handler:nil]]; | 550 handler:nil]]; |
553 [[[[UIApplication sharedApplication] keyWindow] rootViewController] | 551 [[[[UIApplication sharedApplication] keyWindow] rootViewController] |
554 presentViewController:alert | 552 presentViewController:alert |
555 animated:YES | 553 animated:YES |
556 completion:nil]; | 554 completion:nil]; |
557 } | 555 } |
558 | 556 |
559 @end | 557 @end |
OLD | NEW |