SwiftUI极简教程41:使用Segment、LazyVGrid和ImagePicker构建一个Logo生成器

简介: 在本章中,你将学会使用Segment分段器、LazyVGrid垂直网格、ImagePicker图片选择器构建一个Logo生成器。在上一章中,我们完善了SearchBar搜索栏、TabView底部导航,还有做了一个Loading加载动作。最近突然有个想法,如果把色卡和图片进行组合,这不就是一个简单的Logo了吗?我能不能做个Logo生成器?说干就干,我们继续完成App的相关内容。

image.png

样式部分

Logo展示区域

首先,我们创建一个新的SwiftUI文件,命名为LogoView

然后,我们使用Image作为logo展示的区域,示例:

// MARK: Logo展示区域
private var ShowLogoView: some View {
    Image(systemName: logoImage)
        .font(.system(size: 68))
        .foregroundColor(logoColor)
        .frame(minWidth: 80, maxWidth: 120, minHeight: 80, maxHeight: 120)
        .background(bgColor)
        .cornerRadius(8)
}
复制代码

image.png

上述代码中,我们声明了三个存储变量logoImagelogoColorbgColor,分别可以调整logo图片填充色背景色

在主要展示页面,我们使用Image构建logo视图,并通过修饰符调整logo的图片大小、logo颜色、整体大小背景填充颜色圆角

Segment分段选择

下面我们来实现切换选择调整logo各项参数的区域,我们通过Segment分段选择来区分开,示例:

// MARK: 分选选择
private var SegmentView: some View {
    Picker("分段选择", selection: $selectedSegment) {
        ForEach(0 ..< 3) {
            Text(self.segmentTitle[$0]).tag($0)
       }
    }
    .pickerStyle(SegmentedPickerStyle())
    .padding()
}
复制代码

image.png

上述代码中,我们声明了两个变量segmentTitleselectedSegment

segmentTitle用来存储分段器的标题selectedSegment来记录当前我们选中的是分段器的哪一项。然后我们使用Picker实现了分段器的样式,使用ForEach遍历分段器选项和内容,这里分段器的样式需要选择SegmentedPickerStyle

然后我们在LogoView视图中展示,看起来还不错。

功能交互

背景颜色切换

背景颜色我们使用网格布局来实现,首先我们先声明了网格的列数为4列,然后颜色部分,我们取回之前色卡部分的颜色。

private var gridItemLayout = [GridItem(.flexible()), GridItem(.flexible()), GridItem(.flexible()), GridItem(.flexible())]
@State var cardItems: [CardModel] = []
复制代码

然后构建背景颜色选择的页面,示例:

//MARK: 背景颜色
private var BGColorView: some View {
    ScrollView {
        LazyVGrid(columns: gridItemLayout, spacing: 20) {
            ForEach(cardItems, id: \.cardColorRBG) { item in
                Rectangle()
                    .fill(Color.Hex(item.cardBGColor))
                    .frame(width: 80, height: 80)
                    .cornerRadius(8)
            }
        }
    }.padding()
}
复制代码

上述代码中,我们构建了一个纵向网格视图,spacing间距为20,然后通过遍历cardItems色卡数组中的数据,然后遍历出一个个色块

我们把网络请求补充上,然后将BGColorView在主视图展示看看效果:

image.png

交互部分,当我们点击背景颜色色块时,上面logo展示区域的logo也同时需要更改背景颜色,我们来实现下。示例:

.onTapGesture {
    bgColor = Color.Hex(item.cardBGColor)
}
复制代码

image.png

我们给背景颜色部分的色卡添加一个点击事件,点击背景颜色色块时,将颜色赋予logo背景颜色。

这样,我们就完成了背景颜色的设置。

图标图片切换

图标部分切换的原理类似,我们这里换成本地的数据做一下演示,先准备一些Apple的系统图标作为示例:

private var appleSymbols = ["house.circle", "person.circle", "bag.circle", "location.circle", "bookmark.circle", "gift.circle", "globe.asia.australia.fill", "lock.circle", "pencil.circle", "link.circle"]
复制代码

然后也通过LazyVGrid纵向网格和ForEach循环的方式遍历appleSymbols图片数组的数据,示例:

// MARK: 图标切换
private var SwitchIconView: some View {
    ScrollView {
        LazyVGrid(columns: gridItemLayout, spacing: 20) {
            ForEach(appleSymbols.indices, id: \.self) { item in
                Image(systemName: appleSymbols[item])
                    .font(.system(size: 30))
                    .frame(width: 80, height: 80)
                    .background(bgColor)
                    .cornerRadius(8)
                    .onTapGesture {
                        logoImage = appleSymbols[item]
                    }
            }
        }
    }.padding()
}
复制代码

上述代码中,当我们创建了一个图标网格,然后遍历appleSymbols图片数组的数据,在点击每个图片时,我们把点击的图片赋值到logo区域中logoImage的变量,就实现了切换图标的效果。

我们来运行下看看效果:

image.png

同理,图标填充色我们就把背景颜色那块内容复制一份过来,然后当点击颜色的时候,赋值给logoColor变量就完成了,这里就不做演示了。

分段选项切换

最后是分段器的切换,我们可以根据selectedSegment变量作为切换状态值,当selectedSegment处于不同的值时,我们切换不同的视图。示例:

VStack(spacing: 60) {
    Spacer()
    ShowLogoView
    Spacer()
    VStack(spacing: -10) {
        SegmentView
        if selectedSegment == 0 {
            BGColorView
        } else if selectedSegment == 1 {
            SwitchIconView
        } else {
            LogoColorView
        }
    }
}
复制代码

完成后,我们尝试更改Logo背景颜色、Logo图标、Logo填充色来看看效果。

image.png

功能进阶

完成上述内容后,总觉得好像还差点东西,思来想去总算发现一个问题。

当前我们的颜色和Logo图标都是内置的,没有办法自定义。背景颜色和填充色还好,但是Logo图片不能自定义,就…….不够优雅

接下来,我们来实现自定义Logo图标的方法,最简单的就是本地上传图片

我们设想下通用的交互,当我们点击图标时,它弹出一个弹窗,让我们选择修改的来源。常用的图片来源有2种:相册、拍照,这里我们只完成相册的部分,毕竟应该很少人会直接拍照作为Logo图标。

image.png

打开Sheet弹窗

我们使用官方提供的Sheet弹窗来完成来源选择交互,示例:

// MARK: - 选择来源弹窗
private var chooseImageSheet: ActionSheet {
    let action = ActionSheet(title: Text("选择来源"), buttons: [.default(Text("相册"), action: {
        // 打开相册
    }), .cancel(Text("取消"), action: {
    })])
    return action
}
复制代码

上述代码中,我们构建了一个打开Sheet弹窗的视图chooseImageSheet,它的类型是ActionSheet,即触发Sheet弹窗

然后我们完善了Sheet弹窗里的相关内容,标题、操作(相册、取消)。

然后我们需要声明一个变量,判断是否打开Sheet弹窗,示例:

@State var showChooseImageSheet: Bool = false
复制代码

完成后,我们在LogoView中使用.actionSheet修饰符实现打开Sheet弹窗的交互。示例:

// 选择来源
.actionSheet(isPresented: $showChooseImageSheet, content: {chooseImageSheet })
复制代码

image.png

选择本地图片

我们已经成功调用Sheet弹窗了,可选项为相册取消,取消即自动收起Sheet弹窗,而当我们点击相册的时候,需要调用本地相册

接下来,我们来实现唤起本地相册的功能。

我们新建一个Swift文件,命名为ImagePickerView,然后实现以下方法:

import SwiftUI
public struct ImagePickerView: UIViewControllerRepresentable {
    private let sourceType: UIImagePickerController.SourceType
    private let onImagePicked: (UIImage) -> Void
    @Environment(\.presentationMode) private var presentationMode
    public init(sourceType: UIImagePickerController.SourceType, onImagePicked: @escaping (UIImage) -> Void) {
        self.sourceType = sourceType
        self.onImagePicked = onImagePicked
    }
    public func makeUIViewController(context: Context) -> UIImagePickerController {
        let picker = UIImagePickerController()
        picker.sourceType = self.sourceType
        picker.delegate = context.coordinator
        return picker
    }
    public func updateUIViewController(_ uiViewController: UIImagePickerController, context: Context) {}
    public func makeCoordinator() -> Coordinator {
        Coordinator(
            onDismiss: { self.presentationMode.wrappedValue.dismiss() },
            onImagePicked: self.onImagePicked
        )
    }
    final public class Coordinator: NSObject, UINavigationControllerDelegate, UIImagePickerControllerDelegate {
        private let onDismiss: () -> Void
        private let onImagePicked: (UIImage) -> Void
        init(onDismiss: @escaping () -> Void, onImagePicked: @escaping (UIImage) -> Void) {
            self.onDismiss = onDismiss
            self.onImagePicked = onImagePicked
        }
        public func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]) {
            if let image = info[.originalImage] as? UIImage {
                self.onImagePicked(image)
            }
            self.onDismiss()
        }
        public func imagePickerControllerDidCancel(_: UIImagePickerController) {
            self.onDismiss()
        }
    }
}
复制代码

image.png

上述代码中,创建了一个公开的结构体ImagePickerView,遵循UIViewControllerRepresentable协议。

然后调用系统的UIImagePickerController选择图片方法,调用成功后关闭弹窗并且返回选中的图片

代码中大部分内容为实现选择图片的相关协议和返回UIImage类型图片的方法,在这里就不赘述了。

我们回到LogoView文件中,我们先声明必要的参数。示例:

@State var showImagePicker: Bool = false
@State var image: UIImage?
复制代码

上述代码中,showImagePicker是我们是否打开选择图片的弹窗,而image则用来接收我们相册中选择的图片。

我们使用.sheet调用打开本地相册的方法。示例:

// 本地图片选择弹窗
.sheet(isPresented: $showImagePicker) {
    ImagePickerView(sourceType: .photoLibrary) { image in
        self.image = image
    }
}
复制代码

我们使用showImagePicker绑定了打开本地图片弹窗的条件,我们把这个触发条件放在我们点击“相册”选项的操作。示例:

image.png

图标图片替换

上述代码中,我们已经通过调用本地相册的方法,获得了选中的图片,这时我们需要替换当前Logo的图标。

我们创建多一个视图来展示选中图片后回调的内容。示例:

// MARK: 本地图片
private var ShowImageView: some View {
    Image(uiImage: image!)
        .resizable()
        .frame(width: 68, height: 68, alignment: .center)
        .clipShape(Circle())
        .padding(20)
        .background(bgColor)
        .cornerRadius(8)
        .onTapGesture {
            self.showChooseImageSheet.toggle()
        }
}
复制代码

上述代码中,我们创建了一个新的视图ShowImageViewImage图片的内容换成了换取本地图库中选中回调的image

图标图片部分,使用clipShape修饰符切成圆形比较美观,背景颜色可调节,图标由于是图标就无需进行填充色调整了。

我们在LogoView视图中根据image的有无,展示不同的视图。效果如下:

image.png

项目预览

image.png

恭喜你,完成了本章的所有内容!

快来动手试试吧!


相关文章
|
3月前
|
存储 机器人 API
如何使用渐变块创建自定义聊天机器人
本文是一篇使用Gradio库的Blocks API创建自定义聊天机器人界面的教程,涵盖了从基础聊天机器人到支持流式响应、用户反馈(喜欢/不喜欢)以及Markdown、图像、音频和视频等多媒体内容的高级功能实现方法。
如何使用渐变块创建自定义聊天机器人
|
3月前
|
Web App开发 JSON JavaScript
WebGL简易教程(十五):加载gltf模型
WebGL简易教程(十五):加载gltf模型
114 1
|
3月前
|
自然语言处理
预训练模型STAR问题之Doc2Bot数据集中结构信息的问题如何解决
预训练模型STAR问题之Doc2Bot数据集中结构信息的问题如何解决
|
5月前
|
人工智能 自然语言处理
如何使用 Co-STAR 模型来设计提示词
如何使用 Co-STAR 模型来设计提示词
496 0
|
11月前
|
存储 监控 数据库
大师学SwiftUI第18章Part2 - 存储图片和自定义相机
在前面的示例中,我们在屏幕上展示了图片,但也可以将其存储到文件或数据库中。另外有时使用相机将照片存储到设备的相册薄里会很有用,这样可供其它应用访问。UIKit框架提供了如下两个保存图片和视频的函数。 •
380 0
|
6月前
|
JavaScript 前端开发 Windows
blog-engine-08-vuepress 以 Markdown 为中心的静态网站生成器
对比多个博客引擎如 Jekyll, Hugo, Hexo, Pelican, Gatsby, VuePress 和 Nuxt.js,文章提供了安装和入门指南。VuePress 是专为 Vue.js 爱好者设计的静态网站生成器,强调 Vue 集成、Markdown 编写、美观默认主题及插件系统。安装涉及 Node.js,通过全局安装 VuePress,然后创建、预览、构建和部署静态文件。适合技术文档和简单博客,但对非 Vue.js 用户有一定学习曲线。
|
机器学习/深度学习 存储 并行计算
【计算机视觉】Fast Segment Anything 安装步骤和示例代码解读(含源代码)
快速分段任意模型 (FastSAM) 是一种 CNN 分段任意模型,仅由 SAM 作者发布的 SA-1B 数据集的 2% 进行训练。 FastSAM 的性能与 SAM 方法相当,运行速度提高了 50 倍。
|
机器学习/深度学习 人工智能 算法
强化学习从基础到进阶-案例与实践[5.1]:Policy Gradient-Cart pole游戏展示
强化学习从基础到进阶-案例与实践[5.1]:Policy Gradient-Cart pole游戏展示
|
JavaScript 前端开发
基于node.js开发的文章生成器(八、网页版本的文章生成器--终章)
# 引言 不知不觉,我们的文章生成器已经迎来了终章,这是网页版狗屁不通文章生成器的终章,也是本系列的最后一章。接下来我就带着大家完成最后这一部分的学习。 # 项目结构 项目目录如下 ![image.png](https://ucc.alicdn.com/pic/developer-ecology/y3obldvnqeb54_63ca3afcfe4d4491ad90c23e73c0f2e1.png) 上面就是项目的目录,分别是lib文件夹,node_modules目录,resources文件夹,axios文件夹,router文件夹,和router_handle目录。 # lib目
|
Web App开发 移动开发 前端开发
巧用 display: contents 增强页面语义
巧用 display: contents 增强页面语义
395 1