iOS/Toy project

[iOS : Toy Project] Github Profile (1)

yevdev 2022. 7. 16. 22:14

💡 Network을 이용해, Github 프로필 가져오기!

 

들어가기에 앞서,,

기본틀 확인!

 

SearchViewController에 Component 연결까지도 되어있다!

일단 ViewController에서 해줘야 할 일은 

  1. setupUI : UI 세팅
  2. userProfile 데이터 확인
  3. Binding : User가 업데이트 되면, UI까지 업데이트 되게!
  4. searchControl 세팅
  5. network 세팅

이다. 천천히 해보자!

 

 

1️⃣ setupUI

private func setupUI() {
    // thumbnail 이미지 Radius 설정
    thumbnail.layer.cornerRadius = 80
}

 

2️⃣ userProfile 데이터 Binding

var subscriptions = Set<AnyCancellable>()

@Published private(set) var user: UserProfile?


// ...



private func bind() {
    $user
        .receive(on: RunLoop.main)
        .sink { [unowned self] result in
            self.update(result)
        }
        .store(in: &subscriptions)
}

private func update(_ user : UserProfile?) {

}

 

3️⃣ searchControl

private func embedSearchControl() {
    self.navigationItem.title = "Search"
    let searchController = UISearchController(searchResultsController: nil)
    searchController.hidesNavigationBarDuringPresentation = false
    searchController.searchBar.placeholder = "yexjin"
    searchController.searchResultsUpdater = self
    searchController.searchBar.delegate = self
    self.navigationItem.searchController = searchController
}
    
extension UserProfileViewController: UISearchResultsUpdating {
    func updateSearchResults(for searchController: UISearchController) {
        let keyword = searchController.searchBar.text
        print("search: \(keyword)")	// for comfirmation
    }
}

extension UserProfileViewController: UISearchBarDelegate {
    func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
        print("button clicked: \(searchBar.text)")	// for comfirmation
    }
}

 

 

여기까지, 전체코드와 실행 화면 및 결과는..!

//  SearchViewController.swift

import UIKit
import Combine

class UserProfileViewController: UIViewController {
    
    // setupUI
    // userProfile
    // bind
    // searchControl
    // network
    
    var subscriptions = Set<AnyCancellable>()
    
    @Published private(set) var user: UserProfile?

    @IBOutlet weak var thumbnail: UIImageView!
    @IBOutlet weak var nameLabel: UILabel!
    @IBOutlet weak var loginLabel: UILabel!
    @IBOutlet weak var followersLabel: UILabel!
    @IBOutlet weak var followingLabel: UILabel!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        setupUI()
        embedSearchControl()
        bind()
    }
    
    private func setupUI() {
        thumbnail.layer.cornerRadius = 80
    }
    
    private func embedSearchControl() {
        self.navigationItem.title = "Search"
        let searchController = UISearchController(searchResultsController: nil)
        searchController.hidesNavigationBarDuringPresentation = false
        searchController.searchBar.placeholder = "yexjin"
        searchController.searchResultsUpdater = self
        searchController.searchBar.delegate = self
        self.navigationItem.searchController = searchController
    }
    
    private func bind() {
        $user
            .receive(on: RunLoop.main)
            .sink { [unowned self] result in
                self.update(result)
            }
            .store(in: &subscriptions)
    }
    
    private func update(_ user : UserProfile?) {
        
    }
}

extension UserProfileViewController: UISearchResultsUpdating {
    func updateSearchResults(for searchController: UISearchController) {
        let keyword = searchController.searchBar.text
        print("search: \(keyword)")
    }
}

extension UserProfileViewController: UISearchBarDelegate {
    func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
        print("button clicked: \(searchBar.text)")
    }
}

애뮬레이터
print

 

 

 

🚫 여기서 다시 해야할 거 짚고 넘어가기

- 이제 Bind을 통해 UI 업데이트

- search control을 통해, network 연결

 

 

4️⃣ Update UI! + Network

private func update(_ user : UserProfile?) {
    guard let user = user else {
        // user가 없을 경우,
        self.nameLabel.text = "n/a"
        self.loginLabel.text = "n/a"
        self.followersLabel.text = ""
        self.followingLabel.text = ""
        self.thumbnail.image = nil
        return
    }

    self.nameLabel.text = user.name
    self.loginLabel.text = user.login
    self.followersLabel.text = "follower: \(user.followers)"
    self.followingLabel.text = "following: \(user.following)"
    self.thumbnail.image = nil  // 이건 나중에 세팅!
}

 

extension UserProfileViewController: UISearchBarDelegate {
    func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
        print("button clicked: \(searchBar.text)")
        
        guard let keyword = searchBar.text, !keyword.isEmpty else { return }
        
        let base = "https://api.github.com/"
        let path = "users/\(keyword)"
        let params: [String:String] = [:]
        let header: [String:String] = ["Content-Type":"application/json"]
        
        var urlComponents = URLComponents(string: base+path)!
        let queryItems = params.map {(key: String, value: String) in
            return URLQueryItem(name: key, value: value)
        }
        urlComponents.queryItems = queryItems
        
        // base부터 header까지 URLRequest로 만들기
        var request = URLRequest(url: urlComponents.url!)
        header.forEach{ (key: String, value: String) in
            request.addValue(value, forHTTPHeaderField: key)
        }
        
        // For Network
        // URLSession을 이용해서 data task 만들기 (Combine이용)
        URLSession.shared
            .dataTaskPublisher(for: request)
            .tryMap{ result -> Data in
                guard let response = result.response as? HTTPURLResponse,
                      (200..<300).contains(response.statusCode) else {
                          let response = result.response as? HTTPURLResponse
                          let statusCode = response?.statusCode ?? -1
                          throw NetworkError.responseError(statusCode: statusCode)
                      }
                return result.data
            }
            // 받은 데이터를 가지고 JSONDecoder을 이용하여  UserProfile로 Decoding
            .decode(type: UserProfile.self, decoder: JSONDecoder())
            .receive(on: RunLoop.main)
            .sink { completion in
                print("completion: \(completion)")
                switch completion {
                case .failure(let error):
                    self.user = nil
                case .finished: break
                }
            } receiveValue: { user in
                // ViewController에 있는 User로 세팅!
                self.user = user
            }.store(in: &subscriptions)
    }
}

입력하고 엔터하면 나오는 나의 부끄러운 정보..

 

 

 

5️⃣ 이미지 가져오기

- 오픈소스 이용해보기!

import Kingfisher

let url = URL(string: "https://example.com/image.png")
imageView.kf.setImage(with: url)

오픈소스 패키지 다운로드

self.thumbnail.kf.setImage(with: user.avatarUrl)

를 추가해주면,,

부끄러운 등장..

 

 

 

 

 

이제 구현 끝!

자세한 코드는 https://github.com/yexjin/iOS_Study의 GithubUserProfile폴더에서!

 

 

 


📌 정리,

  • 구현목록
    1. setupUI : UI 세팅
    2. userProfile 데이터 확인
    3. searchControl 세팅
    4.  
    5. Binding : User가 업데이트 되면, UI까지 업데이트 되게!
    6. network 세팅
      • urlComponent, urlRequest 만들기
      • urlSession을 이용해, Data task 만들기
  • 오픈소스를 이용한 Image 가져오기 (Pakage 설치)

 

 


Reference

패스트캠퍼스 온라인 강의

https://yexjinitlog.tistory.com/121?category=1012361 

 

[iOS] iOS에서의 네트워크

1️⃣ URLSession iOS 네트워크 작업을 도와줌 URLSesstion을 이용해서 서버와 소통 URLSessionConfiguration .default - 디스크를 이용한 정보 저장을 하는 configuration - 그냥 브라우저를 띄울 때 .ephemeral..

yexjinitlog.tistory.com