apple/SwiftUI & Combine

[SwiftUI] Toast, popup

lgvv 2022. 5. 23. 15:36

Toast, popup

 

✅ 이번에는 오픈소스를 활용해서 toast와 popup을 하는 작업을 진행

이전에 플러터나 안드로이드 개발할 때, 주로 사용하던 UI인데, iOS에도 비슷하게 구현할 수 있다고 해서 공부해봄.

 

✅ 오픈소스

https://github.com/exyte/PopupView

 

GitHub - exyte/PopupView: Toasts and popups library written with SwiftUI

Toasts and popups library written with SwiftUI. Contribute to exyte/PopupView development by creating an account on GitHub.

github.com

 

오픈소스에서 SPM을 지원한다고한다.

처음 공부할때는 최근에는 CocoaPod이 당연하다 싶었는데 최근에는 SPM이 퍼스트 파티라서 이쪽을 더 많이 사용하는 것 같다.

(카르타고 안써봐서 어떤지 모름)

 

 

✅ 요약

 

1. 컬러를 Extension 처리하는 방법

  -> 여기 코드는 봐두면 정말 좋은 정보

  -> HexCode를 기준으로 Color를 변환하여 사용할 수 있음.

 

2. 오픈소스를 이용법

  -> 변수가 아니라 함수로도 some View를 사용할 수가 있습니다

 

 

✅ Color

Color를 extension을 통해서 사용한다.

import Foundation
import SwiftUI

extension Color {
    init(hexcode: String) {
        let scanner = Scanner(string: hexcode)
        var rgbValue: UInt64 = 0
        
        scanner.scanHexInt64(&rgbValue)
        
        let red = (rgbValue & 0xff0000) >> 16
        let green = (rgbValue & 0xff00) >> 8
        let blue = rgbValue & 0xff
        
        self.init(red: Double(red) / 0xff, green: Double(green) / 0xff, blue: Double(blue) / 0xff)
        
    }
}

✅ 컬러코드 사용법

.background(Color(hexcode: "8227b0"))

 

 

오픈소스를 이용하여 예쁜 디자인과 사용성을 높여보자!

import SwiftUI
import ExytePopupView

struct ContentView: View {
    
    @State var shouldShowBottomSolidMessage : Bool = false
    
    @State var shouldShowBottomToastMessage : Bool = false
    
    @State var shouldShowTopSolidMessage : Bool = false
    
    @State var shouldShowTopToastMessage : Bool = false
    
    @State var shouldShowPopup : Bool = false
    
    func createBottomSolidMessage() -> some View {
        HStack(alignment: .center,spacing: 10){
                            
            Image(systemName: "book.fill")
                .font(.system(size: 40))
                .foregroundColor(Color.white)
//                        .background(Color.yellow)
            VStack(alignment: .leading, spacing: 0){
                Text("안내 메세지")
                    .fontWeight(.black)
                    .foregroundColor(Color.white)
                
                Text("토스트 메세지 입니다!asdfasdfaasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdf")
                    .lineLimit(2)
                    .font(.system(size: 14))
                    .foregroundColor(Color.white)
                // Divider의 역할은 텍스트가 길어져도 이미지가 중앙에 정렬되게 하기 위함.
                Divider().opacity(0) 
            }
//                    .background(Color.red)
        }
        .padding(.vertical, 5)
        .padding(.horizontal, 10)
        .frame(width: UIScreen.main.bounds.width)
        // 노치의 여부에 따라서 safeArea를 정해야하기 때문에
        .padding(.bottom, UIApplication.shared.windows.first?.safeAreaInsets.bottom == 0 ? 0 : 15)
        .background(Color.purple)
    }
    
    func createTopSolidMessage() -> some View {
            HStack(alignment: .center,spacing: 10){
                                
                Image("jeongDaeRi")
                    .resizable()
                    .aspectRatio(contentMode: ContentMode.fill)
                    .frame(width: 60, height: 60)
                    .cornerRadius(10)
                VStack(alignment: .leading, spacing: 5){
                    Text("개발하는 정대리님의 메세지")
                        .fontWeight(.black)
                        .foregroundColor(Color.white)
                    
                    Text("안녕하세요 오늘도 빡코딩하고 계신가요? \n오늘은 토스트 메세지와 팝업에 대해 알아보겠습니다!")
                        .font(.system(size: 14))
                        .foregroundColor(Color.white)
                        Divider().opacity(0)
                }
    //                    .background(Color.red)
            }
            .padding(.vertical, 5)
            .padding(.horizontal, 10)
            .frame(width: UIScreen.main.bounds.width)
            .padding(.top, UIApplication.shared.windows.first?.safeAreaInsets.bottom == 0 ? 20 : 35)
            .background(Color.blue)
        }
    
    func createBottomToastMessage() -> some View {
        HStack(alignment: .top,spacing: 10){
                                    
                    Image("cat")
                        .resizable()
                        .aspectRatio(contentMode: ContentMode.fill)
                        .offset(y: 10)
                        .frame(width: 60, height: 60)
                        .cornerRadius(10)
                        
        //                        .background(Color.yellow)
                    VStack(alignment: .leading){
                        Text("내 고양이")
                            .fontWeight(.black)
                            .foregroundColor(Color.white)
                        
                        Text("토스트 메세지 입니다!asdfasdfaasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfaasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdf")
                            
                            .font(.system(size: 14))
                            .foregroundColor(Color.white)
                        Divider().opacity(0)
                    }
        //                    .background(Color.red)
                }
                .padding(15)
                .frame(width: 300)
                .background(Color.green)
                .cornerRadius(20)
                
        
    }
    
    func createTopToastMessage() -> some View {
        HStack(alignment: .center,spacing: 10){
                                    
                    Image(systemName: "paperplane.fill")
                        .font(.system(size: 25))
                        .padding(.leading, 5)
                        .foregroundColor(Color.white)
                    VStack(alignment: .leading, spacing: 2){
                        Text("정대리님의 메세지")
                            .fontWeight(.black)
                            .foregroundColor(Color.white)
                        Text("오늘도 빡코딩하고 계신가요?")
                            .font(.system(size: 14))
                            .lineLimit(1)
                            .foregroundColor(Color.white)
                    }
                    .padding(.trailing, 15)
        //                    .background(Color.red)
                }
                .padding(.vertical, 5)
                .padding(.horizontal, 10)
                .background(Color.orange)
                .cornerRadius(25)
                .padding(.top, UIApplication.shared.windows.first?.safeAreaInsets.bottom == 0 ? 0 : 30)
        
                    
    }
    
    func createPopup() -> some View {
        VStack(spacing: 10) {
                    Image("jeongDaeRi")
                        .resizable()
                        .aspectRatio(contentMode: ContentMode.fit)
                        .frame(width: 100, height: 100)

                    Text("개발하는 정대리")
                        .foregroundColor(.white)
                        .fontWeight(.bold)

                    Text("한국에서 개발자로 살아남기!\n예전에 저처럼 프로그래머가 되고 싶지만\n그 길을 몰라 해매는 분들에게 도움 되고자\n이 채널을 운영하기 시작했습니다.\n\n프로그램에 관심 있는 분들이나 취업 준비생 분들께\n조금이나마 도움이 되었으면 합니다.\n아자 아자 화이팅!😍👍")
                        .font(.system(size: 12))
                        .foregroundColor(Color(red: 0.9, green: 0.9, blue: 0.9))
                        .multilineTextAlignment(.center)

                    Spacer().frame(height: 10) // Spacer에도 frame 지정 가능

                    Button(action: {
                        self.shouldShowPopup = false
                    }) {
                        Text("닫기")
                            .font(.system(size: 14))
                            .foregroundColor(.black)
                            .fontWeight(.bold)
                    }
                    .frame(width: 100, height: 40)
                    .background(Color.white)
                    .cornerRadius(20.0)
                }
        //        .padding(EdgeInsets(top: 20, leading: 20, bottom: 20, trailing: 20))
                    .padding(.horizontal, 10)
                .frame(width: 300, height: 400)
                .background(Color(hexcode: "8227b0"))
                .cornerRadius(10.0)
                .shadow(radius: 10)
    }
    
    var body: some View {
        ZStack{
            VStack(spacing: 10){
                
                Button(action: {
                    self.shouldShowBottomSolidMessage = true
                }, label: {
                    Text("바텀솔리드메세지")
                        .font(.system(size: 25))
                        .fontWeight(.bold)
                        .foregroundColor(Color.white)
                        .padding()
                        .background(Color.purple)
                        .cornerRadius(10)
                })

                Button(action: {
                    self.shouldShowBottomToastMessage = true
                }, label: {
                    Text("바텀토스트메세지")
                        .font(.system(size: 25))
                        .fontWeight(.bold)
                        .foregroundColor(Color.white)
                        .padding()
                        .background(Color.green)
                        .cornerRadius(10)
                })

                Button(action: {
                       self.shouldShowTopSolidMessage = true
                   }, label: {
                       Text("탑솔리드메세지")
                           .font(.system(size: 25))
                           .fontWeight(.bold)
                           .foregroundColor(Color.white)
                           .padding()
                           .background(Color.blue)
                           .cornerRadius(10)
                   })

                Button(action: {
                    self.shouldShowTopToastMessage = true
                }, label: {
                    Text("탑토스트메세지")
                        .font(.system(size: 25))
                        .fontWeight(.bold)
                        .foregroundColor(Color.white)
                        .padding()
                        .background(Color.orange)
                        .cornerRadius(10)
                })

                Button(action: {
                    self.shouldShowPopup = true
                }, label: {
                    Text("팝업")
                        .font(.system(size: 25))
                        .fontWeight(.bold)
                        .foregroundColor(Color.white)
                        .padding()
                        .background(Color(hexcode: "8227b0"))
                        .cornerRadius(10)
                })

            } // Vstack
        } // Zstack
        // popup의 경우 오픈소스에서 제공하는 것들
            .edgesIgnoringSafeArea(.all)
            .popup(
            	isPresented: $shouldShowBottomSolidMessage, // 나타나는 조건 binding
                type: .toast, // 타입은 토스트로
                position: .bottom, // 아래에서
                animation: .easeInOut, // 애니메이션 효과
                autohideIn: 2, // 2초간 지속
                closeOnTap: true, // 내부 영역 탭하면 사라짐
                closeOnTapOutside: true, // 바깥 영역 탭하면 사라짐
                view: { // 우리가 지정한 뷰 - 클로저로 사용 가능
                self.createBottomSolidMessage()
            })
            .popup(
            	isPresented: $shouldShowBottomToastMessage,
            	type: .floater(verticalPadding: 20), // 플로팅 타입은 위에 딱 붙어서!
                position: .bottom,
                animation: .spring(),
                autohideIn: 2,
                closeOnTap: true,
                closeOnTapOutside: true,
                view: {
                self.createBottomToastMessage()
            })
            .popup(isPresented: $shouldShowTopSolidMessage, type: .toast, position: .top, animation: .easeInOut, autohideIn: 2, closeOnTap: true, closeOnTapOutside: true, view: {
                self.createTopSolidMessage()
            })
            .popup(isPresented: $shouldShowTopToastMessage, type: .floater(verticalPadding: 20), position: .top, animation: .spring(), autohideIn: 2, closeOnTap: true, closeOnTapOutside: true, view: {
                self.createTopToastMessage()
            })
            .popup(isPresented: $shouldShowPopup, type: .default, position: .bottom, animation: .spring(), closeOnTap: false, closeOnTapOutside: false, view: {
                self.createPopup()
            })
        
        
    }
}

 

✅ 오픈소스에서 .popup쪽을 살펴보자.

popup쪽 주석을 보면 쉽게 이해할 수 있겠지만 type 부분에 대한 설명을 첨가하자면

float은 지정한 position에 딱 붙어서 나타난다.

toast는 안드로이드에서 제공하는 것과 마찬가지로 나타난다.

 

 

 

 

결과물
결과물