Đây là phần thứ hai của bài viết về cách tạo bản sao giao diện người dùng Spotify với tính năng Tự động thanh toán theo chương trình. Nếu bạn bỏ lỡ phần đầu tiên, đừng lo lắng - chỉ cần đi và kiểm tra nó ngay bây giờ.
Trong bài viết này, chúng tôi sẽ thêm một số hình ảnh chế nhạo và cố gắng làm cho giao diện người dùng giống như của Spotify.
Đây là những gì chúng ta sẽ làm hôm nay?
Đây là những gì chúng ta đã bỏ qua trong phần đầu tiên:
Bước tiếp theo là tạo các ô tùy chỉnh. Vì vậy, hãy bắt đầu bằng cách tạo một cái với tên SubCustomCell
.
Đầu tiên, tạo một tệp Swift mới bên trong thư mục dự án và đặt tên là SubCustomCell.swift
. Tệp này sẽ chứa ô tùy chỉnh của chúng tôi sẽ đại diện cho Danh sách phát. Sau khi tạo tệp, hãy thử thêm mã bên dưới và khởi tạo ô, có thể bằng backgroundColor
, để xem những thay đổi về giao diện người dùng khi chúng tôi đăng ký ô 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 đó, chúng tôi đăng ký SubCustomCell
bên trong CustomCell.swift
trong init
khối. Thay thế UICollectionViewCell.self
với
như bên dưới. SubCustomCell
collectionView.register(SubCustomCell.self, forCellWithReuseIdentifier: cellId)
Ngoài ra, chúng tôi cần thực hiện sửa đổi trên cellForItemAt
và làm cho nó phù hợp với 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 sẽ thấy backgroundColor
đã thay đổi thành red
.
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ẽ điền vào các ô bằng một số hình ảnh chế nhạo và tạo một ImageView
bên trong mỗi ô. Tôi đã tải xuống một số hình ảnh ngẫu nhiên từ pexels.com, nhưng hãy thoải mái sử dụng bất kỳ hình ảnh nào bạn thích (bao gồm cả những hình ảnh này). Bạn có thể tìm thấy chúng trong tệp dự án trên Github.
Hãy tạo UIImageView
bên trong SubCustomCell.swift
và thực hiện một số ràng buộc.
let ImageView : UIImageView = {
let iv = UIImageView()
iv.backgroundColor = .yellow
return iv
}()
Và thêm nó vào view
trong init
chặn sử dụng addSubView
.
override init(frame: CGRect) {
super.init(frame: frame)
addSubview(ImageView)
}
Bây giờ hãy tạo ImageView
chiếm tất cả không gian trong ô với các ràng buộc bên dưới.
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 bên trái của ô -
rightAnchor
đại diện cho neo bên phải của ô -
bottomAnchor
đại diện cho neo dưới cùng của ô -
topAnchor
đại diện cho điểm neo trên cùng của ô
Và bằng cách tạo ImageView
anchor trên cùng của ô bằng với anchor trên cùng của ô (và làm tương tự cho ImageView
neo trái, phải và dưới) nó tạo thành ImageView
chiếm tất cả không gian của SubCustomCell
(ô).
Lưu ý:trước tiên bạn cần sử dụng translatesAutoresizingMaskIntoConstraints
để có thể áp dụng các ràng buộc cho các phần tử. Cũng đừng quên gọi isActive
và gán nó cho true
- nếu không làm điều đó, các ràng buộc sẽ không hoạt động và không có gì thay đổi.
ImageView
nên có một hình ảnh, vì vậy hãy thêm một hình ảnh.
let ImageView : UIImageView = {
let iv = UIImageView()
iv.backgroundColor = .yellow
// we have >image1< file inside the project
iv.image = UIImage(named: "image1")
iv.contentMode = .scaleAspectFill
iv.clipsToBounds = true
return iv
}()
Và nếu bạn xây dựng và chạy ứng dụng, bạn sẽ thấy kết quả và hình ảnh mà chúng tôi đã thêm vào SubCustomCell
.
Mát mẻ ?. Bây giờ có một phần tử chúng ta nên thêm vào SubCustomCell
để kết thúc. Chúng tôi cần một tiêu đề đại diện cho tiêu đề của danh sách phát:UILabel
.
Đối với tiêu đề, nó sẽ như thế này:
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 một số văn bản ngẫu nhiên ở đó - bạn có thể đặt bất cứ thứ gì bạn thích. Bước tiếp theo là thêm phần tử vào dạng xem và cung cấp cho nó một số ràng buộc. Tiêu đề sẽ được đặt ở cuối ImageView
.
Thêm vào chế độ xem:
addSubview(TitleLabel)
Áp dụng các 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à chúng ta bắt đầu!
Chúng tôi đã làm cho hình ảnh chiếm hầu hết không gian trong ô và phần còn lại được chiếm bởi tiêu đề. Như bạn có thể thấy, bạn có thể cuộn theo chiều ngang trong từng phần và cũng có thể cuộn theo chiều dọc trong toàn bộ màn hình.
Bây giờ chúng ta đưa một số dữ liệu giả vào các ô để làm cho nó giống như thật. Để làm điều đó, tôi đã tạo một JSON
tệp chứa một số dữ liệu ngẫu nhiên cho các phần và danh sách phát.
Đầu tiên, hãy tạo hai cấu trúc, Section
và Playlist
. Chúng tôi tạo một tệp riêng cho mỗi cấu trúc.
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
//
// playlist.swift
// spotifyAutoLayout
//
// Created by admin on 12/6/19.
// Copyright © 2019 Said Hayani. All rights reserved.
//
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 ?? ""
}
}
Và sau đó bên trong ViewController.swift
chúng tôi tạo một hàm tìm nạp JSON cho chúng tôi và lưu trữ kết quả trong 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? [ Any] {
// do stuff
jsonResult.forEach { (item) in
let section = Section(dictionary: item as! [String : Any])
// print("FEtching",section.playlists)
self.sections.append(section)
}
self.collectionView.reloadData()
}
} catch {
// handle error
}
}
}
fetchJson
hàm được gọi trong ViewDidLoad
phương pháp. Chúng tôi cũng có một biến được gọi là sections
nơi chúng tôi lưu trữ kết quả:
var sections = [Section]()
Bước tiếp theo là chuyển dữ liệu từ ViewController
thành CustomCell
. Để làm được điều đó, chúng tôi tạo một biến bên trong CustomCell
sẽ nhận dữ liệu cho từng phần:
var section : Section?{
didSet{
print("section ✅",self.section)
}
}
Chúng tôi sử dụng cellForItemAt
bên trong ViewController
phương pháp chuyể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 tôi luôn gọi là self
.collectionView.reloadData()
mọi lúc fetchJson
được gọi như vậy khối bên dưới, bên trong CustomCell
, cũng sẽ được gọi. Kiểm tra bảng điều khiển, shift
+ lệnh + C:
var section : Section? {
didSet{
print("section ✅",self.section)
}
}
Điều đầu tiên chúng tôi thay đổi là đặt tiêu đề phần:
var section : Section? {
didSet{
print("section ✅",self.section)
guard let section = self.section else {return}
self.titleLabel.text = section.title
}
}
Và sau đó bạn sẽ thấy rằng mỗi phần có một tiêu đề cụ thể trên màn hình?.
Bây giờ là lúc chuyển dữ liệu xuống SubCustomCell
. Chúng tôi làm tương tự như chúng tôi đã làm ở trên. Chúng ta cần chuyển playlists
mảng, vì vậy chúng tôi tạo một biến có tên playlists
bên trong CustomCell
.
var playlists : [PlayList]() //empty
Đầu tiên, chúng tôi ánh xạ qua playlists
từ JSON
. Sau đó, chúng tôi thêm từng danh sách phát với playlists
var.
var section : Section? {
didSet{
print("section ✅",self.section)
guard let section = self.section else {return}
self.titleLabel.text = section.title
// append to playlists array
self.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 cố gắng chạy ứng dụng, nó có thể bị lỗi. Điều này là do chúng tôi đã quên đặt số phần. Vì chúng tôi hiện đang nhận dữ liệu từ JSON, nên số lượng phải động dựa trên số phần chúng tôi có. Số phần phải bằng số phần bên trong JSON
, vì vậy chúng tôi cần sửa đổi numberOfItemsInSection
bên trong ViewController
bên dưới:
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return sections.count
}
Chúng tôi làm điều tương tự với cùng một phương pháp bên trong CustomCell.swift
- nhưng ở đây chúng tôi xem xét số của playlists
thay vào đó.
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return self.playlists.count
}
Bước cuối cùng chúng ta phải hoàn thành là chuyển từng danh sách phát duy nhất Object
thành SubCustomCell
trong cellForItemAt
trong CustomCell.swift
.
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as! SubCustomCell
// here ?
cell.playlist = playlists[indexPath.item]
return cell
}
Và chúng tôi sẽ lấy dữ liệu đó bên trong SubCustomCell
qua playlist
và cuối cùng là hiển thị tiêu đề và hình ảnh của danh sách phát.
var playlist : PlayList? {
didSet{
print("Playlist ?",self.playlist)
guard let playlist = self.playlist else {return}
// The Image ?
self.ImageView.image = UIImage(named: playlist.image)
// the playlist title ?
self.TitleLabel.text = self.playlist?.title
}
}
Tôi nghĩ bây giờ mọi thứ sẽ hoạt động tốt, giống như bên dưới?
Một bản cập nhật cuối cùng cho giao diện người dùng:chúng tôi phải thêm một số phần đệm và lề vào section
và playlist
tiêu đề và thu nhỏ danh sách phát một chút.
Đầu tiên chúng ta hãy thêm một số đệm cho tiêu đề phần. Để làm điều đó, chúng tôi chỉ cần cung cấp constant
thuộc tính một số giá trị số bên trong ô phần CustomCell
và trong setupSubCells
:
collectionView.topAnchor.constraint(equalTo: titleLabel.bottomAnchor,constant: 15).isActive = true
Và nếu bạn thấy toàn bộ collectionView
đến ở cuối titleLabel
, tất cả những gì chúng ta cần làm là thêm nhiều dung lượng hơn bằng cách thêm 15
:
Tiếp theo, chúng ta đến với tiêu đề của playlist
. Điều này sẽ nằm bên trong SubCustomCell
và chúng tôi chỉ cần thêm không gian ở cuối ImageView.
ImageView.bottomAnchor.constraint(equalTo: TitleLabel.topAnchor,constant: -15).isActive = true
Chúng tôi đã có hằng số ở đó. Để nó hoạt động, giá trị phải là -15
Cuối cùng, danh sách phát cần phải nhỏ hơn một chút. Điều này thật dễ dàng:chúng tôi chỉ tạo playlist
chiều cao và chiều rộng của ô bằng section
chiều cao của ô chia cho 2, giống như bên dưới:
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)
}
Đặt chiều cao của ImageView bằng 150
nữa.
//SubCutomCell.swift
ImageView.heightAnchor.constraint(equalToConstant: 150).isActive = true
Và chúng ta bắt đầu?.
Hoàn hảo! Tôi nghĩ như vậy là đủ cho ngày hôm nay - tôi không muốn làm bài viết này quá dài. Vì vậy, chúng ta sẽ có một phần khác, nơi chúng ta sẽ thêm TabBar
và mô tả, cũng như một số biểu tượng cho danh sách phát.
Xem Mã nguồn đầy đủ trên GitHub ?.
Cảm ơn vì đã dành thời gian cho tôi. Tôi hy vọng tôi đã không bỏ lỡ bất cứ điều gì. Nếu tôi đã làm, vui lòng @mention tôi trên Twitter, hoặc nếu bạn có bất kỳ câu hỏi nào hoặc bổ sung cho bài đăng này, cánh cửa luôn mở cho bất kỳ ai. Cảm ơn ??.
Đăng ký vào danh sách email của tôi để được thông báo khi phần thứ ba của hướng dẫn này được xuất bản.
Nhân tiện, gần đây tôi đã làm việc với một nhóm kỹ sư phần mềm mạnh mẽ cho một trong những ứng dụng di động của tôi. Tổ chức thật tuyệt vời và sản phẩm được phân phối rất nhanh, nhanh hơn nhiều so với các công ty và dịch giả tự do khác mà tôi đã làm việc cùng và tôi nghĩ rằng tôi có thể thành thật giới thiệu họ cho các dự án khác ngoài đó. Gửi email cho tôi nếu bạn muốn liên hệ - [email protected].