如何使用AutoLayout以编程方式为iOS构建Spotify克隆:添加照片和更新UI

This is the second part of an article on building a Spotify UI clone with autoLayout programmatically. If you missed the first part, no worries - just please go and check it now.

这是关于以编程方式使用autoLayout构建Spotify UI克隆的文章的第二部分。 如果您错过了第一部分,请不用担心-请立即检查一下

In this article, we are going to add some mocked pictures and try to make the UI look the same as Spotify's.

在本文中,我们将添加一些模拟图片,并尝试使UI看起来与Spotify的相同。

This is what we are going to do today 👇

这就是我们今天要做的👇

This is were we left off in the first part:

这是我们在第一部分中省略的内容:

The next step is to create customized cells. So let's start by creating one with the name  SubCustomCell.

下一步是创建自定义单元。 因此,让我们开始创建一个名称为SubCustomCell的名称。

First, create a new Swift file inside the project folder and name it SubCustomCell.swift. This file will contain our custom cell that will represent the Playlist. After creating the file, try to add in the code below and initialize the cell, maybe with backgroundColor,  to see the UI changes when we register the cell with the collectionView.

首先,在项目文件夹中创建一个新的Swift文件,并将其命名为SubCustomCell.swift 。 该文件将包含代表播放列表的自定义单元。 创建文件后,尝试添加以下代码并初始化单元格(也许使用backgroundColor ,以查看在我们向collectionView注册单元格时UI发生的变化。

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")
    }
}

Then we register the SubCustomCell  inside CustomCell.swift within the init block. Replace UICollectionViewCell.self with  SubCustomCell like below.

然后,在init块内的CustomCell.swift注册SubCustomCell 。 如下所示,用SubCustomCell替换UICollectionViewCell.self

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

Also we need to make a modification on the cellForItemAt method and make it conform to  SubCustomCell like the following.

同样,我们需要对cellForItemAt方法进行修改,使其符合SubCustomCell ,如下所示。

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

You should see the backgroundColor changed to red .

您应该看到backgroundColor更改为red

Up until this point everything should be straightforward and clear.

到目前为止,一切都应该简单明了。

Now we're going to fill the cells with some mocked pictures and create an ImageView inside each cell. I already downloaded some random pictures from pexels.com, but feel free to use any pictures you like (including these). You can find them in the project files on Github.

现在,我们将使用一些模拟图片填充单元格,并在每个单元格内创建一个ImageView 。 我已经从pexels.com下载了一些随机图片但是可以随意使用任何您喜欢的图片(包括这些图片)。 您可以在Github上项目文件中找到它们。

Let's create the UIImageView inside SubCustomCell.swift and make some constraints.

让我们在SubCustomCell.swift创建UIImageView并进行一些约束。

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

And add it to the view within the init block using addSubView.

并使用addSubView将其添加到init块内的view中。

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

Now let's make the ImageView take up all the space within the cell with the constraints below.

现在,让我们通过以下约束使ImageView占据单元格中的所有空间。

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 represents the left anchor of the cell

    LeftAnchor代表单元格的左锚点

  • rightAnchor represents the right anchor of the cell

    rightAnchor代表单元格的右锚点

  • bottomAnchor represents the bottom anchor of the cell

    bottomAnchor表示单元格的底部锚点

  • topAnchor represents the top anchor of the cell

    topAnchor代表单元格的最高锚点

And by making ImageView 's top anchor equal to the cell's top anchor (and doing the same for ImageView 's left, right, and bottom anchor) it makes the ImageView take up all the space of the SubCustomCell (cell).

通过使ImageView的顶部锚点与单元格的顶部锚点相等(并对ImageView的左,右和底部锚点进行相同操作),可以使ImageView占用SubCustomCell (单元格)的所有空间。

Note: first you need to use translatesAutoresizingMaskIntoConstraints to be able to apply the constraints to the elements. Also don't forget to call isActive property and assign it to true – without doing that the constraints won't work and nothing will change.

注意:首先,您需要使用translatesAutoresizingMaskIntoConstraints才能将约束应用于元素。 另外,不要忘记调用isActive属性并将其分配为true -否则约束将不起作用,并且什么也不会改变。

The ImageView should have an image, so let's add one.

ImageView应该有一个图像,所以我们添加一个。

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
        
    }()

And if you build and run the app, you should see the results and picture we added to the SubCustomCell.

并且,如果您构建并运行该应用程序,则应看到我们添加到SubCustomCell的结果和图片。

Cool 😎. Now there is an element we should add to the SubCustomCell to finish up. We need a title that will represent the title of the playlist:  UILabel.

酷😎。 现在有一个元素应该添加到SubCustomCell以完成操作。 我们需要一个代表播放列表标题的标题: UILabel

For the title it will be like this:

对于标题,它将是这样的:

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
    }()

I just put some random text there – you can put whatever you like. The next step is to add the element to the view and give it some constraints. The title will be placed at the bottom of the ImageView.

我只是在其中放一些随机文本-您可以随便放置任何内容。 下一步是将元素添加到视图中并给它一些约束。 标题将放置在ImageView的底部。

添加到视图: (Add to view:)

addSubview(TitleLabel)

将约束应用于ImageViewTitleLabel (Applying the constraints for both the ImageView and the 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

And here we go!

现在我们开始!

We made the picture take up most of the space in the cell, and the rest is taken up by the title. As you can see, you can scroll horizontally in each section and also vertically in the entire screen.

我们使图片占据了单元格中的大部分空间,其余部分则由标题占据。 如您所见,您可以在每个部分中水平滚动,也可以在整个屏幕中垂直滚动。

Now we are put some mock data into the cells to make it feel like it's real. For that I created a JSON file that contains some random data for sections and playlists.

现在,我们将一些模拟数据放入单元格中,以使其看起来像是真实的。 为此,我创建了一个JSON文件,其中包含部分和播放列表的一些随机数据。

First let's create a two structs, Section and Playlist . We create a separate file for each struct.

首先,我们创建两个结构, SectionPlaylist 。 我们为每个结构创建一个单独的文件。

section.swift

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

//
//  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 ?? ""
    }
   
}

And then inside ViewController.swift we create a function that fetches the JSON for us and stores the results in an array.

然后在ViewController.swift创建一个函数,该函数为我们获取JSON并将结果存储在数组中。

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
              }
        }
    }

The fetchJson function is called within the ViewDidLoad method. We also have a variable called sections where we store the results:

fetchJson函数在内部调用ViewDidLoad的方法。 我们还有一个名为sections的变量,用于存储结果:

var sections = [Section]()

The next step is to pass the data from ViewController to CustomCell. For that we create a variable inside CustomCell which will receive the data for each section:

下一步是将数据从ViewController传递到CustomCell 。 为此,我们在CustomCell创建一个变量,该变量将接收每个部分的数据:

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

We use cellForItemAt  inside the ViewController method to pass the data directly to the CustomCell .

我们在ViewController方法内部使用cellForItemAt将数据直接传递到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
    }

Note: we always call self.collectionView.reloadData() every-time fetchJson is called so the block below, inside CustomCell, will be called as well. Check the console, shift + command + C:

注:我们始终调用self .collectionView.reloadData()的每次fetchJson被称为所以下面的方块,里面CustomCell ,将被称为好。 检查控制台, shift + Command + C:

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

The first thing we change is to set the the section title:

我们更改的第一件事是设置节标题:

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

And then you should see that each section has a specific title on the screen 😃.

然后,您应该在屏幕see上看到每个部分都有特定的标题。

Now it's time to pass the data down to SubCustomCell. We do the same thing as we did above. We need to pass the playlists array, so we create a variable named playlists inside CustomCell.

现在是时候将数据传递给SubCustomCell 。 我们做与上述相同的事情。 我们需要传递playlists数组,因此我们在CustomCell创建一个名为playlists的变量。

var playlists : [PlayList]() //empty

First, we map through the playlists  from the JSON. Then we add each playlist with the playlists var.

首先,我们通过JSON映射playlists 。 然后,我们将每个播放列表与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()
        }
    }

Attention! If you try to run the app it may crash. This is because we forgot to set the number of sections. Since we are now receiving the data from JSON, the number should be dynamic based on the number of sections we have. The number of sections should be equal to the number of sections inside the JSON, so we need to modify numberOfItemsInSection inside ViewController to the below :

注意! 如果您尝试运行该应用程序,则可能会崩溃。 这是因为我们忘记了设置部分的数量。 由于我们现在正在从JSON接收数据,因此该数量应该根据我们拥有的部分的数量而动态变化。 部分的数量应等于JSON内部的部分数量,因此我们需要将ViewController内部的numberOfItemsInSection修改为以下内容:

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

We do the same thing with the same method inside CustomCell.swift – but here we consider the number of the playlists instead.

我们在CustomCell.swift使用相同的方法执行相同的CustomCell.swift -但在这里,我们考虑playlists的数量。

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

The last step we have to complete is to pass each single playlist Object to SubCustomCell within cellForItemAt in CustomCell.swift.

我们必须完成的最后一步是将每个播放列表Object传递给SubCustomCellcellForItemAt中的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
    }

And we are going to get that data inside SubCustomCell via the playlist variable and finally display the title and image of the playlist.

我们将通过playlist变量在SubCustomCell获取该数据,并最终显示playlist的标题和图像。

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
               
           }
       }

I think everything should work fine now, just as below 😃

我认为现在一切正常,就像下面的😃

One last update to the UI: we have to add some padding and margins to the section  and playlist titles and make the playlist a little bit smaller.

用户界面的最新更新:我们必须在sectionplaylist标题中添加一些填充和边距,并使播放列表更小一些。

Let's first add some padding for the section titles. To do that, we need just to give the constant property some number value inside the section cell CustomCell and within setupSubCells:

首先,为部分标题添加一些填充。 为此,我们只需要在小节单元格CustomCell内和setupSubCells内为constant属性提供一些数值:

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

And if you see the entire collectionView come in at the bottom of the titleLabel, all we need to do is add more space by adding 15:

而且,如果您看到整个collectionView出现在titleLabel的底部,那么我们要做的就是通过添加15来添加更多空间:

Next we come to the title of the playlist. This will be inside SubCustomCell, and we just need to add more space at the bottom of the ImageView.

接下来,我们进入playlist的标题。 这将在SubCustomCell内部,我们只需要在ImageView的底部添加更多空间即可。

ImageView.bottomAnchor.constraint(equalTo: TitleLabel.topAnchor,constant: -15).isActive = true

We already have the constant there. In order for it to work, the value should be -15

我们已经在那里有了常数。 为了使其正常工作,该值应为-15

Finally the playlist needs to be a little bit smaller. This is easy: we just make the playlist cell's height and width equal to the section cell's height divided by 2, just like below:

最后,播放列表需要小一些。 这很容易:我们只需使playlist单元格的高度和宽度等于section单元格的高度除以2,如下所示:

CustomCell.swift

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)
        
    }

Make the ImageView's height equal to 150 as well.

使ImageView的高度也等于150

//SubCutomCell.swift
  ImageView.heightAnchor.constraint(equalToConstant: 150).isActive = true

And here we go 👇.

然后我们去👇。

Perfect! I think that's enough for today – I don't want to make this article too long. So we will have another part where we will add the TabBar and the description, as well as some icons for the playlist.

完善! 我认为今天就足够了–我不想把这篇文章写得太长。 因此,我们将在另一部分中添加TabBar和说明以及播放列表的一些图标。

View the Full source code on GitHub🔗.

在GitHub上 查看 完整的源代码

Thanks for your time. I hope I haven't missed anything. If I did please @mention me on Twitter, or if you have any questions or an addition to this post the doors are always open to anyone. Thanks🙏🏼.

谢谢你的时间。 我希望我什么都没错过。 如果我想在Twitter上 @ @提及我,或者如果您对本帖子有任何疑问或补充,则随时对任何人开放。 谢谢🙏🏼。

Subscribe to my email list to be notified when the third part of this tutorial is published.

订阅 我的电子邮件列表,以在本教程的第三部分发布时得到通知。

By the way, I’ve recently worked with a strong group of software engineers for one of my mobile applications. The organization was great, and the product was delivered very quickly, much faster than other firms and freelancers I’ve worked with, and I think I can honestly recommend them for other projects out there. Shoot me an email if you want to get in touch — said@devsdata.com.

顺便说一句,我最近与一群强大的软件工程师一起为我的一个移动应用程序工作。 该组织很棒,产品交付速度非常快,比我所合作的其他公司和自由职业者要快得多,我想我可以诚实地推荐他们用于其他项目。 如果您想与我联系,请给我发送电子邮件-said@devsdata.com

翻译自: https://www.freecodecamp.org/news/how-to-build-a-spotify-clone-for-ios-with-autolayout-programmatically-part-2/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值