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

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

快来动手试试吧!


相关文章
|
8月前
|
机器学习/深度学习 自然语言处理 算法
【CV大模型SAM(Segment-Anything)】真是太强大了,分割一切的SAM大模型使用方法:可通过不同的提示得到想要的分割目标
【CV大模型SAM(Segment-Anything)】真是太强大了,分割一切的SAM大模型使用方法:可通过不同的提示得到想要的分割目标
|
机器学习/深度学习 存储 并行计算
【计算机视觉】Fast Segment Anything 安装步骤和示例代码解读(含源代码)
快速分段任意模型 (FastSAM) 是一种 CNN 分段任意模型,仅由 SAM 作者发布的 SA-1B 数据集的 2% 进行训练。 FastSAM 的性能与 SAM 方法相当,运行速度提高了 50 倍。
|
机器学习/深度学习 PyTorch 算法框架/工具
使用PyTorch构建GAN生成对抗网络源码(详细步骤讲解+注释版)01 手写字体识别
生成对抗网络(GAN)是一种用于生成新的照片,文本或音频的模型。它由两部分组成:生成器和判别器。生成器的作用是生成新的样本,而判别器的作用是识别这些样本是真实的还是假的。两个模型相互博弈,通过不断调整自己的参数来提高自己的能力。生成器希望判别器错误地认为其生成的样本是真实的,而判别器希望能正确地识别生成器生成的样本是假的。最终,生成器会学到如何生成逼真的样本,而判别器会学到如何区分真假样本。
|
存储 数据可视化 计算机视觉
目标检测的Tricks | 【Trick10】工具类文件调用(coco评价指标包、日志工具、Tensorboard工具...)
目标检测的Tricks | 【Trick10】工具类文件调用(coco评价指标包、日志工具、Tensorboard工具...)
760 0
目标检测的Tricks | 【Trick10】工具类文件调用(coco评价指标包、日志工具、Tensorboard工具...)
LIO-SAM代码逐行解读(1)-准备工作
LIO-SAM代码逐行解读(1)-准备工作
441 0
|
存储
LIO-SAM代码逐行解读(5)-点云匹配及后端优化模块
LIO-SAM代码逐行解读(5)-点云匹配及后端优化模块
1000 0
|
前端开发 JavaScript
细读 ES6 | Generator 生成器
细读 ES6 | Generator 生成器
210 0
细读 ES6 | Generator 生成器
|
编解码 Go 定位技术
Google Earth Engine ——数据全解析专辑(“CSP/ERGo/1_0/Global/SRTM_landforms“)SRTM 90m地貌数据集
Google Earth Engine ——数据全解析专辑(“CSP/ERGo/1_0/Global/SRTM_landforms“)SRTM 90m地貌数据集
187 0
Google Earth Engine ——数据全解析专辑(“CSP/ERGo/1_0/Global/SRTM_landforms“)SRTM 90m地貌数据集
|
数据处理 异构计算 索引
ML之catboost:catboost模型中常用的Pool类型数据结构源代码解读、案例应用之详细攻略
ML之catboost:catboost模型中常用的Pool类型数据结构源代码解读、案例应用之详细攻略
|
机器学习/深度学习 算法 图形学
Dataset:fetch_20newsgroups(20类新闻文本)数据集的简介、安装、使用方法之详细攻略
Dataset:fetch_20newsgroups(20类新闻文本)数据集的简介、安装、使用方法之详细攻略

热门文章

最新文章