1. 이전 포스팅 확인하기

 

https://growingsaja.tistory.com/891

 

[Objective-C] 앱 만들기 입문 - 4 : json file 읽어서 저장된 데이터 사용하기

1. 이전 포스팅 확인하기 https://growingsaja.tistory.com/890 [Objective-C] 앱 만들기 입문 - 3 : 상하(위아래) 스크롤 기능 넣기 1. 이전 포스팅 확인하기 https://growingsaja.tistory.com/888 [Objective-C] 앱 만들기 입문

growingsaja.tistory.com

 

 

 

 

 

2. 본 프로젝트 파일 및 그룹 구성도

 

 

 

 

 

 

3. CRTConnect 파일 수정

 

// vim RTConnect.h

// Foundation 프레임워크를 임포트합니다. Foundation은 많은 기본적인 Objective-C 클래스와 인터페이스를 포함하고 있습니다.
#import <Foundation/Foundation.h>

// CRTConnect라는 이름의 클래스를 선언하고, NSObject 클래스를 상속받습니다. NSObject는 모든 Objective-C 클래스에서 사용되는 기본 클래스입니다.
@interface CRTConnect : NSObject

// 선언된 메서드를 사용해 API 호출을 하고, 결과를 completionHandler로 반환합니다.
// fetchDataFromAPI:withCompletionHandler: 라는 메서드를 선언합니다. 이 메서드는 다음과 같이 정의되어 있습니다:
// 매서드 자체 반환값: void, 반환값이 없습니다.
// 매개변수1: (NSString *)apiURL: 호출할 API의 URL을 넘겨줍니다. 이는 문자열 (NSString)로 전달됩니다.
// 매개변수2: withCompletionHandler:(void (^)(NSDictionary *jsonResponse, NSError *error))completionHandler: 결과를 반환할 때 호출할 completionHandler 블록입니다. 이 블록은 두 가지 인자를 받습니다:
// 매개변수2-1: NSDictionary *jsonResponse: API에서 반환된 JSON 데이터를 NSDictionary 형식으로 변환한 객체입니다.
// 매개변수2-2: NSError *error: API 호출 중 발생한 오류를 포함하는 NSError 객체입니다. 오류가 없는 경우, 이 값은 nil입니다.
- (void)fetchDataFromAPI:(NSString *)apiURL withCompletionHandler:(void (^)(NSDictionary *jsonResponse, NSError *error))completionHandler;

// CRTConnect 클래스의 인터페이스 선언을 종료합니다.
@end

// 이 클래스는 CRTConnect 객체를 사용하여 API를 호출하고, 호출 결과를 처리할 수 있는 기능을 제공하도록 설계되어 있습니다. NSData를 사용하여 API를 호출한 후, 결과를 딕셔너리 형태로 파싱하고 completionHandler에 전달하는 구현이 필요합니다.

 

// vim CRTConnect.m

#import "CRTConnect.h"

@implementation CRTConnect

- (void)fetchDataFromAPI:(NSString *)apiURL withCompletionHandler:(void (^)(NSDictionary *jsonResponse, NSError *error))completionHandler {
    // NSURLSession을 사용해 API 호출을 준비합니다.
    NSURL *url = [NSURL URLWithString:apiURL];
    NSURLSession *session = [NSURLSession sharedSession];
    NSURLSessionDataTask *dataTask = [session dataTaskWithURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
        if (error) {
            // 네트워크 오류 발생시 completionHandler에 에러 정보를 전달합니다.
            completionHandler(nil, error);
        } else {
            NSError *jsonError;
            // JSON 데이터를 NSDictionary 객체로 변환합니다.
            NSDictionary *jsonResponse = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&jsonError];
            
            if (jsonError) {
                // JSON 파싱 오류 발생시 completionHandler에 에러 정보를 전달합니다.
                completionHandler(nil, jsonError);
            } else {
                // 정상적으로 파싱된 경우 completionHandler에 결과를 전달합니다.
                completionHandler(jsonResponse, nil);
            }
        }
    }];
    
    // URL 세션 태스크를 시작합니다.
    [dataTask resume];
}

@end

 

 

 

 

 

4. 두번째 탭 뷰로 보이는 SpotGoodsTrackerListVC 파일 수정

 

// vim Controller/SpotGoodsTrackerListVC.h

#import <UIKit/UIKit.h>
#import "CRTConnect.h"

// SecondViewController라는 이름의 뷰 컨트롤러 클래스를 선언합니다.
@interface SpotGoodsTrackerListVC : UIViewController {
    UIScrollView *scrollView;
}

@property (strong, nonatomic) CRTConnect *allBybitCoinLiveFromApi;

@end

 

// vim Controller/SpotGoodsTrackerListVC.m

#import <Foundation/Foundation.h>
#import "SpotGoodsTrackerListVC.h"

@implementation SpotGoodsTrackerListVC

- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.allBybitCoinLiveFromApi = [[CRTConnect alloc] init];
    
    // API 호출 예제
    NSString *apiURL = @"https://api.bybit.com/spot/quote/v1/ticker/price";
    [self.allBybitCoinLiveFromApi fetchDataFromAPI:apiURL withCompletionHandler:^(NSDictionary *jsonResponse, NSError *error) {
        if (error) {
            NSLog(@"Error: %@", error.localizedDescription);
        } else {
            // 여기에서 jsonResponse를 가공 한 후 앱에서 사용하실 수 있습니다.
            NSLog(@"Response: %@", jsonResponse);
            
            
        }
    }];
}

@end

 

 

 

 

 

5. 두번째 탭을 누르면 api에 콜한 결과물 리턴 데이터가 잘 NSLog로 출력됨을 확인할 수 있음

 

 

 

 

 

 

7. 1초마다 api에 콜해서 얻은 정보로 뷰 업데이트 실습

 

 - 주의사항1 : api콜을 한 뒤 얻은 정보로 View를 다시 그려줘야합니다.

 - 주의사항2 : View를 다시 그릴때, 이전에 그려져있던 View를 삭제해줘야합니다.

 - 주의사항3 : api call한 return 결과 데이터를 symbol 기준으로 정렬해 노출합니다.

 PS. 단시간 내에 너무 많은 양의 콜 및 뷰 그리기를 처리하기에는 적합하지 않은, 실습용 코드입니다.

 

 - 개선 필요 사항이지만 본 포스트에서는 해결하지 않은 문제 : api call 후 return받은 데이터로 다시 화면을 그리면 화면의 최상단으로 다시 돌아가버립니다.

 

 a. CRTConnect.h

// vim CRTConnect.h

// Foundation 프레임워크를 임포트합니다. Foundation은 많은 기본적인 Objective-C 클래스와 인터페이스를 포함하고 있습니다.
#import <Foundation/Foundation.h>

// CRTConnect라는 이름의 클래스를 선언하고, NSObject 클래스를 상속받습니다. NSObject는 모든 Objective-C 클래스에서 사용되는 기본 클래스입니다.
@interface CRTConnect : NSObject

// 선언된 메서드를 사용해 API 호출을 하고, 결과를 completionHandler로 반환합니다.
// fetchDataFromAPI:withCompletionHandler: 라는 메서드를 선언합니다. 이 메서드는 다음과 같이 정의되어 있습니다:
// 매서드 자체 반환값: void, 반환값이 없습니다.
// 매개변수1: (NSString *)apiURL: 호출할 API의 URL을 넘겨줍니다. 이는 문자열 (NSString)로 전달됩니다.
// 매개변수2: withCompletionHandler:(void (^)(NSDictionary *jsonResponse, NSError *error))completionHandler: 결과를 반환할 때 호출할 completionHandler 블록입니다. 이 블록은 두 가지 인자를 받습니다:
// 매개변수2-1: NSDictionary *jsonResponse: API에서 반환된 JSON 데이터를 NSDictionary 형식으로 변환한 객체입니다.
// 매개변수2-2: NSError *error: API 호출 중 발생한 오류를 포함하는 NSError 객체입니다. 오류가 없는 경우, 이 값은 nil입니다.
- (void)fetchDataFromAPI:(NSString *)apiURL withCompletionHandler:(void (^)(NSDictionary *jsonResponse, NSError *error))completionHandler;

// CRTConnect 클래스의 인터페이스 선언을 종료합니다.
@end

// 이 클래스는 CRTConnect 객체를 사용하여 API를 호출하고, 호출 결과를 처리할 수 있는 기능을 제공하도록 설계되어 있습니다. NSData를 사용하여 API를 호출한 후, 결과를 딕셔너리 형태로 파싱하고 completionHandler에 전달하는 구현이 필요합니다.

b. CRTConnect.m

// vim CRTConnect.m

#import "CRTConnect.h"

@implementation CRTConnect

- (void)fetchDataFromAPI:(NSString *)apiURL withCompletionHandler:(void (^)(NSDictionary *jsonResponse, NSError *error))completionHandler {
    // NSURLSession을 사용해 API 호출을 준비합니다.
    NSURL *url = [NSURL URLWithString:apiURL];
    NSURLSession *session = [NSURLSession sharedSession];
    NSURLSessionDataTask *dataTask = [session dataTaskWithURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
        if (error) {
            // 네트워크 오류 발생시 completionHandler에 에러 정보를 전달합니다.
            NSLog(@"Error: %@", error.localizedDescription);
            completionHandler(nil, error);
            return;
        } else {
            NSError *jsonError;
            // JSON 데이터를 NSDictionary 객체로 변환합니다.
            NSDictionary *jsonResponse = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&jsonError];
            
            if (jsonError) {
                // JSON 파싱 오류 발생시 completionHandler에 에러 정보를 전달합니다.
                completionHandler(nil, jsonError);
                NSLog(@"JSON parsing error: %@", jsonError.localizedDescription);
                return;
            } else {
                // 정상적으로 파싱된 경우 completionHandler에 결과를 전달합니다.
                completionHandler(jsonResponse, nil);
            }
        }
    }];
    
    // URL 세션 태스크를 시작합니다.
    [dataTask resume];
}

@end

c. SpotGoodsTrackerListVC.h

// vim Controller/SpotGoodsTrackerListVC.h

#import <UIKit/UIKit.h>

// SecondViewController라는 이름의 뷰 컨트롤러 클래스를 선언합니다.
@interface SpotGoodsTrackerListVC : UIViewController
@property (strong, nonatomic) UIScrollView *scrollView;
@property (strong, nonatomic) UIStackView *stackView;
@end

d. SpotGoodsTrackerListVC.m

// vim Controller/SpotGoodsTrackerListVC.m

#import <Foundation/Foundation.h>
#import "SpotGoodsTrackerListVC.h"
#import "CRTConnect.h"

@implementation SpotGoodsTrackerListVC

- (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 = 12;
    [self.scrollView addSubview:self.stackView];
    
    // NSTimer 생성 및 메서드 호출 설정
    [NSTimer scheduledTimerWithTimeInterval:1
                                     target:self
                                   selector:@selector(updateDataAndView)
                                   userInfo:nil
                                    repeats:YES];
}
- (void)updateDataAndView {
    // 데이터 가져오기 및 뷰 업데이트 코드
    // ****************************** [Start] 데이터 가져오기 ****************************** //
    CRTConnect* tryApiCall = [[CRTConnect alloc] init];
    NSString *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를 가공 한 후 앱에서 사용하실 수 있습니다.
            
            NSArray* result = jsonResponse[@"result"];
            
            NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"symbol" ascending:NO];
            NSArray *sortedArray = [result sortedArrayUsingDescriptors:@[sortDescriptor]];
            
            NSLog(@"Response: %@", sortedArray);
            
            
            // ****************************** [Start] 뷰 그리기 ****************************** //
            
            // 메인 스레드에서만 UI 업데이트 수행
            dispatch_async(dispatch_get_main_queue(), ^{
                // 기존 뷰 삭제
                for (UIView *subview in self.scrollView.subviews) {
                    [subview removeFromSuperview];
                }
                // ****************************** [Start] 뷰 ****************************** //
                // 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;
                // 카드와 카드 사이의 공백
                CGFloat cardViewSpacing = 12.0;
                // 카드뷰 목록 노출 시작 x축 위치 자동 설정
                CGFloat cardViewXPosition = horizontalMargin;
                // ****************************** //
                // 카드 자체에 대한 세팅
                // 카드 높이 길이 (상하 길이) 설정
                CGFloat cardViewHeight = 60.0;
                // 카드 좌우 길이 phone size 참조하여 자동 조정
                CGFloat cardViewWidth = [UIScreen mainScreen].bounds.size.width - horizontalMargin * 2;
                // 카드뷰 안에 내용 들어가는 공간까지의 margin
                CGFloat basicMarginInCard = 10.0;
                CGFloat defaultFontSize = 16.0;
                // ****************************** //
                // ****************************** [End] 뷰 ****************************** //
                
                // 카드뷰들을 생성하고 뷰에 추가합니다.
                for (int i = 0; i < sortedArray.count; i++) {
                    UIView *cardView = [[UIView alloc] initWithFrame:CGRectMake(cardViewXPosition, cardViewTopStartMargin + i * (cardViewSpacing + cardViewHeight), cardViewWidth, cardViewHeight)];
                    
                    // 카드뷰 모서리를 둥글게 설정합니다. 조건 1
                    cardView.layer.cornerRadius = 8.0;
                    // cardView의 경계를 기준으로 내용물이 보이는 영역을 제한합니다. masksToBounds를 YES로 설정하면, cardView의 경계 밖에 있는 모든 내용물은 자르고 숨깁니다(클립 됩니다). 즉 뷰의 경계 값을 초과한 부분을 자르기 위해 masksToBounds를 YES로 설정합니다. 반면 masksToBounds가 NO인 경우(기본값)에는 뷰의 경계 밖에 있는 내용물이 그대로 보이게 됩니다.
                    cardView.layer.masksToBounds = YES;
                    
                    // UILabel의 텍스트 색상 및 배경색 설정
                    // 카드뷰 배경색을 설정합니다.
                    if (UITraitCollection.currentTraitCollection.userInterfaceStyle == UIUserInterfaceStyleDark) {
                        // 다크모드인 경우
                        cardView.backgroundColor = [UIColor darkGrayColor];
                        [self.view addSubview:cardView];
                    } else {
                        // 라이트모드인 경우
                        cardView.backgroundColor = [UIColor lightGrayColor];
                        [self.view addSubview:cardView];
                    }
                    
                    // UILabel 객체를 생성합니다. 이 레이블은 암호화폐의 이름을 표시할 것입니다.
                    // 따라서 CGRect를 사용하여 레이블의 위치와 크기를 설정하며, 왼쪽 위 모서리에서 시작합니다.
                    UILabel *cryptoNameLabel = [[UILabel alloc] initWithFrame:CGRectMake(basicMarginInCard, basicMarginInCard, cardViewWidth / 2 - 20, 20)];
                    // 레이블의 텍스트를 설정합니다. 여기에서는 sortedArray 배열의 i 번째 요소를 사용합니다.
                    cryptoNameLabel.text = sortedArray[i][@"symbol"];
                    cryptoNameLabel.font = [UIFont systemFontOfSize:defaultFontSize];
                    // 생성한 cryptoNameLabel을 cardView의 서브뷰로 추가합니다. 이렇게 함으로써 레이블이 카드 뷰에 표시됩니다.
                    [cardView addSubview:cryptoNameLabel];
                    
                    
                    // 암호화폐 가격 레이블을 생성하고 카드뷰에 추가합니다. 조건 3
                    UILabel *cryptoPriceLabel = [[UILabel alloc] initWithFrame:CGRectMake(cardViewWidth / 3, basicMarginInCard, cardViewWidth / 2 - basicMarginInCard, 20)];
                    cryptoPriceLabel.text = sortedArray[i][@"price"];
                    cryptoPriceLabel.textAlignment = NSTextAlignmentRight;
                    cryptoPriceLabel.font = [UIFont systemFontOfSize:defaultFontSize];
                    [cardView addSubview:cryptoPriceLabel];
                    
                    // 거래소 이름 레이블을 생성하고 카드뷰에 추가합니다. 조건 5
                    UILabel *exchangeNameLabel = [[UILabel alloc] initWithFrame:CGRectMake(10, cardViewHeight - 25, cardViewWidth - 20, 20)];
                    exchangeNameLabel.text = @"Bybit";
                    exchangeNameLabel.font = [UIFont systemFontOfSize:defaultFontSize];
                    [cardView addSubview:exchangeNameLabel];
                    [self.scrollView addSubview:cardView];
                }
                // 상하 스크롤 최대치 자동 설정
                CGFloat contentHeight = sortedArray.count * (cardViewHeight + cardViewSpacing);
                self.scrollView.contentSize = CGSizeMake(self.view.bounds.size.width, contentHeight);
            });
            
            // ****************************** [End] 데이터 가져오기 ****************************** //
            
            
        }
    }];
    
    // ****************************** [End] 뷰 그리기 ****************************** //
    
}
@end

 

 

 

 

 

8. 결과 예시

 

 

 

 

 

+ Recent posts