[SwiftUI] CustomPopUpView ์• ๋‹ˆ๋ฉ”์ด์…˜ ํšจ๊ณผ ํ•ด๊ฒฐํ•˜๊ธฐ

2024. 6. 27. 20:49ยท๐ŸŽ Dev/๊ตฌํ˜„

 
 
 
 
 

 
 
 
Topic:
 

- ์ปค์Šคํ…€ ํŒ์—…์„ ๋„์šธ ๋•Œ ์• ๋‹ˆ๋ฉ”์ด์…˜ ํšจ๊ณผ ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ์ฃผ๊ธฐ
---> SwiftUI์—์„œ fullScreen์œผ๋กœ ํ™”๋ฉด์ „ํ™˜ ์• ๋‹ˆ๋ฉ”์ด์…˜ ํšจ๊ณผ๋ฅผ ์ฃผ๊ธฐ์—” ํ•œ์ •์ ์ด์—ˆ๊ณ , UIKit์—์„œ ์ž‘๋™ ๋˜๋Š” ๊ฑธ ๋ฆฌํŒฉํ† ๋ง ํ•  ๋•Œ ๋˜‘๊ฐ™์ด ๊ตฌํ˜„ํ•ด์•ผ ํ–ˆ๊ธฐ์— ํ™”๋ฉด์ „ํ™˜์„ ํ•  ๋•Œ fullScreen์„ ์“ฐ์ง€ ์•Š๋Š” ๋ฐฉ๋ฒ•์„ ์ฐพ์•„์•ผํ–ˆ๋‹ค.
 
 
 

 
์ ‘๊ทผ:
 
1. ZStack์„ ํ™œ์šฉํ•ด์„œ ๋ทฐ ์œ„์— ๋ทฐ๋ฅผ ๋„์šฐ์ž! ๊ฐ€ ์ด๋ฒˆ ์ด์Šˆ ํ•ด๊ฒฐ์ ‘๊ทผ์˜ ์ „๋ถ€๋‹ค.
 
*** ์ด ํฌ์ŠคํŠธ๋ฅผ ์œ„ํ•ด ์ž‘์„ฑํ•œ ์ฝ”๋“œ๋“ค์€ ํ™”๋ฉด์ „ํ™˜์„ ์ค‘์ ์œผ๋กœ ์ง  ์ฝ”๋“œ์ด๊ธฐ์— UI๋Š” ๊ณ ๋ คํ•˜์ง€ ์•Š์•˜๋‹ค. (์ฆ‰, UI ๋ ˆ์ด์•„์›ƒ ์žก์€ ์ฝ”๋“œ๋Š” ์ชผ๊ธˆ...  ๋”๋Ÿฝ๋‹ค.)
 
 
1-1. ์™œ ZStack์„ ์“ฐ๊ณ ์ž ํ–ˆ๋ƒ - ํ•˜๋ฉด, ZStack์— ๋Œ€ํ•œ ์ •์˜๊ฐ€ ํ•„์š”ํ•˜๋‹ค.
 
A view that overlays its subviews, aligning them in both axes. (...) The ZStack assigns each successive subview a higher z-axis value than the one before it, meaning later subviews appear โ€œon topโ€ of earlier ones.

 

๋ผ๊ณ  ์• ํ”Œ ๊ณต์‹๋ฌธ์„œ๊ฐ€ ์„ค๋ช…ํ•ด์ฃผ๊ณ  ์žˆ๋Š”๋ฐ, ZStack์˜ ํŠน์ง•์€ ๋Šฆ์€ ์ˆœ๋ฒˆ๋Œ€๋กœ ๋ทฐ์˜ ์ƒ๋‹จ์— ์Œ“์ธ๋‹ค๋Š” ๊ฑฐ๋‹ค. ๊ทธ๋Ÿฌ๋‹ˆ๊นŒ, ๊ฐ€์žฅ ๋งˆ์ง€๋ง‰ ์ˆœ๋ฒˆ์ธ ๋ทฐ๊ฐ€ ๊ฐ€์žฅ ์ตœ์ƒ๋‹จ์— ๋ณด์—ฌ์ง„๋‹ค.

 

 

1-2. ZStack์„ ํ™œ์šฉํ•˜๊ธฐ ์œ„ํ•œ ์กฐ๊ฑด์€ ํ•˜๋‚˜๋‹ค. ํŒ์—… View์™€ ํŒ์—…์„ ๋„์›Œ์ฃผ๋Š” View ๋ชจ๋‘ ZStack์œผ๋กœ ๊ตฌ์„ฑ๋˜์–ด ์žˆ์–ด์•ผํ•œ๋‹ค. ๊ทธ๋ž˜์•ผ ZStack ๋ผ๋ฆฌ ๋ทฐ์˜ ์ˆœ์„œ๋ฅผ ์ •ํ•ด์„œ ๋ณด์—ฌ์ค„ ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.
 

 
<ํŒ์—… View>
 

import SwiftUI

struct CustomPopUpView: View {
    // MARK: - properites
    // ํ•ด๋‹น ํ”„๋กœํผํ‹ฐ๋กœ ํŒ์—…์„ ๋„์šธ์ง€ ๋ง์ง€๋ฅผ ๋ถ€๋ชจ๋ทฐ์˜ state ๊ฐ’์œผ๋กœ ์ „๋‹ฌ ๋ฐ›์„ ์˜ˆ์ •
    @Binding var showPopUp: Bool
    
    let title: String // ๊ณต์ง€ ์ด๋ฆ„
    let message: String // ๊ณต์ง€ ๋‚ด์šฉ
    let btn1: String // ์™ผ์ชฝ ๋ฒ„ํŠผ text
    let btn2: String // ์˜ค๋ฅธ์ชฝ ๋ฒ„ํŠผ text
    
    // MARK: - body
    var body: some View {
        
        ZStack {
            
            /// Background - ํŒ์—…์„ ๋„์šธ ๋•Œ ํŒ์—… ์™ธ ๋ฐฐ๊ฒฝ์„ ์–ด๋‘ก๊ฒŒ ํ•˜๊ณ ์ž ํ–ˆ๋‹ค
            Rectangle()
                .background(.black)
                .opacity(0.2)
            
            VStack(spacing: 12) {
                
                Text(title)
                    .frame(height: 30)
                    .fontWeight(.bold)
                    .padding(.top, 12)
                
                Divider()
                
                Text(message)
                    .multilineTextAlignment(.center)
                    .padding(.vertical, 12)
                
                HStack(spacing: 8) {
                    
                    Button(action: {
                        
                        print("ํ™•์ธ ๋ˆ„๋ฆ„")
                        
                    }, label: {
                        Text(btn1)
                            .fontWeight(.semibold)
                            .foregroundStyle(.white)
                            .padding(.vertical, 8)
                            .frame(width: 100)
                    })
                    .background(
                        /// ๋ฒ„ํŠผ ํ…Œ๋‘๋ฆฌ
                        RoundedRectangle(cornerRadius: 8)
                            .foregroundStyle(.brown)
                        
                    )
                    
                    Button(action: {
                        
                        print("์ทจ์†Œ ๋ˆ„๋ฆ„")
                        showPopUp = false
                        
                    }, label: {
                        Text(btn2)
                            .foregroundStyle(.gray)
                            .padding(.vertical, 8)
                            .frame(width: 100)

                            
                    })
                    .background(
                        /// ๋ฒ„ํŠผ ํ…Œ๋‘๋ฆฌ
                        RoundedRectangle(cornerRadius: 8)
                            .stroke(.gray, lineWidth: 1)
                    )
                    
                }
                .frame(height: 44)
                .padding(.bottom, 12)

                
            }
            .background(.white)
            .roundedCorner(16, corners: .allCorners)
            .padding(.horizontal, 20)
            
        }
        // ZStack์„ ํ™”๋ฉด์— ๊ฝ‰ ์ฑ„์šฐ๋„๋ก ํ•ด์„œ fullScreen์ฒ˜๋Ÿผ ๋ณด์ด๊ฒŒ ํ•จ
        .ignoresSafeArea(.all)
        
    }
}

#Preview {
    CustomPopUpView(
        showPopUp: .constant(false),
        title: "๊ณต์ง€",
        message: "์ •๋ง๋กœ ์‚ญ์ œ๋ฅผ ํ•˜์‹œ๊ฒ ์Šต๋‹ˆ๊นŒ?",
        btn1: "ํ™•์ธ",
        btn2: "์ทจ์†Œ"
    )
}

 
-- ํ•ด๋‹น ๋ทฐ์˜ ๊ตฌํ˜„ ๋‚ด์šฉ ๋ฐ ์„ค๋ช…์€ ์ฃผ์„์œผ๋กœ ์ž‘์„ฑํ–ˆ๋‹ค.
 
 
 
 
<ํŒ์—…์„ ๋„์šธ View>
 

import SwiftUI

struct ContentView: View {
    
    // ํŒ์—… ๋„์›€ ์—ฌ๋ถ€ ๊ฒฐ์ •
    @State private var showPopUp: Bool = false
    
    var body: some View {
        
        ZStack {
            
            VStack(spacing: 16) {
                Text("test popup animation")
                    .font(.title)
                
                Button("ํŒ์—… ๋„์šฐ๊ธฐ") {
                    print("๋ฒ„ํŠผ ๋ˆ„๋ฆ„")
                    
                    // ํŒ์—… ๋„์šฐ๊ธฐ ๋ฒ„ํŠผ์„ ๋ˆ„๋ฅด๋ฉด state ๋ณ€์ˆ˜์— ๋ฐ”๋€ ๊ฐ’์„ ์ „๋‹ฌํ•ด์ค€๋‹ค
                    // withAnimation์œผ๋กœ ์• ๋‹ˆ๋ฉ”์ด์…˜ ํšจ๊ณผ๋ฅผ ์ปค์Šคํ…€ํ•ด์ค€๋‹ค. 
                    // withAnimation์— ์ข…๋ฅ˜์™€ ํƒ€์ด๋จธ ์ง€์ •๊นŒ์ง€ ๊ฐ€๋Šฅํ•˜๋‹ˆ ํ•„์š” ์‹œ ์ฐพ์•„๋ณด๊ธฐ!
                    withAnimation {
                        showPopUp = true
                    }
                    
                }
                .foregroundStyle(.white)
                .padding(.horizontal, 20)
                .frame(height: 50)
                .background(.gray)
            }
            
            // showPopUp ๊ฐ’์ด true๋กœ ๋ฐ”๋€Œ๋ฉด ํ•ด๋‹น ํŒ์—…๋ทฐ๋ฅผ ๋„์›Œ์ค€๋‹ค. (ZStack ํ™œ์šฉ!)
            if showPopUp {
                showPopUpView(
                    title: "์•ˆ๋‚ด",
                    message: "์ •๋ง๋กœ ์‚ญ์ œ๋ฅผ ํ•˜์‹œ๊ฒ ์Šต๋‹ˆ๊นŒ? \n์‚ญ์ œ ์‹œ ๋ฐ์ดํ„ฐ๋ฅผ ๋ณต๊ตฌํ•˜์‹ค ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.",
                    btn1: "ํ™•์ธ",
                    btn2: "์ทจ์†Œ"
                )
            }
            
        }
        
    }
}

extension ContentView {
    
    // showPopUP๊ฐ’์— ๋”ฐ๋ผ ๋„์›Œ์ค„ ๋ทฐ๋ฅผ ํŽธ์˜์ƒ extension์— ํ•จ์ˆ˜๋กœ view๋ฅผ ๊ตฌํ˜„ํ•ด๋‘์—ˆ๋‹ค.
    func showPopUpView(
        title: String,
        message: String,
        btn1: String,
        btn2: String
    ) -> some View {
        
        CustomPopUpView(
            showPopUp: $showPopUp,
            title: title,
            message: message,
            btn1: btn1,
            btn2: btn2
        )
        
    }
    
    
}

#Preview {
    ContentView()
}

 
-- ํ•ด๋‹น ๋ทฐ์˜ ๊ตฌํ˜„ ๋‚ด์šฉ ๋ฐ ์„ค๋ช…์€ ์ฃผ์„์œผ๋กœ ์ž‘์„ฑํ–ˆ๋‹ค.
 
 
 
(+)
 
 
RoundedCorner๋Š” ํƒ€ ๋ธ”๋กœ๊ทธ๋“ค์—์„œ ์‰ฝ๊ฒŒ ์ฐธ๊ณ ํ•  ์ˆ˜ ์žˆ๋Š” ์ฝ”๋“œ๋ฅผ ์ฐธ๊ณ ํ•ด์„œ ์ž‘์„ฑํ–ˆ๋‹ค.
 

struct RoundedCorner: Shape {
    var radius: CGFloat = .infinity
    var corners: UIRectCorner = .allCorners
    
    func path(in rect: CGRect) -> Path {
        let path = UIBezierPath(roundedRect: rect, byRoundingCorners: corners, cornerRadii: CGSize(width: radius, height: radius))
        return Path(path.cgPath)
    }
}

extension View {
    
    func roundedCorner(_ radius: CGFloat, corners: UIRectCorner) -> some View {
        clipShape(RoundedCorner(radius: radius, corners: corners) )
    }
    
}

 
 
 
 
 
 
 
References
 
https://developer.apple.com/documentation/swiftui/zstack

ZStack | Apple Developer Documentation

A view that overlays its subviews, aligning them in both axes.

developer.apple.com

 

์ €์ž‘์žํ‘œ์‹œ (์ƒˆ์ฐฝ์—ด๋ฆผ)

'๐ŸŽ Dev > ๊ตฌํ˜„' ์นดํ…Œ๊ณ ๋ฆฌ์˜ ๋‹ค๋ฅธ ๊ธ€

[SwiftUI] Infinite Carousel ๊ตฌํ˜„ํ•˜๊ธฐ 1 (feat. Timer)  (0) 2024.09.05
[SwiftUI] pagerView ๋งŒ๋“ค๊ธฐ (iOS ๋ฒ„์ „๋Œ€์‘)  (2) 2024.08.14
[SwiftUI] ์Šค์œ ๋กœ ์„น์…˜ ์ ‘์—ˆ๋‹คํˆ๋‹ค ๊ตฌํ˜„ํ•˜๊ธฐ  (0) 2024.06.21
[SwiftUI] pre/next buttons๊ฐ€ ์žˆ๋Š” ์ด๋ฏธ์ง€ ์Šฌ๋ผ์ด๋” ๊ตฌํ˜„ํ•˜๊ธฐ  (2) 2024.05.16
[SwiftUI] TabView page indicator ์ปค์Šคํ…€ํ•˜๊ธฐ  (1) 2024.04.25
'๐ŸŽ Dev/๊ตฌํ˜„' ์นดํ…Œ๊ณ ๋ฆฌ์˜ ๋‹ค๋ฅธ ๊ธ€
  • [SwiftUI] Infinite Carousel ๊ตฌํ˜„ํ•˜๊ธฐ 1 (feat. Timer)
  • [SwiftUI] pagerView ๋งŒ๋“ค๊ธฐ (iOS ๋ฒ„์ „๋Œ€์‘)
  • [SwiftUI] ์Šค์œ ๋กœ ์„น์…˜ ์ ‘์—ˆ๋‹คํˆ๋‹ค ๊ตฌํ˜„ํ•˜๊ธฐ
  • [SwiftUI] pre/next buttons๊ฐ€ ์žˆ๋Š” ์ด๋ฏธ์ง€ ์Šฌ๋ผ์ด๋” ๊ตฌํ˜„ํ•˜๊ธฐ
Callie_
Callie_
  • Callie_
    CalliOS
    Callie_
  • ์ „์ฒด
    ์˜ค๋Š˜
    ์–ด์ œ
    • ๋ถ„๋ฅ˜ ์ „์ฒด๋ณด๊ธฐ
      • ๐ŸŽ APPLE
      • ๐ŸŽ Dev
        • Swift
        • UIKit
        • SwiftUI
        • Issue
        • ๊ตฌํ˜„
      • ๐ŸŽ Design
        • HIG
      • โš™๏ธ CS
      • ๐Ÿ’ก ์•Œ๊ณ ๋ฆฌ์ฆ˜
        • ํ”„๋กœ๊ทธ๋ž˜๋จธ์Šค
        • ๋ฐฑ์ค€
      • ๐ŸŸ๏ธ ์ง๊ด€๋กœ๊ทธ (์ถœ์‹œ์•ฑ)
        • ์—…๋ฐ์ดํŠธ
      • ๐ŸŒฑ SeSAC iOS 3๊ธฐ
  • ๋ธ”๋กœ๊ทธ ๋ฉ”๋‰ด

    • ํ™ˆ
    • ํƒœ๊ทธ
  • ๋งํฌ

  • ๊ณต์ง€์‚ฌํ•ญ

  • ์ธ๊ธฐ ๊ธ€

  • ํƒœ๊ทธ

    Swift
    DidEndOnExit
    modalPresentStyle
    Snapshot
    ํ™”๋ฉด์ „ํ™˜
    .OverFullScreen
    Entry Point
    Infoํƒญ
    apply
    CocoaTouchFramework
    TableViewCell
    cornerradius
    .fullScreen
    layer.shadow
    SwiftUI
    addTarget
    ios
    CustomView
    ์ƒ๋ช…์ฃผ๊ธฐ
    clipsToBound
    assets
    DiffableDataSource
    IBAction
    TapGestureRecognizer
    Enum
    SeSAC
    ๋„คํŠธ์›Œํฌํ†ต์‹ 
    diffable
    IBOutlet
    stroyboard
  • ์ตœ๊ทผ ๋Œ“๊ธ€

  • ์ตœ๊ทผ ๊ธ€

  • hELLOยท Designed By์ •์ƒ์šฐ.v4.10.0
Callie_
[SwiftUI] CustomPopUpView ์• ๋‹ˆ๋ฉ”์ด์…˜ ํšจ๊ณผ ํ•ด๊ฒฐํ•˜๊ธฐ
์ƒ๋‹จ์œผ๋กœ

ํ‹ฐ์Šคํ† ๋ฆฌํˆด๋ฐ”