在计算器的仿写中,本人首次实际使用MVC构架和Mansory自动布局。
安装Mansory
首先安装cocoapods,然后在终端找到项目文件夹,执行 pod init
随后修改文件,将内容改为:
platform :ios, '12.0'
target '计算器77' do
pod 'LookinServer', :configurations => ['Debug']
pod 'Masonry'
end
作者同时安装了Lookin方便调试,随后在终端执行: pod install。
如果下载有问题,请给终端挂一个梯子,因为下载源是国外的。
就此安装完成。
框架
M:我的Model负责执行运算逻辑
V:我的View负责实现一个基本的计算器页面
C:负责联系主页面和Model,同时负责各种判断各种非法运算
页面:
我实现的页面如图。
我使用两层for循环,外层循环负责便历5行,内层循环负责遍历每一行的四个按钮
for (int i = 0; i < 5; i++) {
UIView* rowView = [[UIView alloc] init];
[self addSubview:rowView];
[rowView mas_makeConstraints:^(MASConstraintMaker *make) {
if (lastRow) {
make.top.equalTo(lastRow.mas_bottom).offset(btnSpacing);
} else {
make.top.equalTo(_topLabel.mas_bottom).offset(40);
}
make.left.right.equalTo(self).inset(20);//左右20
make.height.mas_equalTo(btnHeight);
}];
lastRow = rowView;
UIView* lastButton = nil;
int buttonsInRow = 4;
if (i == 4) buttonsInRow = 3; // 第5行只有3个
for (int j = 0; j < buttonsInRow; j++) {
NSInteger index = i * 4 + j;
if (i == 4 && j >= 1) {
index = i * 4 + j + 1;
}
NSString* title = _array[index];
UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
button.tag = 100 + index;
[button setTitle:title forState:UIControlStateNormal];
[button setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
button.layer.cornerRadius = btnHeight / 2;
button.titleLabel.font = [UIFont systemFontOfSize:42];
button.clipsToBounds = YES;
[button addTarget:self action:@selector(return:) forControlEvents:UIControlEventTouchUpInside];
if (i == 0) {
button.backgroundColor = [UIColor colorWithWhite:0.6 alpha:1];
} else if (j == 3 && i != 4) {
button.backgroundColor = [UIColor colorWithRed:236/255.0 green:146/255.0 blue:47/255.0 alpha:1];
} else {
button.backgroundColor = [UIColor colorWithWhite:0.3 alpha:1];
}
[rowView addSubview:button];
[button mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.bottom.equalTo(rowView);
if (i == 4) {
if (j == 0) {
// "0" 按钮占两倍宽度
make.left.equalTo(rowView);
make.width.equalTo(rowView).multipliedBy(0.5).offset(-btnSpacing/2);
} else {
// 其他按钮正常宽度
make.left.equalTo(lastButton.mas_right).offset(btnSpacing);
make.width.equalTo(rowView).multipliedBy(0.25).offset(-btnSpacing*0.75);
}
} else {
// 其他行正常处理
if (lastButton) {
make.left.equalTo(lastButton.mas_right).offset(btnSpacing);
} else {
make.left.equalTo(rowView);
}
make.top.bottom.equalTo(rowView);
make.width.equalTo(@(btnHeight)); // 宽高相等,圆形
}
}];
lastButton = button;
}
}
Controller:
这部分属于重中之重,尤其在于各种非法输入的防止。
我分别设置了几个属性,来辅助进行判定
self.pointFlag = NO;
self.operatorFlag = NO;
self.equalFlag = NO;
self.left = 0;
self.right = 0;
每次在页面的点击,都会调用我的pressChange函数:
- (void)pressChange:(UIButton *)button {
NSString *title = button.currentTitle;
NSLog(@"按钮被点击: %@", title);
if (self.equalFlag) {
if ([title isEqualToString:@"="]) {
// 第二次按等号什么都不做
return;
} else if ([self isOperator:title]) {
// 允许继续输入
self.equalFlag = NO;
} else {
// 输入的是数字,表示开始新一轮运算
[self.calArray removeAllObjects];
self.equalFlag = NO;
}
}
// "AC"
if ([title isEqualToString:@"AC"]) {
self.calView.topLabel.text = @"0";
[self.calArray removeAllObjects];
self.pointFlag = NO;
self.operatorFlag = NO;
self.equalFlag = NO;
self.left = 0;
self.right = 0;
return;
}
// 等号
else if ([title isEqualToString:@"="]) {
// 括号未配对
if (self.left != self.right) {
NSLog(@"当前左括号数量 left = %ld,右括号数量 right = %ld", (long)self.left, (long)self.right);
self.calView.topLabel.text = @"ERROR brackets not match!";
return;
}
// 表达式为空
if (self.calArray.count == 0) {
self.calView.topLabel.text = @"0";
return;
}
NSString *last = [self.calArray lastObject];
// 表达式不能以运算符或左括号结尾
if ([self isOperator:last] || [last isEqualToString:@"("]) {
self.calView.topLabel.text = @"ERROR,NO Calculator or (";
return;
}
NSString *expression = [self.calArray componentsJoinedByString:@""];
expression = [expression stringByReplacingOccurrencesOfString:@"×" withString:@"*"];
expression = [expression stringByReplacingOccurrencesOfString:@"÷" withString:@"/"];
NSLog(@"计算表达式:%@", expression);
NSLog(@"当前左括号数量 left = %ld,右括号数量 right = %ld", (long)self.left, (long)self.right);
NSString *result = [self.model calculate:expression];
self.calView.topLabel.text = result;
// 清空
[self.calArray removeAllObjects];
[self.calArray addObject:result];
self.equalFlag = YES;
self.pointFlag = NO;
self.operatorFlag = NO;
self.left = 0;
self.right = 0;
return;
}
// 小数点处理
else if ([title isEqualToString:@"."]) {
NSString *last = [self.calArray lastObject];
// 当前没有输入任何内容
if (!last) {
self.pointFlag = YES;
[self.calArray addObject:@"0."];
}
// 如果当前是数字,并且当前数字中还没有小数点
else if (!self.pointFlag && ![self isOperator:last] && ![last isEqualToString:@"("] && ![last isEqualToString:@")"]) {
self.pointFlag = YES;
NSString *newStr = [last stringByAppendingString:@"."];
[self.calArray removeLastObject];
[self.calArray addObject:newStr];
}
// 当前是操作符或括号或等其他情况,自动补 0.
else if (!self.pointFlag && ![last isEqualToString:@")"]) {
self.pointFlag = YES;
[self.calArray addObject:@"0."];
}
self.calView.topLabel.text = [self.calArray componentsJoinedByString:@""];
return;
}
else if ([title isEqualToString:@"("]) {
NSString* last = [self.calArray lastObject];
if (last && ![self isOperator:last] && ![last isEqualToString:@"("]) {
return;
}
self.left++;
[self.calArray addObject:title];
self.operatorFlag = NO;
self.pointFlag = NO;
self.calView.topLabel.text = [self.calArray componentsJoinedByString:@""];
return;
}
else if ([title isEqualToString:@")"]) {
if (self.left > self.right) {
NSString* last = [self.calArray lastObject];
if ([self isOperator:last] || [last isEqualToString:@"("]) {
return;
}
self.right++;
[self.calArray addObject:title];
self.calView.topLabel.text = [self.calArray componentsJoinedByString:@""];
}
return;
}
// 运算符
else if ([self isOperator:title]) {
NSString *last = [self.calArray lastObject];
// kong
if (!last) {
if ([title isEqualToString:@"-"]) {
// 允许表达式起始为负号
[self.calArray addObject:title];
self.calView.topLabel.text = [self.calArray componentsJoinedByString:@""];
}
return;
}
// 处理上一个字符是运算符
if ([self isOperator:last]) {
// 其他运算符替换上一个运算符
[self.calArray removeLastObject];
[self.calArray addObject:title];
self.calView.topLabel.text = [self.calArray componentsJoinedByString:@""];
return;
}
// 处理上一个字符是左括号的情况
if ([last isEqualToString:@"("]) {
if ([title isEqualToString:@"-"]) {
// 允许在左括号后添加负号
[self.calArray addObject:title];
self.calView.topLabel.text = [self.calArray componentsJoinedByString:@""];
}
return;
}
// 正常添加运算符
[self.calArray addObject:title];
self.pointFlag = NO;
self.operatorFlag = YES;
self.calView.topLabel.text = [self.calArray componentsJoinedByString:@""];
} else {
// 数字处理
NSString *last = [self.calArray lastObject];
if (!last || [self isOperator:last] || [last isEqualToString:@"("] || [last isEqualToString:@")"]) {
[self.calArray addObject:title];
} else {
NSString *newStr = [last stringByAppendingString:title];
[self.calArray removeLastObject];
[self.calArray addObject:newStr];
}
self.operatorFlag = NO;
self.calView.topLabel.text = [self.calArray componentsJoinedByString:@""];
}
}
- (BOOL)isOperator:(NSString *)str {
return [@[@"+", @"-", @"*", @"/"] containsObject:str];
}
- (BOOL)isValidExpression:(NSString *)expression {
return ![expression containsString:@"++"] &&
![expression containsString:@"--"] &&
![expression containsString:@"**"] &&
![expression containsString:@"//"] &&
![expression containsString:@"××"] &&
![expression containsString:@"÷÷"];
}
@end
在程序中,我已经给出了一些注释,相信大家也能看懂。
一些特殊情况的处理
我设置了运算符号的替换,以避免多个连续运算符。
我有两个变量,记录(和)的数量,在按下等号时,如果两个数量不同,会报错。
同时,我还添加了一些逻辑:例如:表达式不能以运算符或左括号结尾;如果上一个是操作符或括号等其他情况,自动补0;如果上一位是(,不能添加)等等,从输入上禁止非法运算的输入。
Model
我的Model中并没有使用常规的后缀转后缀。
我使用了两个栈,一个符号栈和一个数字栈
- (void)applyTopOperator {
//栈里没有两个元素
if (_operandStack.count < 2 || _operatorStack.count == 0) return;
unichar op = [_operatorStack.lastObject characterAtIndex:0];
[_operatorStack removeLastObject];
//两个运算的数字
double b = [[_operandStack lastObject] doubleValue];
[_operandStack removeLastObject];
double a = [[_operandStack lastObject] doubleValue];
[_operandStack removeLastObject];
double result = [self applyOperator:op to:a and:b];
[_operandStack addObject:@(result)];
}
- (NSString *)calculate:(NSString *)expression {
[_operandStack removeAllObjects];
[_operatorStack removeAllObjects];
expression = [expression stringByReplacingOccurrencesOfString:@"×" withString:@"*"];
expression = [expression stringByReplacingOccurrencesOfString:@"÷" withString:@"/"];
// 添加结束符,我也没想清楚有嘛用
expression = [expression stringByAppendingString:@" "];
NSInteger length = expression.length;
NSInteger i = 0;
//负
BOOL isNegative = NO;
BOOL expectOperand = YES;
while (i < length) {
unichar c = [expression characterAtIndex:i];
if (c == ' ') {
i++;
continue;
}
// 负数,有负号且不准备被操作
if (c == '-' && expectOperand) {
isNegative = YES;
i++;
continue;
}
// 处理数字
if ([self isDigit:c]) {
NSInteger start = i;
//循环手机数字
while (i < length && [self isDigit:[expression characterAtIndex:i]]) {
i++;
}
NSString *numStr = [expression substringWithRange:NSMakeRange(start, i - start)];
double value = [numStr doubleValue];//类型转换
//fushu处理
if (isNegative) {
value = -value;
isNegative = NO;
}
[_operandStack addObject:@(value)];
expectOperand = NO;
continue;
}
// 处理左括号
if (c == '(') {
[_operatorStack addObject:[NSString stringWithCharacters:&c length:1]];//操作栈
expectOperand = YES; // 可能跟着负数
i++;
continue;
}
// 处理右括号
if (c == ')') {
//一直运算知道遇到左括号
while (_operatorStack.count > 0) {
unichar top = [_operatorStack.lastObject characterAtIndex:0];
//括号内算完了
if (top == '(') {
[_operatorStack removeLastObject]; // 弹出左括号
break;
}
//做一次运算
[self applyTopOperator];
}
expectOperand = NO;
i++;
continue;
}
// 处理运算符
if ([self isOperator:c]) {
// 处理运算符优先级
while (_operatorStack.count > 0) {
unichar top = [_operatorStack.lastObject characterAtIndex:0];
// 遇到左括号停止
if (top == '(') {
break;
}
// 比较优先级
if ([self precedenceForOperator:top] >= [self precedenceForOperator:c]) {
[self applyTopOperator];
} else {
break;
}
}
[_operatorStack addObject:[NSString stringWithCharacters:&c length:1]];
expectOperand = YES; // 运算符后可能跟着负数
i++;
continue;
}
i++;
}
// 处理栈中剩余的操作符
while (_operatorStack.count > 0) {
[self applyTopOperator];//函数
}
// 获取结果
if (_operandStack.count == 1) {
double result = [[_operandStack lastObject] doubleValue];
if (isnan(result) || isinf(result)) {
return @"错误"; // 这里替代异常处理,直接返回错误提示,或者可以用@catch那个东西,以后详细学习
}
return [self formatResult:result];
}
return @"ERROR";
}
最后,还要加一个结果处理
//结果处理
- (NSString *)formatResult:(double)value {
//检查是否为非数字/无穷大,学到了
if (isnan(value) || isinf(value)) {
return @"ERROR";
}
// 整数
if (fmod(value, 1) == 0) { //返回value / 1的余数
return [NSString stringWithFormat:@"%.0f", value];
}
NSString *result = [NSString stringWithFormat:@"%.6f", value];
// 删除小数点后多余的0
NSRange dotRange = [result rangeOfString:@"."];
if (dotRange.location != NSNotFound) { //从后往前
// 遍历后缀,找到从后往前第一个不是0的位置
NSUInteger i = result.length;
while (i > dotRange.location + 1 && [result characterAtIndex:i - 1] == '0') {
i--;
}
result = [result substringToIndex:i];
// 如果最后是小数点,也去掉
if ([result hasSuffix:@"."]) {
result = [result substringToIndex:result.length - 1];
}
}
return result;
}