> 技术文档 > iOS仿写 —— 计算器

iOS仿写 —— 计算器

在计算器的仿写中,本人首次实际使用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 = 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  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;}