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行,内层循环负责遍历每一行的四个按钮

objectivec 复制代码
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:

这部分属于重中之重,尤其在于各种非法输入的防止。

我分别设置了几个属性,来辅助进行判定

objectivec 复制代码
 self.pointFlag = NO;
    self.operatorFlag = NO;
    self.equalFlag = NO;
    self.left = 0;
    self.right = 0;

每次在页面的点击,都会调用我的pressChange函数:

objectivec 复制代码
- (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中并没有使用常规的后缀转后缀。

我使用了两个栈,一个符号栈和一个数字栈

objectivec 复制代码
- (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)];
}
objectivec 复制代码
- (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";
}

最后,还要加一个结果处理

objectivec 复制代码
//结果处理
- (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;
}
相关推荐
烈焰晴天25 分钟前
一款基于 ReactNative 最新发布的`Android/iOS` 新架构文档预览开源库
android·react native·ios
2501_9159090632 分钟前
iOS电池寿命与App能耗监测实战 构建完整性能监控系统
android·ios·小程序·https·uni-app·iphone·webview
不自律的笨鸟13 小时前
iPhone 神级功能,3D Touch 回归!!!
ios·手机·iphone
Magnetic_h18 小时前
【iOS】类和分类的加载过程
笔记·学习·ios·objective-c·xcode
归辞...18 小时前
「iOS」————MRC
macos·ios·cocoa
谈吐大方的鹏sir19 小时前
SwiftUI-VStack、HStack和ZStack组件学习
ios
胎粉仔1 天前
Objective-c 初阶——异常处理(try-catch)
开发语言·ios·objective-c
转转技术团队1 天前
iOS微距拍照大揭秘:为什么你的App近距离总是拍不清?
ios