1. 이전 포스팅 확인하기

 

https://growingsaja.tistory.com/907

 

[Objective-C] 앱 만들기 입문 - 13 : 특정 거래소 불필요 가격 정보(소수점 뒤에 0 나오는거) 삭제 & 출

1. 이전 포스팅 확인하기 https://growingsaja.tistory.com/904 [Objective-C] 앱 만들기 입문 - 12 : 미사용 변수 정리 및 spot별로 조건에 따라 노출 위치 수정 1. 이전 포스팅 확인하기 https://growingsaja.tistory.com/902

growingsaja.tistory.com

 

 

 

 

 

2. 디자인 부분 수정

 

카드 하나하나의 좌우 끝에 margin이 있어서, 해당 margin을 없애 공간을 조금이라도 더 확보합니다.

기설정된 2라는 값과 관련된, 카드 밖 좌우 마진과 카드 시작 x좌표 위치를 없애줍니다.

 

 

 - 차이 비교

 

 

 

 

 

3. 프리미엄 수치 0.1% 이상인 경우 회색배경색 입히기 - 주요 코드

 

// vim Controller/PopluarAssetListVC.m

// ...


        // ****** max premium spot 이미지 및 프리미엄 수치 설정 ****** //
        maxPremiumPercent = [maxPremiumPercent stringByAppendingString:@"%"];
        // 특정 값 이상 프리미엄 발생시, 텍스트에 배경색 입히기
        if ([maxPremiumPercent floatValue] >= 0.1) {
            NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:maxPremiumPercent];
            // ****** 시스템 테마 설정에 따라 색 다르게 적용 ****** //
            if (UITraitCollection.currentTraitCollection.userInterfaceStyle == UIUserInterfaceStyleDark) {
                // 다크모드인 경우
                [attributedString addAttribute:NSBackgroundColorAttributeName
                                         value:[UIColor darkGrayColor]
                                         range:NSMakeRange(0, maxPremiumPercent.length)];
            } else {
                // 라이트모드인 경우
                [attributedString addAttribute:NSBackgroundColorAttributeName
                                         value:[UIColor lightGrayColor]
                                         range:NSMakeRange(0, maxPremiumPercent.length)];
            }
            // 특성 적용
            maxPricePremiumPercentLabel.attributedText = attributedString;
        } else {
            maxPricePremiumPercentLabel.text = maxPremiumPercent;
        }
        
        maxPremiumTabHighExchangeImage.image = [UIImage imageNamed:customSetting[@"exchangeImageFileName"][topRankPriceExchange]];
        maxPremiumTabLowExchangeImage.image = [UIImage imageNamed:customSetting[@"exchangeImageFileName"][bottomRankPriceExchange]];
        
        
// ...

 

 

 

 

 

4. 프리미엄 수치 0.1% 이상인 경우 회색배경색 입히기 - 전체 코드

 

// vim Controller/PopluarAssetListVC.m

#import <Foundation/Foundation.h>
#import "PopluarAssetListVC.h"
// api 통신 용도
#import "CRTConnect.h"
// default.json 파일 읽기 용도
#import "CRTJSONReader.h"
// 현재 시간 가져오는 용도
#import "CRTSystem.h"

// ****** custom ****** //
// 거래소 별 이미지 파일명 매치
#import "ExchangeImage.h"

@implementation PopluarAssetListVC {
    // json에서 가져온 popularList raw 데이터
    NSMutableArray *popularList;
    // Spot 가격 정보 저장용 데이터
    NSMutableDictionary *popularSpotPriceList;
    // 거래소, Spot 공통 참조 데이터
    NSMutableDictionary *customSetting;
}

- (void)viewDidLoad {
    [super viewDidLoad];
    
    // 스크롤 뷰 생성 및 초기화
    self.scrollView = [[UIScrollView alloc] initWithFrame:self.view.bounds];
    self.scrollView.backgroundColor = [UIColor clearColor];
    [self.view addSubview:self.scrollView];
    
    // 스택 뷰 생성 및 초기화
    self.stackView = [[UIStackView alloc] initWithFrame:self.scrollView.bounds];
    self.stackView.axis = UILayoutConstraintAxisVertical;
    self.stackView.distribution = UIStackViewDistributionEqualSpacing;
    self.stackView.alignment = UIStackViewAlignmentFill;
    self.stackView.spacing = 0;
    [self.scrollView addSubview:self.stackView];
    
    // default.json의 popularList 데이터 가져오기
    [self loadPopularList];
    [self setPriceList];
    [self loadCustomSetting];
    
    // NSTimer 생성 및 메서드 호출 설정 - 매 특정시간마다 호출
    [NSTimer scheduledTimerWithTimeInterval:1
                                     target:self
                                   selector:@selector(updateDataAndView)
                                   userInfo:nil
                                    repeats:YES];
}

// 데이터 가져오기 및 뷰 업데이트 코드
- (void)updateDataAndView {
    // **************************************** [Start] api 콜 준비 **************************************** //
    CRTConnect* tryApiCall = [[CRTConnect alloc] init];
    // **************************************** [Start] Binance 데이터 가져오기 **************************************** //
    NSString *apiURL = @"https://api.binance.com/api/v3/ticker/price";
    [tryApiCall fetchDataFromAPI:apiURL withCompletionHandler:^(NSDictionary *jsonResponse, NSError *error) {
        if (error) {
            NSLog(@"Error: %@", error.localizedDescription);
        } else {
            // 여기에서 jsonResponse를 가공 한 후 앱에서 사용하실 수 있습니다.
            // Binance는 api return data가 array
            NSArray* resultOfApi = (NSArray *)jsonResponse;
            
            // api로 받은 데이터 깔끔한 dictionary로 가공하기
            for (int i=0; i<resultOfApi.count; i++) {
                NSString *symbol = resultOfApi[i][@"symbol"];
                NSString *price = resultOfApi[i][@"price"];
                if ([price floatValue] >= 1000) {
                    price = [price substringWithRange:NSMakeRange(0, price.length-6)];
                } else if ([price floatValue] >= 100) {
                    price = [price substringWithRange:NSMakeRange(0, price.length-5)];
                } else if ([price floatValue] >= 1) {
                    price = [price substringWithRange:NSMakeRange(0, price.length-4)];
                } else if ([price floatValue] >= 0.1) {
                    price = [price substringWithRange:NSMakeRange(0, price.length-3)];
                } else if ([price floatValue] >= 0.01) {
                    price = [price substringWithRange:NSMakeRange(0, price.length-2)];
                } else if ([price floatValue] >= 0.001) {
                    price = [price substringWithRange:NSMakeRange(0, price.length-1)];
                } else {
                    // price값 그대로 가져다씁니다.
                }

                self->popularSpotPriceList[@"Binance"][symbol] = price;
            }
        }
    }];
    // **************************************** [End] Binance 데이터 가져오기 **************************************** //
    // **************************************** [Start] Bybit 데이터 가져오기 **************************************** //
    apiURL = @"https://api.bybit.com/spot/quote/v1/ticker/price";
    [tryApiCall fetchDataFromAPI:apiURL withCompletionHandler:^(NSDictionary *jsonResponse, NSError *error) {
        if (error) {
            NSLog(@"Error: %@", error.localizedDescription);
        } else {
            // 여기에서 jsonResponse를 가공 한 후 앱에서 사용하실 수 있습니다.
            // result 안의 value만 추출
            NSArray* resultOfApi = jsonResponse[@"result"];
            
            // api로 받은 데이터 깔끔한 dictionary로 가공하기
            for (int i=0; i<resultOfApi.count; i++) {
                NSString *symbol = resultOfApi[i][@"symbol"];
                NSString *price = resultOfApi[i][@"price"];
                self->popularSpotPriceList[@"Bybit"][symbol] = price;
            }
        }
    }];
    // **************************************** [End] Bybit 데이터 가져오기 **************************************** //
    // 메인 스레드에서만 UI 업데이트 수행
    dispatch_async(dispatch_get_main_queue(), ^{
        [self updateView];
    });
}

-(void) loadCustomSetting {
    customSetting = [@{} mutableCopy];
    customSetting[@"exchangeImageFileName"] = [ExchangeImage getImageFileNameAndExchangeName];
    NSLog(@"%@", customSetting);
}

// default.json 파일의 popularList 데이터 읽기
-(void) loadPopularList {
    // default.json의 popularList 불러오고 정상인지 1차 확인하기 = popularList에 있는 각 element들의 개수가 같은지 확인
    CRTJSONReader *tryReadingDefaultJsonFile = [[CRTJSONReader alloc] init];
    NSDictionary *defaultData = [tryReadingDefaultJsonFile loadJSONFromFile:@"default"];
    popularList = defaultData[@"popularList"];
    int checkDefaultJsonFile = 0;
    for (int i=0; i<popularList.count-1; i++) {
        if ([popularList[i] count] == [popularList[i+1] count]) {
            checkDefaultJsonFile += 1;
        }
    }
    if (checkDefaultJsonFile == [popularList count]-1) {
        // default.json파일의 popularList 안에 있는 Array들 중 길이가 모두 같으면
        NSLog(@"%@", @"[INFO] default.json Check : Normal");
    } else {
        // default.json파일의 popularList 안에 있는 Array들 중 길이가 다른 것이 1개라도 있으면 WARN 출력 및 앱 dead
        NSLog(@"****************************************************");
        NSLog(@"[WARN] Check File : default.json - popularList");
        NSLog(@"****************************************************");
    }
}

// popularList에서 수집을 1건 이상이라도 할 예정인 거래소 목록을 실시간 호가 정보 데이터에 셋업하기
-(void) setPriceList {
    // 초기값 세팅 (안해주면 for문 돌면서 해당 dictionary에 데이터가 들어가지 않음)
    popularSpotPriceList = [@{} mutableCopy];
    for (int i=0; i<popularList.count; i++) {
        for (NSString *eachPopular in popularList[i][0]) {
            NSString *category = [[eachPopular componentsSeparatedByString:@"-"] objectAtIndex:0];
            NSString *exchange = [[eachPopular componentsSeparatedByString:@"-"] objectAtIndex:1];
            if ([category isEqual:@"spot"]) {
                popularSpotPriceList[exchange] = [@{} mutableCopy];
            }
        }
    }
}

-(void) updateView {
    // **************************************** [Start] 뷰 그리기 **************************************** //
    // 기존 뷰 삭제
    for (UIView *subview in self.scrollView.subviews) {
        [subview removeFromSuperview];
    }
    // viewDidLoad 메서드에서 스크롤 뷰를 초기화하고 설정합니다.
    self.scrollView = [[UIScrollView alloc] initWithFrame:self.view.bounds];
    self.scrollView.backgroundColor = [UIColor clearColor];
    [self.view addSubview:self.scrollView];
    
    //카드뷰 배치에 필요한 변수를 설정합니다.
    // 카드 목록 나열될 공간 세팅
    // ****************************** //
    // 목록 상단 공백 margin 설정
    CGFloat cardViewTopStartMargin = 10.0;
    // 카드 목록의 좌우 공백 각각의 margin
    CGFloat horizontalMargin = 2.0;
    // 카드와 카드 사이의 공백
    CGFloat cardViewSpacing = 0.0;
    // 카드뷰 목록 노출 시작 x축 위치 자동 설정
    CGFloat cardViewXPosition = horizontalMargin;
    // ****************************** //
    // 카드 자체에 대한 세팅
    // 카드 높이 길이 (상하 길이) 설정
    CGFloat cardViewHeight = 44.0;
    // 카드 좌우 길이 phone size 참조하여 자동 조정
    CGFloat cardViewWidth = [UIScreen mainScreen].bounds.size.width - horizontalMargin * 2;
    // 카드뷰 안에 내용 들어가는 공간까지의 margin
    CGFloat basicMarginInCard = 4.0;
    CGFloat defaultFontSize = 16.0;
    // ****************************** //
    
    // **************************************** [Start] 최상단에 기타 정보 공간 **************************************** //
    UIView *topInfoTab = [[UIView alloc] initWithFrame:CGRectMake(cardViewXPosition, cardViewTopStartMargin - (cardViewSpacing + cardViewHeight), cardViewWidth, cardViewHeight)];
    // 국기 너비 길이 (좌우 길이) 설정
    CGFloat miniImange = 18.0;
    
    /* #################### 글로벌 지구 이모티콘 및 시간 설정 #################### */
    UILabel *earthLabel = [[UILabel alloc] initWithFrame:CGRectMake(basicMarginInCard, basicMarginInCard, miniImange, 20)];
    earthLabel.text = @"🌍";
    // 표준 시간 = 그리니치 표준시 : 더블린, 에든버러, 리스본, 런던, 카사블랑카, 몬로비아
    UILabel *liveGMTLabel = [[UILabel alloc] initWithFrame:CGRectMake(basicMarginInCard + miniImange, basicMarginInCard, cardViewWidth / 2, 20)];
    // 레이블의 텍스트를 설정합니다. 여기에서는 sortedArray 배열의 i 번째 요소를 사용합니다.
    liveGMTLabel.text = [[CRTSystem alloc] init].NowDateTimeUTC;
    liveGMTLabel.font = [UIFont fontWithName:@"Pretendard-Regular" size:defaultFontSize];
    
    /* #################### 한국 이모티콘 및 시간 설정 #################### */
    // 태극기
    UILabel *koreanFlagLabel = [[UILabel alloc] initWithFrame:CGRectMake(basicMarginInCard, basicMarginInCard + cardViewHeight / 2, miniImange, 20)];
    koreanFlagLabel.text = @"🇰🇷";
    // 한국 시간
    UILabel *liveKSTLabel = [[UILabel alloc] initWithFrame:CGRectMake(basicMarginInCard + miniImange, basicMarginInCard + cardViewHeight / 2, cardViewWidth / 2, 20)];
    // 레이블의 텍스트를 설정합니다. 여기에서는 sortedArray 배열의 i 번째 요소를 사용합니다.
    liveKSTLabel.text = [[CRTSystem alloc] init].NowDateTimeKST;
    liveKSTLabel.font = [UIFont fontWithName:@"Pretendard-Regular" size:defaultFontSize];
    
    /* #################### cardView를 self.scrollView에 추가 #################### */
    // global 적용
    [topInfoTab addSubview:earthLabel];
    [topInfoTab addSubview:liveGMTLabel];
    // korea 적용
    [topInfoTab addSubview:koreanFlagLabel];
    [topInfoTab addSubview:liveKSTLabel];
    [self.scrollView addSubview:topInfoTab];
    
    // **************************************** [Start] 카드뷰 목록 쭉 만들기 **************************************** //
    for (int i=0; i<popularList.count; i++) {
        // 기준 key인 symbol 변수 설정
        NSString *symbolKey = popularList[i][4];
        /* #################### 카드뷰 기본 세팅 #################### */
        UIView *cardView = [[UIView alloc] initWithFrame:CGRectMake(cardViewXPosition, cardViewTopStartMargin + i * (cardViewSpacing + cardViewHeight), cardViewWidth, cardViewHeight)];
        
        // UILabel의 텍스트 색상 및 배경색 설정
        // 카드뷰 배경색을 설정합니다.
        if (UITraitCollection.currentTraitCollection.userInterfaceStyle == UIUserInterfaceStyleDark) {
            // 다크모드인 경우
            cardView.backgroundColor = [UIColor blackColor];
            [self.view addSubview:cardView];
            // 카드 테두리 다크그레이색
            cardView.layer.borderColor = [UIColor darkGrayColor].CGColor;
        } else {
            // 라이트모드인 경우
            cardView.backgroundColor = [UIColor whiteColor];
            [self.view addSubview:cardView];
            // 카드 테두리 다크그레이색
            cardView.layer.borderColor = [UIColor lightGrayColor].CGColor;
        }
        
        // 카드 테두리 두께
        cardView.layer.borderWidth = 0.5;
        
        // 카드뷰 모서리를 둥글게 설정합니다. 조건 1
        cardView.layer.cornerRadius = 0.0;
        // cardView의 경계를 기준으로 내용물이 보이는 영역을 제한합니다. masksToBounds를 YES로 설정하면, cardView의 경계 밖에 있는 모든 내용물은 자르고 숨깁니다(클립 됩니다). 즉 뷰의 경계 값을 초과한 부분을 자르기 위해 masksToBounds를 YES로 설정합니다. 반면 masksToBounds가 NO인 경우(기본값)에는 뷰의 경계 밖에 있는 내용물이 그대로 보이게 됩니다.
        cardView.layer.masksToBounds = YES;
        
        // UILabel 객체를 생성합니다. 이 레이블은 암호화폐의 이름을 표시할 것입니다.
        // 따라서 CGRect를 사용하여 레이블의 위치와 크기를 설정하며, 왼쪽 위 모서리에서 시작합니다.
        UILabel *cryptoNameLabel = [[UILabel alloc] initWithFrame:CGRectMake(basicMarginInCard, basicMarginInCard, cardViewWidth / 4, 20)];
        // 레이블의 텍스트를 설정합니다.
        cryptoNameLabel.text = symbolKey;
        cryptoNameLabel.font = [UIFont fontWithName:@"Pretendard-Regular" size:defaultFontSize];
//        systemFontOfSize:defaultFontSize
        // 생성한 cryptoNameLabel을 cardView의 서브뷰로 추가합니다. 이렇게 함으로써 레이블이 카드 뷰에 표시됩니다.
        [cardView addSubview:cryptoNameLabel];
        
        /* #################### High spot 현재 가격, 거래소 정보 노출 라벨 세팅 #################### */
        // 암호화폐 가격 레이블을 생성하고 카드뷰에 추가합니다.
        // 가격위한 기본 셋
        UILabel *cryptoPriceHighLabel = [[UILabel alloc] initWithFrame:CGRectMake(cardViewWidth / 2, basicMarginInCard, cardViewWidth / 2, 20)];
        cryptoPriceHighLabel.textAlignment = NSTextAlignmentRight;
        cryptoPriceHighLabel.font = [UIFont fontWithName:@"Pretendard-Bold" size:defaultFontSize];
        
        // 거래소 이름 및 로고 레이블을 생성하고 카드뷰에 추가합니다.
        // 거래소 이름을 위한 기본 셋
        UILabel *exchangeNameHighLabel = [[UILabel alloc] initWithFrame:CGRectMake(cardViewWidth / 4 + miniImange, basicMarginInCard, cardViewWidth/4, 20)];
        exchangeNameHighLabel.font = [UIFont fontWithName:@"Pretendard-Regular" size:defaultFontSize];
        // 거래소 아이콘을 위한 기본 셋
        UIImageView *exchangeLogoHighImage = [[UIImageView alloc] initWithFrame:CGRectMake(cardViewWidth / 4, basicMarginInCard, miniImange, miniImange)];
        exchangeLogoHighImage.contentMode = UIViewContentModeScaleAspectFit; // 해당 옵션을 사용하여 가로세로 비율 유지 크기입니다.
        
        /* #################### Low spot 현재 가격, 거래소 정보 노출 라벨 세팅 #################### */
        // 암호화폐 가격 레이블을 생성하고 카드뷰에 추가합니다.
        // 가격위한 기본 셋
        UILabel *cryptoPriceLowLabel = [[UILabel alloc] initWithFrame:CGRectMake(cardViewWidth / 2, cardViewHeight/2, cardViewWidth / 2, 20)];
        cryptoPriceLowLabel.textAlignment = NSTextAlignmentRight;
        cryptoPriceLowLabel.font = [UIFont fontWithName:@"Pretendard-Bold" size:defaultFontSize];
        
        // 거래소 이름 및 로고 레이블을 생성하고 카드뷰에 추가합니다.
        // 거래소 이름을 위한 기본 셋
        UILabel *exchangeNameLowLabel = [[UILabel alloc] initWithFrame:CGRectMake(cardViewWidth / 4 + miniImange, cardViewHeight/2, cardViewWidth/4, 20)];
        exchangeNameLowLabel.font = [UIFont fontWithName:@"Pretendard-Regular" size:defaultFontSize];
        // 거래소 아이콘을 위한 기본 셋
        UIImageView *exchangeLogoLowImage = [[UIImageView alloc] initWithFrame:CGRectMake(cardViewWidth / 4, cardViewHeight/2, miniImange, miniImange)];
        exchangeLogoLowImage.contentMode = UIViewContentModeScaleAspectFit; // 해당 옵션을 사용하여 가로세로 비율 유지 크기입니다.
        
        /* #################### spot 프리미엄 노출 세팅 #################### */
        // 기본 셋
        UILabel *maxPricePremiumPercentLabel = [[UILabel alloc] initWithFrame:CGRectMake(basicMarginInCard, cardViewHeight/2, cardViewWidth / 8 + miniImange, 20)];
        maxPricePremiumPercentLabel.textAlignment = NSTextAlignmentRight;
        maxPricePremiumPercentLabel.font = [UIFont fontWithName:@"Pretendard-Bold" size:defaultFontSize];
        // top rank 거래소 아이콘을 위한 기본 셋
        UIImageView *maxPremiumTabHighExchangeImage = [[UIImageView alloc] initWithFrame:CGRectMake(basicMarginInCard, cardViewHeight/2, miniImange, miniImange)];
        maxPremiumTabHighExchangeImage.contentMode = UIViewContentModeScaleAspectFit; // 해당 옵션을 사용하여 가로세로 비율 유지 크기입니다.
        // bottom rank 거래소 아이콘을 위한 기본 셋
        UIImageView *maxPremiumTabLowExchangeImage = [[UIImageView alloc] initWithFrame:CGRectMake(basicMarginInCard + miniImange + cardViewWidth / 8, cardViewHeight/2, miniImange, miniImange)];
        maxPremiumTabLowExchangeImage.contentMode = UIViewContentModeScaleAspectFit; // 해당 옵션을 사용하여 가로세로 비율 유지 크기입니다.
        
        /* #################### 취급 상품 정리 및 데이터 가공 for 동일 상품에 대한 거래소간 가격 비교 기존 Array 제작 #################### */
        NSMutableArray *exchangeNameList = [@[] mutableCopy];
        // 특정 거래소에 있는지 확인
        for (NSString *item in popularList[i][0]) {
            NSString *category = [[item componentsSeparatedByString:@"-"] objectAtIndex:0];
            // spot인 상품의 거래소 목록 만들기
            if ([category isEqual: @"spot"]) {
                NSString *exchangeName = [[item componentsSeparatedByString:@"-"] objectAtIndex:1];
                [exchangeNameList addObject:exchangeName];
            }
        }
        
        /* #################### 누가 High로 배치될지, Low로 배치될지 로직 #################### */
        // 거래소 가격 비교해보고 탑랭크, 바텀랭크 지정
        NSString *topRankPriceExchange = @"";
        NSString *bottomRankPriceExchange = @"";
        // 가격 정보가 1개라도 있을 경우
        if ([popularSpotPriceList[exchangeNameList[0]] objectForKey:symbolKey] || [popularSpotPriceList[exchangeNameList[1]] objectForKey:symbolKey]) {
            if ([popularSpotPriceList[exchangeNameList[0]][symbolKey] floatValue] >= [popularSpotPriceList[exchangeNameList[1]][symbolKey] floatValue]) {
                // index 0번 거래소가 1번 거래소보다 더 크거나 같으면
                topRankPriceExchange = exchangeNameList[0];
                bottomRankPriceExchange = exchangeNameList[1];
            } else {
                // index 1번 거래소가 0번 거래소보다 더 크면
                topRankPriceExchange = exchangeNameList[1];
                bottomRankPriceExchange = exchangeNameList[0];
            }
        } else {
            // 가격 정보가 모두 없을 경우 (로딩중이거나 못불러오거나) 전부 0으로 노출
            topRankPriceExchange = exchangeNameList[0];
            bottomRankPriceExchange = exchangeNameList[1];
            popularSpotPriceList[topRankPriceExchange][symbolKey] = 0;
            popularSpotPriceList[bottomRankPriceExchange][symbolKey] = 0;
        }
        
        /* #################### 프리미엄 비교 #################### */
        float topPrice = [popularSpotPriceList[topRankPriceExchange][symbolKey] floatValue];
        float bottomPrice = [popularSpotPriceList[bottomRankPriceExchange][symbolKey] floatValue];
        // 소수점 둘째자리 수까지
        NSString *maxPremiumPercent = [NSString stringWithFormat:@"%.2f", (topPrice-bottomPrice)/bottomPrice*100];
        
        // ****** top price spot 거래소명 및 이미지 설정 ****** //
        exchangeNameHighLabel.text = topRankPriceExchange;
        exchangeLogoHighImage.image = [UIImage imageNamed:customSetting[@"exchangeImageFileName"][topRankPriceExchange]];
        cryptoPriceHighLabel.text = popularSpotPriceList[topRankPriceExchange][symbolKey];
        
        // ****** bottom price spot 거래소 이미지 설정 ****** //
        exchangeNameLowLabel.text = bottomRankPriceExchange;
        exchangeLogoLowImage.image = [UIImage imageNamed:customSetting[@"exchangeImageFileName"][bottomRankPriceExchange]];
        cryptoPriceLowLabel.text = popularSpotPriceList[bottomRankPriceExchange][symbolKey];
        
        // ****** max premium spot 이미지 및 프리미엄 수치 설정 ****** //
        maxPremiumPercent = [maxPremiumPercent stringByAppendingString:@"%"];
        // 특정 값 이상 프리미엄 발생시, 텍스트에 배경색 입히기
        if ([maxPremiumPercent floatValue] >= 0.1) {
            NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:maxPremiumPercent];
            // ****** 시스템 테마 설정에 따라 색 다르게 적용 ****** //
            if (UITraitCollection.currentTraitCollection.userInterfaceStyle == UIUserInterfaceStyleDark) {
                // 다크모드인 경우
                [attributedString addAttribute:NSBackgroundColorAttributeName
                                         value:[UIColor darkGrayColor]
                                         range:NSMakeRange(0, maxPremiumPercent.length)];
            } else {
                // 라이트모드인 경우
                [attributedString addAttribute:NSBackgroundColorAttributeName
                                         value:[UIColor lightGrayColor]
                                         range:NSMakeRange(0, maxPremiumPercent.length)];
            }
            // 특성 적용
            maxPricePremiumPercentLabel.attributedText = attributedString;
        } else {
            maxPricePremiumPercentLabel.text = maxPremiumPercent;
        }
        
        maxPremiumTabHighExchangeImage.image = [UIImage imageNamed:customSetting[@"exchangeImageFileName"][topRankPriceExchange]];
        maxPremiumTabLowExchangeImage.image = [UIImage imageNamed:customSetting[@"exchangeImageFileName"][bottomRankPriceExchange]];
        
        // top rank spot
        [cardView addSubview:cryptoPriceHighLabel];
        [cardView addSubview:exchangeNameHighLabel];
        [cardView addSubview:exchangeLogoHighImage];
        
        // bottom rank spot
        [cardView addSubview:cryptoPriceLowLabel];
        [cardView addSubview:exchangeNameLowLabel];
        [cardView addSubview:exchangeLogoLowImage];
        
        // spot max premium % in spot
        [cardView addSubview:maxPricePremiumPercentLabel];
        [cardView addSubview:maxPremiumTabHighExchangeImage];
        [cardView addSubview:maxPremiumTabLowExchangeImage];
        
        // cardView를 self.scrollView에 추가합니다.
        [self.scrollView addSubview:cardView];
    }
    // **************************************** [End] 카드뷰 목록 쭉 만들기 **************************************** //
    // 상하 스크롤 최대치 자동 설정
    CGFloat contentHeight = popularList.count * (cardViewHeight + cardViewSpacing);
    self.scrollView.contentSize = CGSizeMake(self.view.bounds.size.width, contentHeight);
}
@end

 

 

 

 

 

5. 프리미엄 수치 0.05% 이상인 경우 회색배경색 입히기 + 0.1% 이상인 경우 오렌지배경색 입히기 - 주요 코드

 

        // ****** max premium spot 이미지 및 프리미엄 수치 설정 ****** //
        maxPremiumPercent = [maxPremiumPercent stringByAppendingString:@"%"];
        // 특정 값 이상 프리미엄 발생시, 텍스트에 배경색 입히기
        if ([maxPremiumPercent floatValue] >= 0.1) {
            // 0.1 이상인 경우 주황색
            NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:maxPremiumPercent];
            [attributedString addAttribute:NSBackgroundColorAttributeName
                                     value:[UIColor orangeColor]
                                     range:NSMakeRange(0, maxPremiumPercent.length)];
            // 특성 적용
            maxPricePremiumPercentLabel.attributedText = attributedString;
        } else if ([maxPremiumPercent floatValue] >= 0.05) {
            // 0.05 이상인 경우 회색
            NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:maxPremiumPercent];
            // ****** 시스템 테마 설정에 따라 색 다르게 적용 ****** //
            if (UITraitCollection.currentTraitCollection.userInterfaceStyle == UIUserInterfaceStyleDark) {
                // 다크모드인 경우
                [attributedString addAttribute:NSBackgroundColorAttributeName
                                         value:[UIColor darkGrayColor]
                                         range:NSMakeRange(0, maxPremiumPercent.length)];
            } else {
                // 라이트모드인 경우
                [attributedString addAttribute:NSBackgroundColorAttributeName
                                         value:[UIColor lightGrayColor]
                                         range:NSMakeRange(0, maxPremiumPercent.length)];
            }
            // 특성 적용
            maxPricePremiumPercentLabel.attributedText = attributedString;
        } else {
            maxPricePremiumPercentLabel.text = maxPremiumPercent;
        }
        
        maxPremiumTabHighExchangeImage.image = [UIImage imageNamed:customSetting[@"exchangeImageFileName"][topRankPriceExchange]];
        maxPremiumTabLowExchangeImage.image = [UIImage imageNamed:customSetting[@"exchangeImageFileName"][bottomRankPriceExchange]];

 

 

 

 

 

6. 프리미엄 수치 0.05% 이상인 경우 회색배경색 입히기 + 0.1% 이상인 경우 오렌지배경색 입히기 - 전체 코드

 

// vim Controller/PopluarAssetListVC.m

#import <Foundation/Foundation.h>
#import "PopluarAssetListVC.h"
// api 통신 용도
#import "CRTConnect.h"
// default.json 파일 읽기 용도
#import "CRTJSONReader.h"
// 현재 시간 가져오는 용도
#import "CRTSystem.h"

// ****** custom ****** //
// 거래소 별 이미지 파일명 매치
#import "ExchangeImage.h"

@implementation PopluarAssetListVC {
    // json에서 가져온 popularList raw 데이터
    NSMutableArray *popularList;
    // Spot 가격 정보 저장용 데이터
    NSMutableDictionary *popularSpotPriceList;
    // 거래소, Spot 공통 참조 데이터
    NSMutableDictionary *customSetting;
}

- (void)viewDidLoad {
    [super viewDidLoad];
    
    // 스크롤 뷰 생성 및 초기화
    self.scrollView = [[UIScrollView alloc] initWithFrame:self.view.bounds];
    self.scrollView.backgroundColor = [UIColor clearColor];
    [self.view addSubview:self.scrollView];
    
    // 스택 뷰 생성 및 초기화
    self.stackView = [[UIStackView alloc] initWithFrame:self.scrollView.bounds];
    self.stackView.axis = UILayoutConstraintAxisVertical;
    self.stackView.distribution = UIStackViewDistributionEqualSpacing;
    self.stackView.alignment = UIStackViewAlignmentFill;
    self.stackView.spacing = 0;
    [self.scrollView addSubview:self.stackView];
    
    // default.json의 popularList 데이터 가져오기
    [self loadPopularList];
    [self setPriceList];
    [self loadCustomSetting];
    
    // NSTimer 생성 및 메서드 호출 설정 - 매 특정시간마다 호출
    [NSTimer scheduledTimerWithTimeInterval:1
                                     target:self
                                   selector:@selector(updateDataAndView)
                                   userInfo:nil
                                    repeats:YES];
}

// 데이터 가져오기 및 뷰 업데이트 코드
- (void)updateDataAndView {
    // **************************************** [Start] api 콜 준비 **************************************** //
    CRTConnect* tryApiCall = [[CRTConnect alloc] init];
    // **************************************** [Start] Binance 데이터 가져오기 **************************************** //
    NSString *apiURL = @"https://api.binance.com/api/v3/ticker/price";
    [tryApiCall fetchDataFromAPI:apiURL withCompletionHandler:^(NSDictionary *jsonResponse, NSError *error) {
        if (error) {
            NSLog(@"Error: %@", error.localizedDescription);
        } else {
            // 여기에서 jsonResponse를 가공 한 후 앱에서 사용하실 수 있습니다.
            // Binance는 api return data가 array
            NSArray* resultOfApi = (NSArray *)jsonResponse;
            
            // api로 받은 데이터 깔끔한 dictionary로 가공하기
            for (int i=0; i<resultOfApi.count; i++) {
                NSString *symbol = resultOfApi[i][@"symbol"];
                NSString *price = resultOfApi[i][@"price"];
                if ([price floatValue] >= 1000) {
                    price = [price substringWithRange:NSMakeRange(0, price.length-6)];
                } else if ([price floatValue] >= 100) {
                    price = [price substringWithRange:NSMakeRange(0, price.length-5)];
                } else if ([price floatValue] >= 1) {
                    price = [price substringWithRange:NSMakeRange(0, price.length-4)];
                } else if ([price floatValue] >= 0.1) {
                    price = [price substringWithRange:NSMakeRange(0, price.length-3)];
                } else if ([price floatValue] >= 0.01) {
                    price = [price substringWithRange:NSMakeRange(0, price.length-2)];
                } else if ([price floatValue] >= 0.001) {
                    price = [price substringWithRange:NSMakeRange(0, price.length-1)];
                } else {
                    // price값 그대로 가져다씁니다.
                }

                self->popularSpotPriceList[@"Binance"][symbol] = price;
            }
        }
    }];
    // **************************************** [End] Binance 데이터 가져오기 **************************************** //
    // **************************************** [Start] Bybit 데이터 가져오기 **************************************** //
    apiURL = @"https://api.bybit.com/spot/quote/v1/ticker/price";
    [tryApiCall fetchDataFromAPI:apiURL withCompletionHandler:^(NSDictionary *jsonResponse, NSError *error) {
        if (error) {
            NSLog(@"Error: %@", error.localizedDescription);
        } else {
            // 여기에서 jsonResponse를 가공 한 후 앱에서 사용하실 수 있습니다.
            // result 안의 value만 추출
            NSArray* resultOfApi = jsonResponse[@"result"];
            
            // api로 받은 데이터 깔끔한 dictionary로 가공하기
            for (int i=0; i<resultOfApi.count; i++) {
                NSString *symbol = resultOfApi[i][@"symbol"];
                NSString *price = resultOfApi[i][@"price"];
                self->popularSpotPriceList[@"Bybit"][symbol] = price;
            }
        }
    }];
    // **************************************** [End] Bybit 데이터 가져오기 **************************************** //
    // 메인 스레드에서만 UI 업데이트 수행
    dispatch_async(dispatch_get_main_queue(), ^{
        [self updateView];
    });
}

-(void) loadCustomSetting {
    customSetting = [@{} mutableCopy];
    customSetting[@"exchangeImageFileName"] = [ExchangeImage getImageFileNameAndExchangeName];
    NSLog(@"%@", customSetting);
}

// default.json 파일의 popularList 데이터 읽기
-(void) loadPopularList {
    // default.json의 popularList 불러오고 정상인지 1차 확인하기 = popularList에 있는 각 element들의 개수가 같은지 확인
    CRTJSONReader *tryReadingDefaultJsonFile = [[CRTJSONReader alloc] init];
    NSDictionary *defaultData = [tryReadingDefaultJsonFile loadJSONFromFile:@"default"];
    popularList = defaultData[@"popularList"];
    int checkDefaultJsonFile = 0;
    for (int i=0; i<popularList.count-1; i++) {
        if ([popularList[i] count] == [popularList[i+1] count]) {
            checkDefaultJsonFile += 1;
        }
    }
    if (checkDefaultJsonFile == [popularList count]-1) {
        // default.json파일의 popularList 안에 있는 Array들 중 길이가 모두 같으면
        NSLog(@"%@", @"[INFO] default.json Check : Normal");
    } else {
        // default.json파일의 popularList 안에 있는 Array들 중 길이가 다른 것이 1개라도 있으면 WARN 출력 및 앱 dead
        NSLog(@"****************************************************");
        NSLog(@"[WARN] Check File : default.json - popularList");
        NSLog(@"****************************************************");
    }
}

// popularList에서 수집을 1건 이상이라도 할 예정인 거래소 목록을 실시간 호가 정보 데이터에 셋업하기
-(void) setPriceList {
    // 초기값 세팅 (안해주면 for문 돌면서 해당 dictionary에 데이터가 들어가지 않음)
    popularSpotPriceList = [@{} mutableCopy];
    for (int i=0; i<popularList.count; i++) {
        for (NSString *eachPopular in popularList[i][0]) {
            NSString *category = [[eachPopular componentsSeparatedByString:@"-"] objectAtIndex:0];
            NSString *exchange = [[eachPopular componentsSeparatedByString:@"-"] objectAtIndex:1];
            if ([category isEqual:@"spot"]) {
                popularSpotPriceList[exchange] = [@{} mutableCopy];
            }
        }
    }
}

-(void) updateView {
    // **************************************** [Start] 뷰 그리기 **************************************** //
    // 기존 뷰 삭제
    for (UIView *subview in self.scrollView.subviews) {
        [subview removeFromSuperview];
    }
    // viewDidLoad 메서드에서 스크롤 뷰를 초기화하고 설정합니다.
    self.scrollView = [[UIScrollView alloc] initWithFrame:self.view.bounds];
    self.scrollView.backgroundColor = [UIColor clearColor];
    [self.view addSubview:self.scrollView];
    
    //카드뷰 배치에 필요한 변수를 설정합니다.
    // 카드 목록 나열될 공간 세팅
    // ****************************** //
    // 목록 상단 공백 margin 설정
    CGFloat cardViewTopStartMargin = 10.0;
    // 카드 목록의 좌우 공백 각각의 margin
    CGFloat horizontalMargin = 2.0;
    // 카드와 카드 사이의 공백
    CGFloat cardViewSpacing = 0.0;
    // 카드뷰 목록 노출 시작 x축 위치 자동 설정
    CGFloat cardViewXPosition = horizontalMargin;
    // ****************************** //
    // 카드 자체에 대한 세팅
    // 카드 높이 길이 (상하 길이) 설정
    CGFloat cardViewHeight = 44.0;
    // 카드 좌우 길이 phone size 참조하여 자동 조정
    CGFloat cardViewWidth = [UIScreen mainScreen].bounds.size.width - horizontalMargin * 2;
    // 카드뷰 안에 내용 들어가는 공간까지의 margin
    CGFloat basicMarginInCard = 4.0;
    CGFloat defaultFontSize = 16.0;
    // ****************************** //
    
    // **************************************** [Start] 최상단에 기타 정보 공간 **************************************** //
    UIView *topInfoTab = [[UIView alloc] initWithFrame:CGRectMake(cardViewXPosition, cardViewTopStartMargin - (cardViewSpacing + cardViewHeight), cardViewWidth, cardViewHeight)];
    // 국기 너비 길이 (좌우 길이) 설정
    CGFloat miniImange = 18.0;
    
    /* #################### 글로벌 지구 이모티콘 및 시간 설정 #################### */
    UILabel *earthLabel = [[UILabel alloc] initWithFrame:CGRectMake(basicMarginInCard, basicMarginInCard, miniImange, 20)];
    earthLabel.text = @"🌍";
    // 표준 시간 = 그리니치 표준시 : 더블린, 에든버러, 리스본, 런던, 카사블랑카, 몬로비아
    UILabel *liveGMTLabel = [[UILabel alloc] initWithFrame:CGRectMake(basicMarginInCard + miniImange, basicMarginInCard, cardViewWidth / 2, 20)];
    // 레이블의 텍스트를 설정합니다. 여기에서는 sortedArray 배열의 i 번째 요소를 사용합니다.
    liveGMTLabel.text = [[CRTSystem alloc] init].NowDateTimeUTC;
    liveGMTLabel.font = [UIFont fontWithName:@"Pretendard-Regular" size:defaultFontSize];
    
    /* #################### 한국 이모티콘 및 시간 설정 #################### */
    // 태극기
    UILabel *koreanFlagLabel = [[UILabel alloc] initWithFrame:CGRectMake(basicMarginInCard, basicMarginInCard + cardViewHeight / 2, miniImange, 20)];
    koreanFlagLabel.text = @"🇰🇷";
    // 한국 시간
    UILabel *liveKSTLabel = [[UILabel alloc] initWithFrame:CGRectMake(basicMarginInCard + miniImange, basicMarginInCard + cardViewHeight / 2, cardViewWidth / 2, 20)];
    // 레이블의 텍스트를 설정합니다. 여기에서는 sortedArray 배열의 i 번째 요소를 사용합니다.
    liveKSTLabel.text = [[CRTSystem alloc] init].NowDateTimeKST;
    liveKSTLabel.font = [UIFont fontWithName:@"Pretendard-Regular" size:defaultFontSize];
    
    /* #################### cardView를 self.scrollView에 추가 #################### */
    // global 적용
    [topInfoTab addSubview:earthLabel];
    [topInfoTab addSubview:liveGMTLabel];
    // korea 적용
    [topInfoTab addSubview:koreanFlagLabel];
    [topInfoTab addSubview:liveKSTLabel];
    [self.scrollView addSubview:topInfoTab];
    
    // **************************************** [Start] 카드뷰 목록 쭉 만들기 **************************************** //
    for (int i=0; i<popularList.count; i++) {
        // 기준 key인 symbol 변수 설정
        NSString *symbolKey = popularList[i][4];
        /* #################### 카드뷰 기본 세팅 #################### */
        UIView *cardView = [[UIView alloc] initWithFrame:CGRectMake(cardViewXPosition, cardViewTopStartMargin + i * (cardViewSpacing + cardViewHeight), cardViewWidth, cardViewHeight)];
        
        // UILabel의 텍스트 색상 및 배경색 설정
        // 카드뷰 배경색을 설정합니다.
        if (UITraitCollection.currentTraitCollection.userInterfaceStyle == UIUserInterfaceStyleDark) {
            // 다크모드인 경우
            cardView.backgroundColor = [UIColor blackColor];
            [self.view addSubview:cardView];
            // 카드 테두리 다크그레이색
            cardView.layer.borderColor = [UIColor darkGrayColor].CGColor;
        } else {
            // 라이트모드인 경우
            cardView.backgroundColor = [UIColor whiteColor];
            [self.view addSubview:cardView];
            // 카드 테두리 다크그레이색
            cardView.layer.borderColor = [UIColor lightGrayColor].CGColor;
        }
        
        // 카드 테두리 두께
        cardView.layer.borderWidth = 0.5;
        
        // 카드뷰 모서리를 둥글게 설정합니다. 조건 1
        cardView.layer.cornerRadius = 0.0;
        // cardView의 경계를 기준으로 내용물이 보이는 영역을 제한합니다. masksToBounds를 YES로 설정하면, cardView의 경계 밖에 있는 모든 내용물은 자르고 숨깁니다(클립 됩니다). 즉 뷰의 경계 값을 초과한 부분을 자르기 위해 masksToBounds를 YES로 설정합니다. 반면 masksToBounds가 NO인 경우(기본값)에는 뷰의 경계 밖에 있는 내용물이 그대로 보이게 됩니다.
        cardView.layer.masksToBounds = YES;
        
        // UILabel 객체를 생성합니다. 이 레이블은 암호화폐의 이름을 표시할 것입니다.
        // 따라서 CGRect를 사용하여 레이블의 위치와 크기를 설정하며, 왼쪽 위 모서리에서 시작합니다.
        UILabel *cryptoNameLabel = [[UILabel alloc] initWithFrame:CGRectMake(basicMarginInCard, basicMarginInCard, cardViewWidth / 4, 20)];
        // 레이블의 텍스트를 설정합니다.
        cryptoNameLabel.text = symbolKey;
        cryptoNameLabel.font = [UIFont fontWithName:@"Pretendard-Regular" size:defaultFontSize];
//        systemFontOfSize:defaultFontSize
        // 생성한 cryptoNameLabel을 cardView의 서브뷰로 추가합니다. 이렇게 함으로써 레이블이 카드 뷰에 표시됩니다.
        [cardView addSubview:cryptoNameLabel];
        
        /* #################### High spot 현재 가격, 거래소 정보 노출 라벨 세팅 #################### */
        // 암호화폐 가격 레이블을 생성하고 카드뷰에 추가합니다.
        // 가격위한 기본 셋
        UILabel *cryptoPriceHighLabel = [[UILabel alloc] initWithFrame:CGRectMake(cardViewWidth / 2, basicMarginInCard, cardViewWidth / 2, 20)];
        cryptoPriceHighLabel.textAlignment = NSTextAlignmentRight;
        cryptoPriceHighLabel.font = [UIFont fontWithName:@"Pretendard-Bold" size:defaultFontSize];
        
        // 거래소 이름 및 로고 레이블을 생성하고 카드뷰에 추가합니다.
        // 거래소 이름을 위한 기본 셋
        UILabel *exchangeNameHighLabel = [[UILabel alloc] initWithFrame:CGRectMake(cardViewWidth / 4 + miniImange, basicMarginInCard, cardViewWidth/4, 20)];
        exchangeNameHighLabel.font = [UIFont fontWithName:@"Pretendard-Regular" size:defaultFontSize];
        // 거래소 아이콘을 위한 기본 셋
        UIImageView *exchangeLogoHighImage = [[UIImageView alloc] initWithFrame:CGRectMake(cardViewWidth / 4, basicMarginInCard, miniImange, miniImange)];
        exchangeLogoHighImage.contentMode = UIViewContentModeScaleAspectFit; // 해당 옵션을 사용하여 가로세로 비율 유지 크기입니다.
        
        /* #################### Low spot 현재 가격, 거래소 정보 노출 라벨 세팅 #################### */
        // 암호화폐 가격 레이블을 생성하고 카드뷰에 추가합니다.
        // 가격위한 기본 셋
        UILabel *cryptoPriceLowLabel = [[UILabel alloc] initWithFrame:CGRectMake(cardViewWidth / 2, cardViewHeight/2, cardViewWidth / 2, 20)];
        cryptoPriceLowLabel.textAlignment = NSTextAlignmentRight;
        cryptoPriceLowLabel.font = [UIFont fontWithName:@"Pretendard-Bold" size:defaultFontSize];
        
        // 거래소 이름 및 로고 레이블을 생성하고 카드뷰에 추가합니다.
        // 거래소 이름을 위한 기본 셋
        UILabel *exchangeNameLowLabel = [[UILabel alloc] initWithFrame:CGRectMake(cardViewWidth / 4 + miniImange, cardViewHeight/2, cardViewWidth/4, 20)];
        exchangeNameLowLabel.font = [UIFont fontWithName:@"Pretendard-Regular" size:defaultFontSize];
        // 거래소 아이콘을 위한 기본 셋
        UIImageView *exchangeLogoLowImage = [[UIImageView alloc] initWithFrame:CGRectMake(cardViewWidth / 4, cardViewHeight/2, miniImange, miniImange)];
        exchangeLogoLowImage.contentMode = UIViewContentModeScaleAspectFit; // 해당 옵션을 사용하여 가로세로 비율 유지 크기입니다.
        
        /* #################### spot 프리미엄 노출 세팅 #################### */
        // 기본 셋
        UILabel *maxPricePremiumPercentLabel = [[UILabel alloc] initWithFrame:CGRectMake(basicMarginInCard, cardViewHeight/2, cardViewWidth / 8 + miniImange, 20)];
        maxPricePremiumPercentLabel.textAlignment = NSTextAlignmentRight;
        maxPricePremiumPercentLabel.font = [UIFont fontWithName:@"Pretendard-Bold" size:defaultFontSize];
        // top rank 거래소 아이콘을 위한 기본 셋
        UIImageView *maxPremiumTabHighExchangeImage = [[UIImageView alloc] initWithFrame:CGRectMake(basicMarginInCard, cardViewHeight/2, miniImange, miniImange)];
        maxPremiumTabHighExchangeImage.contentMode = UIViewContentModeScaleAspectFit; // 해당 옵션을 사용하여 가로세로 비율 유지 크기입니다.
        // bottom rank 거래소 아이콘을 위한 기본 셋
        UIImageView *maxPremiumTabLowExchangeImage = [[UIImageView alloc] initWithFrame:CGRectMake(basicMarginInCard + miniImange + cardViewWidth / 8, cardViewHeight/2, miniImange, miniImange)];
        maxPremiumTabLowExchangeImage.contentMode = UIViewContentModeScaleAspectFit; // 해당 옵션을 사용하여 가로세로 비율 유지 크기입니다.
        
        /* #################### 취급 상품 정리 및 데이터 가공 for 동일 상품에 대한 거래소간 가격 비교 기존 Array 제작 #################### */
        NSMutableArray *exchangeNameList = [@[] mutableCopy];
        // 특정 거래소에 있는지 확인
        for (NSString *item in popularList[i][0]) {
            NSString *category = [[item componentsSeparatedByString:@"-"] objectAtIndex:0];
            // spot인 상품의 거래소 목록 만들기
            if ([category isEqual: @"spot"]) {
                NSString *exchangeName = [[item componentsSeparatedByString:@"-"] objectAtIndex:1];
                [exchangeNameList addObject:exchangeName];
            }
        }
        
        /* #################### 누가 High로 배치될지, Low로 배치될지 로직 #################### */
        // 거래소 가격 비교해보고 탑랭크, 바텀랭크 지정
        NSString *topRankPriceExchange = @"";
        NSString *bottomRankPriceExchange = @"";
        // 가격 정보가 1개라도 있을 경우
        if ([popularSpotPriceList[exchangeNameList[0]] objectForKey:symbolKey] || [popularSpotPriceList[exchangeNameList[1]] objectForKey:symbolKey]) {
            if ([popularSpotPriceList[exchangeNameList[0]][symbolKey] floatValue] >= [popularSpotPriceList[exchangeNameList[1]][symbolKey] floatValue]) {
                // index 0번 거래소가 1번 거래소보다 더 크거나 같으면
                topRankPriceExchange = exchangeNameList[0];
                bottomRankPriceExchange = exchangeNameList[1];
            } else {
                // index 1번 거래소가 0번 거래소보다 더 크면
                topRankPriceExchange = exchangeNameList[1];
                bottomRankPriceExchange = exchangeNameList[0];
            }
        } else {
            // 가격 정보가 모두 없을 경우 (로딩중이거나 못불러오거나) 전부 0으로 노출
            topRankPriceExchange = exchangeNameList[0];
            bottomRankPriceExchange = exchangeNameList[1];
            popularSpotPriceList[topRankPriceExchange][symbolKey] = 0;
            popularSpotPriceList[bottomRankPriceExchange][symbolKey] = 0;
        }
        
        /* #################### 프리미엄 비교 #################### */
        float topPrice = [popularSpotPriceList[topRankPriceExchange][symbolKey] floatValue];
        float bottomPrice = [popularSpotPriceList[bottomRankPriceExchange][symbolKey] floatValue];
        // 소수점 둘째자리 수까지
        NSString *maxPremiumPercent = [NSString stringWithFormat:@"%.2f", (topPrice-bottomPrice)/bottomPrice*100];
        
        // ****** top price spot 거래소명 및 이미지 설정 ****** //
        exchangeNameHighLabel.text = topRankPriceExchange;
        exchangeLogoHighImage.image = [UIImage imageNamed:customSetting[@"exchangeImageFileName"][topRankPriceExchange]];
        cryptoPriceHighLabel.text = popularSpotPriceList[topRankPriceExchange][symbolKey];
        
        // ****** bottom price spot 거래소 이미지 설정 ****** //
        exchangeNameLowLabel.text = bottomRankPriceExchange;
        exchangeLogoLowImage.image = [UIImage imageNamed:customSetting[@"exchangeImageFileName"][bottomRankPriceExchange]];
        cryptoPriceLowLabel.text = popularSpotPriceList[bottomRankPriceExchange][symbolKey];
        
        // ****** max premium spot 이미지 및 프리미엄 수치 설정 ****** //
        maxPremiumPercent = [maxPremiumPercent stringByAppendingString:@"%"];
        // 특정 값 이상 프리미엄 발생시, 텍스트에 배경색 입히기
        if ([maxPremiumPercent floatValue] >= 0.1) {
            // 0.1 이상인 경우 주황색
            NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:maxPremiumPercent];
            [attributedString addAttribute:NSBackgroundColorAttributeName
                                     value:[UIColor orangeColor]
                                     range:NSMakeRange(0, maxPremiumPercent.length)];
            // 특성 적용
            maxPricePremiumPercentLabel.attributedText = attributedString;
        } else if ([maxPremiumPercent floatValue] >= 0.05) {
            // 0.05 이상인 경우 회색
            NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:maxPremiumPercent];
            // ****** 시스템 테마 설정에 따라 색 다르게 적용 ****** //
            if (UITraitCollection.currentTraitCollection.userInterfaceStyle == UIUserInterfaceStyleDark) {
                // 다크모드인 경우
                [attributedString addAttribute:NSBackgroundColorAttributeName
                                         value:[UIColor darkGrayColor]
                                         range:NSMakeRange(0, maxPremiumPercent.length)];
            } else {
                // 라이트모드인 경우
                [attributedString addAttribute:NSBackgroundColorAttributeName
                                         value:[UIColor lightGrayColor]
                                         range:NSMakeRange(0, maxPremiumPercent.length)];
            }
            // 특성 적용
            maxPricePremiumPercentLabel.attributedText = attributedString;
        } else {
            maxPricePremiumPercentLabel.text = maxPremiumPercent;
        }
        
        maxPremiumTabHighExchangeImage.image = [UIImage imageNamed:customSetting[@"exchangeImageFileName"][topRankPriceExchange]];
        maxPremiumTabLowExchangeImage.image = [UIImage imageNamed:customSetting[@"exchangeImageFileName"][bottomRankPriceExchange]];
        
        // top rank spot
        [cardView addSubview:cryptoPriceHighLabel];
        [cardView addSubview:exchangeNameHighLabel];
        [cardView addSubview:exchangeLogoHighImage];
        
        // bottom rank spot
        [cardView addSubview:cryptoPriceLowLabel];
        [cardView addSubview:exchangeNameLowLabel];
        [cardView addSubview:exchangeLogoLowImage];
        
        // spot max premium % in spot
        [cardView addSubview:maxPricePremiumPercentLabel];
        [cardView addSubview:maxPremiumTabHighExchangeImage];
        [cardView addSubview:maxPremiumTabLowExchangeImage];
        
        // cardView를 self.scrollView에 추가합니다.
        [self.scrollView addSubview:cardView];
    }
    // **************************************** [End] 카드뷰 목록 쭉 만들기 **************************************** //
    // 상하 스크롤 최대치 자동 설정
    CGFloat contentHeight = popularList.count * (cardViewHeight + cardViewSpacing);
    self.scrollView.contentSize = CGSizeMake(self.view.bounds.size.width, contentHeight);
}
@end

 

 

 

 

 

7. 결과 예시

 

 

 

 

 

 

+ Recent posts