【SwiftUI模块】0056、SwiftUI创建时尚的电影预订应用程序UI

SwiftUI模块系列 - 已更新56篇
SwiftUI项目 - 已更新5个项目
往期Demo源码下载

技术:SwiftUI、SwiftUI4.0、电影、电影预定、订票
运行环境:
SwiftUI4.0 + Xcode14 + MacOS12.6 + iPhone Simulator iPhone 14 Pro Max

概述

使用SwiftUI做一个时尚的电影预订应用程序UI - 效果

详细

一、运行效果

请添加图片描述

二、项目结构图

在这里插入图片描述

三、程序实现 - 过程

思路:
1.创建头部模块 进行测试上下滚动拥有放大缩小效果
2.搭建分类模块 固定在头部下面
3.搭建列表模块
4.监听滚动偏移的操作

1.创建一个项目命名为 SpotifyResponvieUI

在这里插入图片描述
在这里插入图片描述

1.1.引入资源文件和颜色

颜色
BG #281A1A
Green #4DD037
随机图片9张
个人大图背景1张
logo1张

在这里插入图片描述

2. 创建一个虚拟文件New Group 命名为 View

在这里插入图片描述
在这里插入图片描述

3. 创建一个虚拟文件New Group 命名为 Model

在这里插入图片描述
在这里插入图片描述

4. 创建一个文件New File 选择SwiftUI View类型 命名为Album 并且继承Identifiable

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

5. 创建一个文件New File 选择SwiftUI View类型 命名为Home

主要是:

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

6. 创建一个文件New File 选择SwiftUI View类型 命名为OffsetModifier

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

Code

ContentView - 主窗口

主要是展示主窗口Home和设置暗黑模式

import SwiftUI

struct ContentView: View {
    var body: some View {
        Home()
        // 永远是黑暗模式
            .preferredColorScheme(.dark)
    }
    
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}
Home - 主页

思路

  1. 主要就是展示大图背景 + 固定的分类 + 列表模块
//
//  Home.swift
//  SpotifyResponvieUI (iOS)
//
//  Created by lyh on 2022/8/23.
//

import SwiftUI

struct Home: View {
    @State var currentType: String = "Popular"
    // 光滑滑动效果
    @Namespace var animation
    @State var _albums: [Album] = albums
    
    // x,y
    @State var headerOffsets: (CGFloat,CGFloat) = (0,0)
    
    var body: some View {
        ScrollView(.vertical, showsIndicators: false) {
            VStack(spacing: 0){
                HeaderView()
                // 带内容的固定标题
                LazyVStack(pinnedViews: [.sectionHeaders]) {
                    Section {
                        SongList()
                    } header: {
                        PinnedHeaderView()
                            .background(Color.black)
                            .offset(y: headerOffsets.1 > 0 ? 0 : -headerOffsets.1 / 8)
                            .modifier(OffsetModifier(offset: $headerOffsets.0, returnFromStart: false))
                            .modifier(OffsetModifier(offset: $headerOffsets.1))
                    }
                }
            }
        }
        .overlay(content: {
            Rectangle()
                .fill(.black)
                .frame(height: 50)
                .frame(maxHeight: .infinity,alignment: .top)
                .opacity(headerOffsets.0 < 5 ? 1 : 0)
        })
        .coordinateSpace(name: "SCROLL")
        .ignoresSafeArea(.container, edges: .vertical)
    }
    
    // 固定的内容
    @ViewBuilder
    func SongList()->some View{
        VStack(spacing: 25){
            ForEach($_albums){$album in
                
                HStack(spacing: 12){
                    
                    Text("#\(getIndex(album: album) + 1)")
                        .fontWeight(.semibold)
                        .foregroundColor(.gray)
                    
                    Image(album.albumImage)
                        .resizable()
                        .aspectRatio(contentMode: .fill)
                        .frame(width: 55, height: 55)
                        .clipShape(RoundedRectangle(cornerRadius: 10, style: .continuous))
                    
                    VStack(alignment: .leading, spacing: 8) {
                        Text(album.albumName)
                            .fontWeight(.semibold)
                        
                        Label {
                            Text("65,587,909")
                        } icon: {
                            Image(systemName: "beats.headphones")
                                .foregroundColor(.white)
                        }
                        .foregroundColor(.gray)
                        .font(.caption)
                    }
                    .frame(maxWidth: .infinity,alignment: .leading)
                    
                    Button {
                        album.isLiked.toggle()
                    } label: {
                     
                        Image(systemName: album.isLiked ? "suit.heart.fill" : "suit.heart")
                            .font(.title3)
                            .foregroundColor(album.isLiked ? Color("Green") : .white)
                    }
                    
                    Button {
                    } label: {
                     
                        Image(systemName: "ellipsis")
                            .font(.title3)
                            .foregroundColor(.white)
                    }
                }
            }
        }
        .padding()
        .padding(.top,25)
        .padding(.bottom,150)
    }
    
    func getIndex(album: Album)->Int{
        return _albums.firstIndex { currentAlbum in
            return album.id == currentAlbum.id
        } ?? 0
    }
    
    // 头部视图
    @ViewBuilder
    func HeaderView()->some View{
        GeometryReader{proxy in
            let minY = proxy.frame(in: .named("SCROLL")).minY
            let size = proxy.size
            let height = (size.height + minY)
            
            Image("Ariana")
                .resizable()
                .aspectRatio(contentMode: .fill)
                .frame(width: size.width,height: height > 0 ? height : 0,alignment: .top)
                .overlay(content: {
                    ZStack(alignment: .bottom) {
                        
                        // 调暗文本内容
                        LinearGradient(colors: [
                            .clear,
                            .black.opacity(0.8)
                        ], startPoint: .top, endPoint: .bottom)
                        
                        VStack(alignment: .leading, spacing: 12) {
                            
                            Text("宇夜iOS")
                                .font(.callout)
                                .foregroundColor(.gray)
                            
                            HStack(alignment: .bottom, spacing: 10) {
                                Text("Ariana Grande")
                                    .font(.title.bold())
                                
                                Image(systemName: "checkmark.seal.fill")
                                    .foregroundColor(.blue)
                                    .background{
                                        Circle()
                                            .fill(.white)
                                            .padding(3)
                                    }
                            }
                            
                            Label {
                             
                                Text("Monthly Listeners")
                                    .fontWeight(.semibold)
                                    .foregroundColor(.white.opacity(0.7))
                            } icon: {
                                Text("62,354,659")
                                    .fontWeight(.semibold)
                            }
                            .font(.caption)
                        }
                        .padding(.horizontal)
                        .padding(.bottom,25)
                        .frame(maxWidth: .infinity,alignment: .leading)
                    }
                })
                .cornerRadius(15)
                .offset(y: -minY)
        }
        .frame(height: 250)
    }
    
    // 固定在头部
    @ViewBuilder
    func PinnedHeaderView()->some View{
        let types: [String] = ["Popular","Albums","Songs","Fans also like","About"]
        ScrollView(.horizontal, showsIndicators: false) {
            HStack(spacing: 25){
                
                ForEach(types,id: \.self){type in
                    VStack(spacing: 12){
                        
                        Text(type)
                            .fontWeight(.semibold)
                            .foregroundColor(currentType == type ? .white : .gray)
                        
                        ZStack{
                            if currentType == type{
                                RoundedRectangle(cornerRadius: 4, style: .continuous)
                                    .fill(.white)
                                    .matchedGeometryEffect(id: "TAB", in: animation)
                            }
                            else{
                                RoundedRectangle(cornerRadius: 4, style: .continuous)
                                    .fill(.clear)
                            }
                        }
                        .padding(.horizontal,8)
                        .frame(height: 4)
                    }
                    .contentShape(Rectangle())
                    .onTapGesture {
                        withAnimation(.easeInOut){
                            currentType = type
                        }
                    }
                }
            }
            .padding(.horizontal)
            .padding(.top,25)
            .padding(.bottom,5)
        }
    }
}

struct Home_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

OffsetModifier - 主要是监听ScrollView的滚动

用来监听ScrollView的滚动 偏移量的改变

//
//  OffsetModifier.swift
//  SpotifyResponvieUI (iOS)
//
//  Created by lyh on 2022/8/23.
//

import SwiftUI

// 继承于 ViewModifier 最主要是能方便扩展一些常见的设置属性
/*
 比如 给Text设置字体\背景颜色\阴影效果
 extension Text {
     func songStyle() -> some View {
         self
             .font(.system(size: 24, weight: .bold))
             .foregroundColor(.white)
             .shadow(radius: 20)
     }
 }
 
 ⭐️如果是继承ViewModifier
 struct SongTextViewModifier: ViewModifier {
     func body(content: Content) -> some View {
         content
           .font(.system(size: 24, weight: .bold))
           .foregroundColor(.white)
           .shadow(radius: 20)
     }
 }

 然后直接通过
 
 Text(song)
       .modifier(SongTextViewModifier())
 设置
 */

struct OffsetModifier: ViewModifier {
    @Binding var offset: CGFloat
    
    // 可选从0返回值
    var returnFromStart: Bool = true
    @State var startValue: CGFloat = 0
    
    func body(content: Content) -> some View {
        content
            .overlay {
                GeometryReader{proxy in
                    Color.clear
                        .preference(key: OffsetKey.self, value: proxy.frame(in: .named("SCROLL")).minY)
                        .onPreferenceChange(OffsetKey.self) { value in
                            if startValue == 0{
                                startValue = value
                            }
                            print(value);

                            offset = (value - (returnFromStart ? startValue : 0))
                            
                            print("offset is \(offset)");

                        }
                }
            }
    }
}

// 偏好的关键
struct OffsetKey: PreferenceKey{
    static var defaultValue: CGFloat = 0
    
    static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
        value = nextValue()
    }
}

Album - 模型
//
//  Album.swift
//  SpotifyResponvieUI (iOS)
//
//  Created by lyh on 2022/8/23.
//

import SwiftUI

// Ablum模型和样本数据
struct Album: Identifiable {
    var id = UUID().uuidString
    var albumName: String
    var albumImage : String
    var isLiked : Bool = false
}


var albums : [Album] = [
    
    Album(albumName: "Positions", albumImage: "Album1"),
    Album(albumName: "The Best", albumImage: "Album2",isLiked: true),
    Album(albumName: "My Everything", albumImage: "Album3"),
    Album(albumName: "Yours Truly", albumImage: "Album4"),
    Album(albumName: "Sweetener", albumImage: "Album5",isLiked: true),
    Album(albumName: "Rain On Me", albumImage: "Album6"),
    Album(albumName: "Stuck With U", albumImage: "Album7"),
    Album(albumName: "7 rings", albumImage: "Album8",isLiked: true),
    Album(albumName: "Bang Bang", albumImage: "Album9"),
    
]

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

宇夜iOS

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值