Initial clean commit

This commit is contained in:
2026-03-26 12:05:37 +08:00
commit 7c3c8dffee
1177 changed files with 147248 additions and 0 deletions

View File

@@ -0,0 +1,633 @@
//
// AVSettingsView.m
// AVDemo
//
#import "AVSettingsView.h"
#import "AVConstants.h"
#import "AVConfigManager.h"
@interface AVSettingsView () <UITextFieldDelegate>
@property (nonatomic, copy) AVSettingsViewCallback callback;
@property (nonatomic, strong) AVVideoConfiguration *tempConfig;
@property (nonatomic, assign) AVSettingsFieldMask fieldsMask;
@property (nonatomic, strong) UIView *containerView;
@property (nonatomic, strong) UIScrollView *scrollView;
@property (nonatomic, strong) UIView *contentView;
@property (nonatomic, strong) UITextField *streamIdField;
@property (nonatomic, strong) UITextField *nicknameField;
@property (nonatomic, strong) UISegmentedControl *codecSegment;
@property (nonatomic, strong) UISegmentedControl *resolutionSegment;
@property (nonatomic, strong) UITextField *fpsField;
@property (nonatomic, strong) UITextField *maxBitrateField;
@property (nonatomic, strong) UITextField *minBitrateField;
@property (nonatomic, strong) UISwitch *localServerSwitch;
@property (nonatomic, strong) UITextField *localServerIPField;
@property (nonatomic, strong) NSLayoutConstraint *containerCenterYConstraint;
@property (nonatomic, weak) UITextField *activeTextField; //
@end
@implementation AVSettingsView
- (instancetype)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
// UI will be setup when showing
//
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(keyboardWillShow:)
name:UIKeyboardWillShowNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(keyboardWillHide:)
name:UIKeyboardWillHideNotification
object:nil];
}
return self;
}
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (void)setupUI {
self.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:0.5];
//
CGFloat screenHeight = CGRectGetHeight([UIScreen mainScreen].bounds);
CGFloat screenWidth = CGRectGetWidth([UIScreen mainScreen].bounds);
BOOL isLandscape = screenWidth > screenHeight;
_containerView = [[UIView alloc] init];
_containerView.backgroundColor = [UIColor systemBackgroundColor];
_containerView.layer.cornerRadius = 12;
_containerView.layer.masksToBounds = YES;
[self addSubview:_containerView];
_containerView.translatesAutoresizingMaskIntoConstraints = NO;
_containerCenterYConstraint = [_containerView.centerYAnchor constraintEqualToAnchor:self.centerYAnchor];
//
CGFloat maxHeight = isLandscape ? (screenHeight * 0.85) : 600; // 使85%
[NSLayoutConstraint activateConstraints:@[
[_containerView.leadingAnchor constraintEqualToAnchor:self.leadingAnchor constant:20],
[_containerView.trailingAnchor constraintEqualToAnchor:self.trailingAnchor constant:-20],
_containerCenterYConstraint,
[_containerView.heightAnchor constraintLessThanOrEqualToConstant:maxHeight]
]];
// Add dismiss button
UIButton *dismissButton = [UIButton buttonWithType:UIButtonTypeSystem];
[dismissButton setTitle:@"×" forState:UIControlStateNormal];
dismissButton.titleLabel.font = [UIFont systemFontOfSize:30];
[dismissButton addTarget:self action:@selector(dismiss) forControlEvents:UIControlEventTouchUpInside];
[_containerView addSubview:dismissButton];
dismissButton.translatesAutoresizingMaskIntoConstraints = NO;
[NSLayoutConstraint activateConstraints:@[
[dismissButton.topAnchor constraintEqualToAnchor:_containerView.topAnchor constant:10],
[dismissButton.trailingAnchor constraintEqualToAnchor:_containerView.trailingAnchor constant:-10],
[dismissButton.widthAnchor constraintEqualToConstant:44],
[dismissButton.heightAnchor constraintEqualToConstant:44]
]];
// Title
UILabel *titleLabel = [[UILabel alloc] init];
titleLabel.text = @"设置";
titleLabel.font = [UIFont systemFontOfSize:20 weight:UIFontWeightBold];
[_containerView addSubview:titleLabel];
titleLabel.translatesAutoresizingMaskIntoConstraints = NO;
[NSLayoutConstraint activateConstraints:@[
[titleLabel.topAnchor constraintEqualToAnchor:_containerView.topAnchor constant:20],
[titleLabel.centerXAnchor constraintEqualToAnchor:_containerView.centerXAnchor]
]];
UIScrollView *scrollView = [[UIScrollView alloc] init];
scrollView.keyboardDismissMode = UIScrollViewKeyboardDismissModeInteractive; //
[_containerView addSubview:scrollView];
_scrollView = scrollView;
// scrollView
CGFloat buttonAreaHeight = isLandscape ? 64 : 80; //
scrollView.translatesAutoresizingMaskIntoConstraints = NO;
[NSLayoutConstraint activateConstraints:@[
[scrollView.topAnchor constraintEqualToAnchor:titleLabel.bottomAnchor constant:20],
[scrollView.leadingAnchor constraintEqualToAnchor:_containerView.leadingAnchor],
[scrollView.trailingAnchor constraintEqualToAnchor:_containerView.trailingAnchor],
[scrollView.bottomAnchor constraintEqualToAnchor:_containerView.bottomAnchor constant:-buttonAreaHeight]
]];
UIView *contentView = [[UIView alloc] init];
[scrollView addSubview:contentView];
_contentView = contentView; //
contentView.translatesAutoresizingMaskIntoConstraints = NO;
[NSLayoutConstraint activateConstraints:@[
[contentView.topAnchor constraintEqualToAnchor:scrollView.topAnchor],
[contentView.leadingAnchor constraintEqualToAnchor:scrollView.leadingAnchor],
[contentView.trailingAnchor constraintEqualToAnchor:scrollView.trailingAnchor],
[contentView.bottomAnchor constraintEqualToAnchor:scrollView.bottomAnchor],
[contentView.widthAnchor constraintEqualToAnchor:scrollView.widthAnchor]
]];
// Add form elements (similar to settings VC)
CGFloat topOffset = 10;
CGFloat spacing = 20;
// Stream ID (conditional)
if (self.fieldsMask & AVSettingsFieldStreamId) {
UILabel *streamIdLabel = [[UILabel alloc] init];
streamIdLabel.text = @"Stream ID";
streamIdLabel.font = [UIFont systemFontOfSize:14 weight:UIFontWeightMedium];
[contentView addSubview:streamIdLabel];
streamIdLabel.translatesAutoresizingMaskIntoConstraints = NO;
[NSLayoutConstraint activateConstraints:@[
[streamIdLabel.leadingAnchor constraintEqualToAnchor:contentView.leadingAnchor constant:20],
[streamIdLabel.topAnchor constraintEqualToAnchor:contentView.topAnchor constant:topOffset]
]];
topOffset += 20;
_streamIdField = [[UITextField alloc] init];
_streamIdField.placeholder = @"请输入Stream ID";
_streamIdField.borderStyle = UITextBorderStyleRoundedRect;
_streamIdField.delegate = self;
_streamIdField.returnKeyType = UIReturnKeyDone;
[contentView addSubview:_streamIdField];
_streamIdField.translatesAutoresizingMaskIntoConstraints = NO;
[NSLayoutConstraint activateConstraints:@[
[_streamIdField.leadingAnchor constraintEqualToAnchor:contentView.leadingAnchor constant:20],
[_streamIdField.trailingAnchor constraintEqualToAnchor:contentView.trailingAnchor constant:-20],
[_streamIdField.topAnchor constraintEqualToAnchor:contentView.topAnchor constant:topOffset],
[_streamIdField.heightAnchor constraintEqualToConstant:40]
]];
topOffset += 40 + spacing;
}
// Nickname (conditional)
if (self.fieldsMask & AVSettingsFieldNickname) {
UILabel *nicknameLabel = [[UILabel alloc] init];
nicknameLabel.text = @"用户昵称";
nicknameLabel.font = [UIFont systemFontOfSize:14 weight:UIFontWeightMedium];
[contentView addSubview:nicknameLabel];
nicknameLabel.translatesAutoresizingMaskIntoConstraints = NO;
[NSLayoutConstraint activateConstraints:@[
[nicknameLabel.leadingAnchor constraintEqualToAnchor:contentView.leadingAnchor constant:20],
[nicknameLabel.topAnchor constraintEqualToAnchor:contentView.topAnchor constant:topOffset]
]];
topOffset += 20;
_nicknameField = [[UITextField alloc] init];
_nicknameField.placeholder = @"请输入昵称";
_nicknameField.borderStyle = UITextBorderStyleRoundedRect;
_nicknameField.delegate = self;
_nicknameField.returnKeyType = UIReturnKeyDone;
[contentView addSubview:_nicknameField];
_nicknameField.translatesAutoresizingMaskIntoConstraints = NO;
[NSLayoutConstraint activateConstraints:@[
[_nicknameField.leadingAnchor constraintEqualToAnchor:contentView.leadingAnchor constant:20],
[_nicknameField.trailingAnchor constraintEqualToAnchor:contentView.trailingAnchor constant:-20],
[_nicknameField.topAnchor constraintEqualToAnchor:contentView.topAnchor constant:topOffset],
[_nicknameField.heightAnchor constraintEqualToConstant:40]
]];
topOffset += 40 + spacing;
}
// Video parameters (conditional)
if (self.fieldsMask & AVSettingsFieldVideoParams) {
// Resolution
UILabel *resolutionLabel = [[UILabel alloc] init];
resolutionLabel.text = @"分辨率";
resolutionLabel.font = [UIFont systemFontOfSize:14 weight:UIFontWeightMedium];
[contentView addSubview:resolutionLabel];
resolutionLabel.translatesAutoresizingMaskIntoConstraints = NO;
[NSLayoutConstraint activateConstraints:@[
[resolutionLabel.leadingAnchor constraintEqualToAnchor:contentView.leadingAnchor constant:20],
[resolutionLabel.topAnchor constraintEqualToAnchor:contentView.topAnchor constant:topOffset]
]];
topOffset += 20;
_resolutionSegment = [[UISegmentedControl alloc] initWithItems:@[@"360p", @"480p", @"540p", @"720p"]];
[contentView addSubview:_resolutionSegment];
_resolutionSegment.translatesAutoresizingMaskIntoConstraints = NO;
[NSLayoutConstraint activateConstraints:@[
[_resolutionSegment.leadingAnchor constraintEqualToAnchor:contentView.leadingAnchor constant:20],
[_resolutionSegment.trailingAnchor constraintEqualToAnchor:contentView.trailingAnchor constant:-20],
[_resolutionSegment.topAnchor constraintEqualToAnchor:contentView.topAnchor constant:topOffset],
[_resolutionSegment.heightAnchor constraintEqualToConstant:32]
]];
topOffset += 32 + spacing;
// FPS
UILabel *fpsLabel = [[UILabel alloc] init];
fpsLabel.text = @"帧率 (FPS)";
fpsLabel.font = [UIFont systemFontOfSize:14 weight:UIFontWeightMedium];
[contentView addSubview:fpsLabel];
fpsLabel.translatesAutoresizingMaskIntoConstraints = NO;
[NSLayoutConstraint activateConstraints:@[
[fpsLabel.leadingAnchor constraintEqualToAnchor:contentView.leadingAnchor constant:20],
[fpsLabel.topAnchor constraintEqualToAnchor:contentView.topAnchor constant:topOffset]
]];
topOffset += 20;
_fpsField = [[UITextField alloc] init];
_fpsField.placeholder = @"例如: 30";
_fpsField.borderStyle = UITextBorderStyleRoundedRect;
_fpsField.keyboardType = UIKeyboardTypeNumberPad;
_fpsField.delegate = self;
[self addDoneToolbarToTextField:_fpsField];
[contentView addSubview:_fpsField];
_fpsField.translatesAutoresizingMaskIntoConstraints = NO;
[NSLayoutConstraint activateConstraints:@[
[_fpsField.leadingAnchor constraintEqualToAnchor:contentView.leadingAnchor constant:20],
[_fpsField.trailingAnchor constraintEqualToAnchor:contentView.trailingAnchor constant:-20],
[_fpsField.topAnchor constraintEqualToAnchor:contentView.topAnchor constant:topOffset],
[_fpsField.heightAnchor constraintEqualToConstant:40]
]];
topOffset += 40 + spacing;
// Max Bitrate
UILabel *maxBitrateLabel = [[UILabel alloc] init];
maxBitrateLabel.text = @"最大码率 (kbps)";
maxBitrateLabel.font = [UIFont systemFontOfSize:14 weight:UIFontWeightMedium];
[contentView addSubview:maxBitrateLabel];
maxBitrateLabel.translatesAutoresizingMaskIntoConstraints = NO;
[NSLayoutConstraint activateConstraints:@[
[maxBitrateLabel.leadingAnchor constraintEqualToAnchor:contentView.leadingAnchor constant:20],
[maxBitrateLabel.topAnchor constraintEqualToAnchor:contentView.topAnchor constant:topOffset]
]];
topOffset += 20;
_maxBitrateField = [[UITextField alloc] init];
_maxBitrateField.placeholder = @"例如: 2000";
_maxBitrateField.borderStyle = UITextBorderStyleRoundedRect;
_maxBitrateField.keyboardType = UIKeyboardTypeNumberPad;
_maxBitrateField.delegate = self;
[self addDoneToolbarToTextField:_maxBitrateField];
[contentView addSubview:_maxBitrateField];
_maxBitrateField.translatesAutoresizingMaskIntoConstraints = NO;
[NSLayoutConstraint activateConstraints:@[
[_maxBitrateField.leadingAnchor constraintEqualToAnchor:contentView.leadingAnchor constant:20],
[_maxBitrateField.trailingAnchor constraintEqualToAnchor:contentView.trailingAnchor constant:-20],
[_maxBitrateField.topAnchor constraintEqualToAnchor:contentView.topAnchor constant:topOffset],
[_maxBitrateField.heightAnchor constraintEqualToConstant:40]
]];
topOffset += 40 + spacing;
// Min Bitrate
UILabel *minBitrateLabel = [[UILabel alloc] init];
minBitrateLabel.text = @"最小码率 (kbps)";
minBitrateLabel.font = [UIFont systemFontOfSize:14 weight:UIFontWeightMedium];
[contentView addSubview:minBitrateLabel];
minBitrateLabel.translatesAutoresizingMaskIntoConstraints = NO;
[NSLayoutConstraint activateConstraints:@[
[minBitrateLabel.leadingAnchor constraintEqualToAnchor:contentView.leadingAnchor constant:20],
[minBitrateLabel.topAnchor constraintEqualToAnchor:contentView.topAnchor constant:topOffset]
]];
topOffset += 20;
_minBitrateField = [[UITextField alloc] init];
_minBitrateField.placeholder = @"例如: 500";
_minBitrateField.borderStyle = UITextBorderStyleRoundedRect;
_minBitrateField.keyboardType = UIKeyboardTypeNumberPad;
_minBitrateField.delegate = self;
[self addDoneToolbarToTextField:_minBitrateField];
[contentView addSubview:_minBitrateField];
_minBitrateField.translatesAutoresizingMaskIntoConstraints = NO;
[NSLayoutConstraint activateConstraints:@[
[_minBitrateField.leadingAnchor constraintEqualToAnchor:contentView.leadingAnchor constant:20],
[_minBitrateField.trailingAnchor constraintEqualToAnchor:contentView.trailingAnchor constant:-20],
[_minBitrateField.topAnchor constraintEqualToAnchor:contentView.topAnchor constant:topOffset],
[_minBitrateField.heightAnchor constraintEqualToConstant:40]
]];
topOffset += 40 + spacing;
} // End of video parameters conditional block
// Local server (conditional)
if (self.fieldsMask & AVSettingsFieldLocalServer) {
// 线
UIView *separator = [[UIView alloc] init];
separator.backgroundColor = [UIColor separatorColor];
[contentView addSubview:separator];
separator.translatesAutoresizingMaskIntoConstraints = NO;
[NSLayoutConstraint activateConstraints:@[
[separator.leadingAnchor constraintEqualToAnchor:contentView.leadingAnchor constant:20],
[separator.trailingAnchor constraintEqualToAnchor:contentView.trailingAnchor constant:-20],
[separator.topAnchor constraintEqualToAnchor:contentView.topAnchor constant:topOffset],
[separator.heightAnchor constraintEqualToConstant:0.5]
]];
topOffset += 16;
//
UILabel *localServerLabel = [[UILabel alloc] init];
localServerLabel.text = @"本地服务器";
localServerLabel.font = [UIFont systemFontOfSize:14 weight:UIFontWeightMedium];
[contentView addSubview:localServerLabel];
localServerLabel.translatesAutoresizingMaskIntoConstraints = NO;
_localServerSwitch = [[UISwitch alloc] init];
[_localServerSwitch addTarget:self action:@selector(localServerSwitchChanged:) forControlEvents:UIControlEventValueChanged];
[contentView addSubview:_localServerSwitch];
_localServerSwitch.translatesAutoresizingMaskIntoConstraints = NO;
[NSLayoutConstraint activateConstraints:@[
[localServerLabel.leadingAnchor constraintEqualToAnchor:contentView.leadingAnchor constant:20],
[localServerLabel.centerYAnchor constraintEqualToAnchor:_localServerSwitch.centerYAnchor],
[_localServerSwitch.trailingAnchor constraintEqualToAnchor:contentView.trailingAnchor constant:-20],
[_localServerSwitch.topAnchor constraintEqualToAnchor:contentView.topAnchor constant:topOffset]
]];
topOffset += 31 + spacing;
// IP
UILabel *ipLabel = [[UILabel alloc] init];
ipLabel.text = @"服务器IP";
ipLabel.font = [UIFont systemFontOfSize:14 weight:UIFontWeightMedium];
[contentView addSubview:ipLabel];
ipLabel.translatesAutoresizingMaskIntoConstraints = NO;
[NSLayoutConstraint activateConstraints:@[
[ipLabel.leadingAnchor constraintEqualToAnchor:contentView.leadingAnchor constant:20],
[ipLabel.topAnchor constraintEqualToAnchor:contentView.topAnchor constant:topOffset]
]];
topOffset += 20;
_localServerIPField = [[UITextField alloc] init];
_localServerIPField.placeholder = @"例如: 192.168.1.219";
_localServerIPField.borderStyle = UITextBorderStyleRoundedRect;
_localServerIPField.keyboardType = UIKeyboardTypeDecimalPad;
_localServerIPField.delegate = self;
[self addDoneToolbarToTextField:_localServerIPField];
[contentView addSubview:_localServerIPField];
_localServerIPField.translatesAutoresizingMaskIntoConstraints = NO;
[NSLayoutConstraint activateConstraints:@[
[_localServerIPField.leadingAnchor constraintEqualToAnchor:contentView.leadingAnchor constant:20],
[_localServerIPField.trailingAnchor constraintEqualToAnchor:contentView.trailingAnchor constant:-20],
[_localServerIPField.topAnchor constraintEqualToAnchor:contentView.topAnchor constant:topOffset],
[_localServerIPField.heightAnchor constraintEqualToConstant:40]
]];
topOffset += 40 + spacing;
} // End of local server conditional block
[NSLayoutConstraint activateConstraints:@[
[contentView.heightAnchor constraintEqualToConstant:topOffset]
]];
// Buttons
UIButton *applyButton = [UIButton buttonWithType:UIButtonTypeSystem];
[applyButton setTitle:@"应用" forState:UIControlStateNormal];
applyButton.backgroundColor = [UIColor systemBlueColor];
[applyButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
applyButton.layer.cornerRadius = 8;
[applyButton addTarget:self action:@selector(applyTapped) forControlEvents:UIControlEventTouchUpInside];
[_containerView addSubview:applyButton];
//
CGFloat buttonHeight = isLandscape ? 36 : 44; //
CGFloat buttonBottomMargin = isLandscape ? 12 : 20; //
applyButton.translatesAutoresizingMaskIntoConstraints = NO;
[NSLayoutConstraint activateConstraints:@[
[applyButton.leadingAnchor constraintEqualToAnchor:_containerView.leadingAnchor constant:20],
[applyButton.trailingAnchor constraintEqualToAnchor:_containerView.trailingAnchor constant:-20],
[applyButton.bottomAnchor constraintEqualToAnchor:_containerView.bottomAnchor constant:-buttonBottomMargin],
[applyButton.heightAnchor constraintEqualToConstant:buttonHeight]
]];
// Tap to dismiss
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(backgroundTapped:)];
[self addGestureRecognizer:tap];
}
- (void)showInViewController:(UIViewController *)viewController
withConfig:(AVVideoConfiguration *)config
fieldsMask:(AVSettingsFieldMask)fieldsMask
callback:(AVSettingsViewCallback)callback {
self.callback = callback;
self.tempConfig = [config copy];
self.fieldsMask = fieldsMask;
// Setup UI now that we have fieldsMask
[self setupUI];
// Load config values
if (_streamIdField) {
// Generate random streamId if empty or default
if (config.streamId.length == 0 || [config.streamId isEqualToString:@"stream"]) {
// Generate random 3-digit number (100-999)
NSInteger randomNumber = 100 + arc4random_uniform(900);
_streamIdField.text = [NSString stringWithFormat:@"stream%ld", (long)randomNumber];
// Update the temp config with the generated streamId
self.tempConfig.streamId = _streamIdField.text;
} else {
_streamIdField.text = config.streamId;
}
}
if (_nicknameField) {
_nicknameField.text = config.nickname;
}
if (_resolutionSegment) {
_resolutionSegment.selectedSegmentIndex = [config currentResolution];
}
if (_fpsField) {
_fpsField.text = [NSString stringWithFormat:@"%ld", (long)config.videoFrameRate];
}
if (_maxBitrateField) {
_maxBitrateField.text = [NSString stringWithFormat:@"%ld", (long)(config.videoBitRate / 1000)];
}
if (_minBitrateField) {
_minBitrateField.text = [NSString stringWithFormat:@"%ld", (long)(config.videoMinBitRate / 1000)];
}
if (_localServerSwitch) {
_localServerSwitch.on = AVConfigManager.sharedManager.localServerEnabled;
}
if (_localServerIPField) {
_localServerIPField.text = AVConfigManager.sharedManager.localServerIP;
}
self.frame = viewController.view.bounds;
self.alpha = 0;
[viewController.view addSubview:self];
[UIView animateWithDuration:0.3 animations:^{
self.alpha = 1.0;
}];
}
- (void)dismiss {
[UIView animateWithDuration:0.3 animations:^{
self.alpha = 0;
} completion:^(BOOL finished) {
[self removeFromSuperview];
}];
}
- (void)applyTapped {
// Update config with all field values
if (_streamIdField) {
self.tempConfig.streamId = _streamIdField.text.length > 0 ? _streamIdField.text : @"";
}
if (_nicknameField) {
self.tempConfig.nickname = _nicknameField.text.length > 0 ? _nicknameField.text : @"test";
}
if (_resolutionSegment) {
[self.tempConfig setResolution:_resolutionSegment.selectedSegmentIndex];
}
if (_fpsField) {
NSInteger fps = [_fpsField.text integerValue];
// Validate FPS
if (fps <= 0) {
fps = 30;
}
self.tempConfig.videoFrameRate = fps;
self.tempConfig.videoMaxKeyframeInterval = fps * 2;
}
if (_maxBitrateField) {
NSInteger maxBitrate = [_maxBitrateField.text integerValue];
// Validate max bitrate
if (maxBitrate <= 0) {
maxBitrate = 2000;
}
self.tempConfig.videoBitRate = maxBitrate * 1000; // Convert kbps to bps
}
if (_minBitrateField) {
NSInteger minBitrate = [_minBitrateField.text integerValue];
// Validate min bitrate
if (minBitrate <= 0) {
minBitrate = 500;
}
self.tempConfig.videoMinBitRate = minBitrate * 1000; // Convert kbps to bps
}
if (_localServerSwitch) {
AVConfigManager.sharedManager.localServerEnabled = _localServerSwitch.on;
}
if (_localServerIPField && _localServerIPField.text.length > 0) {
AVConfigManager.sharedManager.localServerIP = _localServerIPField.text;
}
[AVConfigManager.sharedManager saveConfig];
if (self.callback) {
self.callback(self.tempConfig);
}
[self dismiss];
}
- (void)backgroundTapped:(UITapGestureRecognizer *)recognizer {
CGPoint location = [recognizer locationInView:self];
if (!CGRectContainsPoint(_containerView.frame, location)) {
//
[self dismiss];
} else {
//
[self endEditing:YES];
}
}
#pragma mark - Keyboard Handling
- (void)keyboardWillShow:(NSNotification *)notification {
NSDictionary *userInfo = notification.userInfo;
CGRect keyboardFrame = [userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue];
CGFloat keyboardHeight = keyboardFrame.size.height;
NSTimeInterval duration = [userInfo[UIKeyboardAnimationDurationUserInfoKey] doubleValue];
UIViewAnimationCurve curve = [userInfo[UIKeyboardAnimationCurveUserInfoKey] integerValue];
// ScrollView contentInset
//
CGRect containerFrameInWindow = [_containerView convertRect:_containerView.bounds toView:self];
CGFloat containerBottom = CGRectGetMaxY(containerFrameInWindow);
CGFloat keyboardTop = CGRectGetHeight(self.bounds) - keyboardHeight;
CGFloat overlap = containerBottom - keyboardTop;
if (overlap > 0) {
// scrollView contentInset
UIEdgeInsets contentInset = _scrollView.contentInset;
contentInset.bottom = overlap + 20; // 20px
[UIView animateWithDuration:duration delay:0 options:(curve << 16) animations:^{
self.scrollView.contentInset = contentInset;
self.scrollView.scrollIndicatorInsets = contentInset;
//
if (self.activeTextField) {
[self scrollToTextField:self.activeTextField];
}
} completion:nil];
}
}
- (void)keyboardWillHide:(NSNotification *)notification {
NSDictionary *userInfo = notification.userInfo;
NSTimeInterval duration = [userInfo[UIKeyboardAnimationDurationUserInfoKey] doubleValue];
UIViewAnimationCurve curve = [userInfo[UIKeyboardAnimationCurveUserInfoKey] integerValue];
// scrollView contentInset
[UIView animateWithDuration:duration delay:0 options:(curve << 16) animations:^{
self.scrollView.contentInset = UIEdgeInsetsZero;
self.scrollView.scrollIndicatorInsets = UIEdgeInsetsZero;
} completion:nil];
}
- (void)scrollToTextField:(UITextField *)textField {
// textField scrollView
CGRect textFieldFrame = [textField convertRect:textField.bounds toView:_contentView];
//
CGFloat padding = 20;
CGRect targetRect = CGRectMake(textFieldFrame.origin.x,
textFieldFrame.origin.y - padding,
textFieldFrame.size.width,
textFieldFrame.size.height + padding * 2);
//
[_scrollView scrollRectToVisible:targetRect animated:YES];
}
#pragma mark - Helper Methods
- (void)addDoneToolbarToTextField:(UITextField *)textField {
UIToolbar *toolbar = [[UIToolbar alloc] initWithFrame:CGRectMake(0, 0, CGRectGetWidth(self.bounds), 44)];
toolbar.barStyle = UIBarStyleDefault;
UIBarButtonItem *flexSpace = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:nil action:nil];
UIBarButtonItem *doneButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone target:self action:@selector(doneButtonTapped)];
toolbar.items = @[flexSpace, doneButton];
textField.inputAccessoryView = toolbar;
}
- (void)doneButtonTapped {
[self endEditing:YES];
}
- (void)localServerSwitchChanged:(UISwitch *)sender {
// IP field only needs editing when switch is on; no visual change needed
}
#pragma mark - UITextFieldDelegate
- (void)textFieldDidBeginEditing:(UITextField *)textField {
self.activeTextField = textField;
//
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self scrollToTextField:textField];
});
}
- (void)textFieldDidEndEditing:(UITextField *)textField {
if (self.activeTextField == textField) {
self.activeTextField = nil;
}
}
- (BOOL)textFieldShouldReturn:(UITextField *)textField {
[textField resignFirstResponder];
return YES;
}
@end