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

前言

在本章中,你将学会使用Segment分段器、LazyVGrid垂直网格、ImagePicker图片选择器构建一个Logo生成器

在上一章中,我们完善了SearchBar搜索栏、TabView底部导航,还有做了一个Loading加载动作。最近突然有个想法,如果把色卡图片进行组合,这不就是一个简单的Logo了吗?我能不能做个Logo生成器

说干就干,我们继续完成App的相关内容。

样式部分

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

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

2.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在主视图展示看看效果:

3.png

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

.onTapGesture {
    bgColor = Color.Hex(item.cardBGColor)
}

4.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的变量,就实现了切换图标的效果。

我们来运行下看看效果:

5.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填充色来看看效果。

6.png

功能进阶

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

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

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

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

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

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

9.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绑定了打开本地图片弹窗的条件,我们把这个触发条件放在我们点击“相册”选项的操作。示例:

10.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的有无,展示不同的视图。效果如下:

11.png

项目预览

12.png

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

快来动手试试吧!

如果本专栏对你有帮助,不妨点赞、评论、关注~

我正在参与掘金技术社区创作者签约计划招募活动,点击链接报名投稿

猜你喜欢

转载自juejin.im/post/7118334943490473992