Computer >> Hướng Dẫn Máy Tính >  >> Điện Thoại Thông Minh >> iPhone

Xây dựng bản sao Spotify iOS với AutoLayout: Thêm ảnh và hoàn thiện UI theo cách lập trình

Đây là phần thứ hai của bài viết về xây dựng bản sao giao diện Spotify với AutoLayout theo cách lập trình. Nếu bạn bỏ lỡ phần đầu, đừng lo – hãy đi kiểm tra nó ngay bây giờ.

Trong bài viết này, chúng ta sẽ thêm một số hình ảnh mẫu và cố gắng làm cho UI trông giống hệt Spotify.

Đây là những gì chúng ta sẽ làm hôm nay:

Xây dựng bản sao Spotify iOS với AutoLayout: Thêm ảnh và hoàn thiện UI theo cách lập trình

Đây là nơi chúng ta dừng lại ở phần đầu:

Xây dựng bản sao Spotify iOS với AutoLayout: Thêm ảnh và hoàn thiện UI theo cách lập trình

Bước tiếp theo là tạo các cell tùy chỉnh (customized cells). Vậy hãy bắt đầu bằng cách tạo một cell tên là SubCustomCell.

Đầu tiên, tạo một file Swift mới bên trong thư mục dự án và đặt tên là SubCustomCell.swift. File này sẽ chứa cell tùy chỉnh đại diện cho Playlist. Sau khi tạo file, hãy thử thêm mã dưới đây và khởi tạo cell, có thể với backgroundColor, để thấy thay đổi UI khi đăng ký cell với collectionView.

import UIKit

class SubCustomCell: UICollectionViewCell {
    override init(frame: CGRect) {
        super.init(frame: frame)
        backgroundColor = .red
    }
    
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

Sau đó, đăng ký SubCustomCell bên trong CustomCell.swift trong khối init. Thay thế UICollectionViewCell.self bằng SubCustomCell.self như bên dưới.

collectionView.register(SubCustomCell.self, forCellWithReuseIdentifier: cellId)

Cũng cần sửa đổi phương thức cellForItemAt để nó tuân theo SubCustomCell như sau.

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as! SubCustomCell
    // cell.backgroundColor = .yellow
    return cell
}

Bạn nên thấy backgroundColor thay thành red.

Xây dựng bản sao Spotify iOS với AutoLayout: Thêm ảnh và hoàn thiện UI theo cách lập trình
Swift CustomCell

Cho đến thời điểm này, mọi thứ nên đơn giản và rõ ràng.

Bây giờ chúng ta sẽ lấp đầy các cell bằng hình ảnh mẫu và tạo một ImageView bên trong mỗi cell. Tôi đã tải xuống một số hình ảnh ngẫu nhiên từ pexels.com, nhưng bạn có thể dùng bất kỳ hình ảnh nào (kể cả những hình này). Bạn có thể tìm thấy chúng trong file dự án trên GitHub.

Hãy tạo UIImageView bên trong SubCustomCell.swift và thêm một số ràng buộc (constraints).

let imageView: UIImageView = {
    let iv = UIImageView()
    iv.backgroundColor = .yellow
    return iv
}()

Và thêm nó vào view trong khối init sử dụng addSubview.

override init(frame: CGRect) {
    super.init(frame: frame)
    addSubview(imageView)
}

Bây giờ hãy làm cho ImageView chiếm toàn bộ không gian trong cell với các ràng buộc dưới đây.

imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.topAnchor.constraint(equalTo: topAnchor).isActive = true
imageView.leftAnchor.constraint(equalTo: leftAnchor).isActive = true
imageView.rightAnchor.constraint(equalTo: rightAnchor).isActive = true
imageView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
  • leftAnchor đại diện cho neo trái của cell
  • rightAnchor đại diện cho neo phải của cell
  • bottomAnchor đại diện cho neo dưới của cell
  • topAnchor đại diện cho neo trên của cell

Bằng cách làm cho neo trên của ImageView bằng neo trên của cell (và làm tương tự cho neo trái, phải, dưới), ImageView sẽ chiếm toàn bộ không gian của SubCustomCell.

Lưu ý: Trước tiên bạn cần dùng translatesAutoresizingMaskIntoConstraints để có thể áp dụng ràng buộc cho các phần tử. Cũng đừng quên gọi thuộc tính isActive và gán nó bằng true – nếu không làm vậy, ràng buộc sẽ không hoạt động và không có gì thay đổi.

ImageView cần có một hình ảnh, nên hãy thêm một tấm.

let imageView: UIImageView = {
    let iv = UIImageView()
    iv.backgroundColor = .yellow
    // chúng ta có file >image1< trong dự án
    iv.image = UIImage(named: "image1")
    iv.contentMode = .scaleAspectFill
    iv.clipsToBounds = true
    return iv
}()

Nếu build và chạy app, bạn sẽ thấy kết quả và hình ảnh đã thêm vào SubCustomCell.

Xây dựng bản sao Spotify iOS với AutoLayout: Thêm ảnh và hoàn thiện UI theo cách lập trình

Tuyệt vời! Bây giờ còn một phần tử cần thêm vào SubCustomCell để hoàn tất. Chúng ta cần một tiêu đề (title) đại diện cho tiêu đề playlist: UILabel.

Đối với tiêu đề, sẽ như sau:

let titleLabel: UILabel = {
    let lb = UILabel()
    lb.textColor = UIColor.lightGray
    lb.font = UIFont.systemFont(ofSize: 16)
    lb.font = UIFont.boldSystemFont(ofSize: 20)
    lb.text = "Evening Music"
    return lb
}()

Tôi chỉ đặt văn bản ngẫu nhiên – bạn có thể đặt bất cứ gì bạn thích. Bước tiếp theo là thêm phần tử vào view và cho nó một số ràng buộc. Tiêu đề sẽ được đặt ở dưới cùng của ImageView.

Thêm vào view:

addSubview(titleLabel)

Áp dụng ràng buộc cho cả ImageView và TitleLabel

imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.topAnchor.constraint(equalTo: topAnchor).isActive = true
imageView.leftAnchor.constraint(equalTo: leftAnchor).isActive = true
imageView.rightAnchor.constraint(equalTo: rightAnchor).isActive = true
imageView.heightAnchor.constraint(equalToConstant: 240).isActive = true
imageView.bottomAnchor.constraint(equalTo: titleLabel.topAnchor).isActive = true

titleLabel.translatesAutoresizingMaskIntoConstraints = false
titleLabel.topAnchor.constraint(equalTo: imageView.bottomAnchor, constant: 10).isActive = true
titleLabel.leftAnchor.constraint(equalTo: leftAnchor, constant: 5).isActive = true
titleLabel.rightAnchor.constraint(equalTo: rightAnchor, constant: -5).isActive = true

Và đây là kết quả!

Xây dựng bản sao Spotify iOS với AutoLayout: Thêm ảnh và hoàn thiện UI theo cách lập trình

Chúng ta đã làm cho hình ảnh chiếm phần lớn không gian trong cell, phần còn lại dành cho tiêu đề. Như bạn thấy, bạn có thể cuộn ngang trong mỗi section và cuộn dọc trên toàn màn hình.

Xây dựng bản sao Spotify iOS với AutoLayout: Thêm ảnh và hoàn thiện UI theo cách lập trình

Bây giờ chúng ta sẽ đưa một số dữ liệu mẫu vào các cell để nó trông giống thật. Với mục đích đó, tôi tạo một file JSON chứa dữ liệu ngẫu nhiên cho các section và playlist.

Đầu tiên, hãy tạo hai struct: Section và Playlist. Chúng ta tạo một file riêng cho mỗi struct.

Section.swift

import Foundation

struct Section {
    var title: String
    var playlists: NSArray
    
    init(dictionary: [String: Any]) {
        self.title = dictionary["title"] as? String ?? ""
        self.playlists = dictionary["playlists"] as? NSArray ?? []
    }
}

Playlist.swift

import Foundation

struct Playlist {
    var title: String
    var image: String
    
    init(dictionary: [String: Any]) {
        self.title = dictionary["title"] as? String ?? ""
        self.image = dictionary["image"] as? String ?? ""
    }
}

Sau đó, bên trong ViewController.swift, tạo một hàm lấy JSON và lưu kết quả vào một mảng.

print("attempt to fetch Json")
if let path = Bundle.main.path(forResource: "test", ofType: "json") {
    do {
        let data = try Data(contentsOf: URL(fileURLWithPath: path), options: .mappedIfSafe)
        let jsonResult = try JSONSerialization.jsonObject(with: data, options: .mutableLeaves)
        if let jsonResult = jsonResult as? [[String: Any]] {
            jsonResult.forEach { item in
                let section = Section(dictionary: item)
                self.sections.append(section)
            }
            self.collectionView.reloadData()
        }
    } catch {
        // handle error
    }
}

Hàm fetchJson được gọi trong phương thức viewDidLoad. Chúng ta cũng có một biến sections để lưu kết quả:

var sections = [Section]()

Bước tiếp theo là truyền dữ liệu từ ViewController đến CustomCell. Tạo một biến bên trong CustomCell để nhận dữ liệu cho mỗi section:

var section: Section? {
    didSet {
        print("section ✅", self.section)
    }
}

Chúng ta dùng cellForItemAt bên trong ViewController để truyền dữ liệu trực tiếp đến CustomCell.

override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as! CustomCell
    cell.section = sections[indexPath.item]
    return cell
}

Lưu ý: Chúng ta luôn gọi self.collectionView.reloadData() mỗi khi fetchJson được gọi, để khối bên dưới, bên trong CustomCell, cũng được gọi. Kiểm tra console, shift + command + C:

var section: Section? {
    didSet {
        print("section ✅", self.section)
    }
}

Điều đầu tiên chúng ta thay đổi là thiết lập tiêu đề section:

var section: Section? {
    didSet {
        print("section ✅", self.section)
        guard let section = self.section else { return }
        self.titleLabel.text = section.title
    }
}

Và bạn sẽ thấy mỗi section có tiêu đề riêng trên màn hình.

Xây dựng bản sao Spotify iOS với AutoLayout: Thêm ảnh và hoàn thiện UI theo cách lập trình

Bây giờ là lúc truyền dữ liệu xuống SubCustomCell. Làm tương tự như trên. Chúng ta cần truyền mảng playlists, nên tạo một biến tên playlists bên trong CustomCell.

var playlists: [Playlist] = []

Đầu tiên, duyệt qua playlists từ JSON. Sau đó thêm từng playlist vào biến playlists.

var section: Section? {
    didSet {
        print("section ✅", self.section)
        guard let section = self.section else { return }
        self.titleLabel.text = section.title
        
        // thêm vào mảng playlists
        section.playlists.forEach { item in
            let playlist = Playlist(dictionary: item as! [String: Any])
            self.playlists.append(playlist)
        }
        self.collectionView.reloadData()
    }
}

Chú ý! Nếu bạn chạy app có thể bị crash. Lý do là quên thiết lập số lượng section. Vì giờ dữ liệu đến từ JSON, số lượng phải động dựa trên số section trong JSON. Số section phải bằng số section trong JSON, nên cần sửa numberOfItemsInSection trong ViewController như sau:

override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
    return sections.count
}

Làm tương tự với cùng phương thức trong CustomCell.swift – nhưng ở đây xét số lượng playlists.

func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
    return self.playlists.count
}

Bước cuối cùng là truyền từng đối tượng Playlist đơn lẻ đến SubCustomCell trong cellForItemAt bên trong CustomCell.swift.

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as! SubCustomCell
    // ở đây
    cell.playlist = playlists[indexPath.item]
    return cell
}

Và chúng ta sẽ lấy dữ liệu đó bên trong SubCustomCell qua biến playlist và cuối cùng hiển thị tiêu đề và hình ảnh của playlist.

var playlist: Playlist? {
    didSet {
        print("Playlist ?", self.playlist)
        guard let playlist = self.playlist else { return }
        // Hình ảnh
        self.imageView.image = UIImage(named: playlist.image)
        // tiêu đề playlist
        self.titleLabel.text = self.playlist?.title
    }
}

Tôi nghĩ mọi thứ nên hoạt động tốt bây giờ, như bên dưới:

Xây dựng bản sao Spotify iOS với AutoLayout: Thêm ảnh và hoàn thiện UI theo cách lập trình

Một cập nhật UI cuối cùng: thêm padding và margin cho tiêu đề section và playlist, và làm playlist nhỏ hơn một chút.

Đầu tiên thêm padding cho tiêu đề section. Chỉ cần gán giá trị cho thuộc tính constant bên trong cell section CustomCell và trong setupSubCells:

collectionView.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 15).isActive = true

Nếu thấy toàn bộ collectionView dính liền dưới titleLabel, chỉ cần thêm khoảng cách bằng cách cộng 15:

Xây dựng bản sao Spotify iOS với AutoLayout: Thêm ảnh và hoàn thiện UI theo cách lập trình

Tiếp đến tiêu đề playlist. Sẽ ở trong SubCustomCell, chỉ cần thêm khoảng cách ở dưới ImageView.

imageView.bottomAnchor.constraint(equalTo: titleLabel.topAnchor, constant: -15).isActive = true

Chúng ta đã có constant đó. Để nó hoạt động, giá trị phải là -15.

Xây dựng bản sao Spotify iOS với AutoLayout: Thêm ảnh và hoàn thiện UI theo cách lập trình

Cuối cùng, playlist cần nhỏ hơn một chút. Điều này dễ dàng: chỉ cần làm chiều cao và rộng của cell playlist bằng chiều cao cell section chia 2, như sau:

CustomCell.swift

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
    let width = frame.height / 2
    let height = frame.height / 2
    return CGSize(width: width, height: height)
}

Làm cho chiều cao ImageView bằng 150.

// SubCustomCell.swift
imageView.heightAnchor.constraint(equalToConstant: 150).isActive = true

Và đây là kết quả.

Xây dựng bản sao Spotify iOS với AutoLayout: Thêm ảnh và hoàn thiện UI theo cách lập trình

Hoàn hảo! Tôi nghĩ đủ cho hôm nay – không muốn bài viết quá dài. Chúng ta sẽ có phần tiếp theo thêm TabBar và mô tả, cũng như một số icon cho playlist.

Xem mã nguồn đầy đủ trên GitHub.

Cảm ơn bạn đã dành thời gian. Hy vọng tôi không bỏ sót gì. Nếu có, hãy @mention tôi trên Twitter, hoặc nếu bạn có câu hỏi hoặc bổ sung cho bài viết này, cửa luôn mở cho mọi người. Cảm ơn!

Đăng ký danh sách email của tôi để được thông báo khi phần ba của hướng dẫn này được xuất bản.

Thông tin thêm: Tôi gần đây đã làm việc với một nhóm kỹ sư phần mềm mạnh cho một ứng dụng di động của mình. Tổ chức rất tốt, sản phẩm được giao rất nhanh, nhanh hơn nhiều so với các công ty và freelancer khác tôi từng làm việc, và tôi có thể khuyên dùng họ cho các dự án khác. Gửi email cho tôi nếu muốn liên hệ — said@devsdata.com.