ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • SwiftUI - 동영상 배경을 가진 모던 로그인 화면 만들기
    SwiftUI 2025. 5. 17. 21:18

    안녕하세요 :) 🧀

     

    이번 포스팅에서는 로그인 화면에 동영상 배경을 구현하는 방법을 알아보겠습니다.

    먼저 완성된 결과물을 살펴볼까요?

     

    위와 같은 화면을 만들기 위해서는 동영상 파일이 필요합니다. 저는 아래 사이트에서 멋진 동영상을 하나 구했어요.
    https://www.pexels.com

    동영상 파일이 너무 길거나 용량이 큰 경우에는 다운 샘플링이 필요한데, 저는 GPT를 활용해서 파일을 4MB 크기로 줄였습니다.

     

    그럼, 동영상 파일이 준비되었으면 이제 하나씩 차근차근 알아보도록 하겠습니다!


    AVQueuePlayer, AVPlayerLooper 구성

    import AVFoundation
    import Foundation
    
    final class LoopingPlayer {
      let player: AVQueuePlayer
      private var looper: AVPlayerLooper?
      
      init(videoName: String, videoType: String = "mp4") {
        // 1) 로컬 번들 혹은 URL로 AVPlayerItem 생성
        guard let url = Bundle.main.url(forResource: videoName, withExtension: videoType) else {
          fatalError("비디오 파일을 찾을 수 없습니다.")
        }
        let asset = AVAsset(url: url)
        let item = AVPlayerItem(asset: asset)
        
        // 2) AVQueuePlayer + looper
        self.player = AVQueuePlayer()
        self.looper = AVPlayerLooper(player: player, templateItem: item)
        
        // 3) 사운드 제거, 끝났을 때 아무 동작도 취하지 않음
        player.isMuted = true
        player.actionAtItemEnd = .none
      }
    }

     

    먼저 동영상을 반복 재생하기 위해 플레이어를 세팅합니다.

    AVQueuePlayerAVPlayerLooper를 활용하는데 간단하게 설명하면 아래와 같습니다.

     

    AVQueuePlayer

    AVPlayer를 상속한 플레이어로, 여러 AVPlayerItem을 순차적으로 재생할 수 있는 큐(Queue) 기능을 제공

    AVPlayerLooper

    AVQueuePlayer와 함께 사용해 지정한 템플릿 아이템을 끝없이 반복 재생해 주는 유틸리티 클래스

     

    그리고 아래와 같이 설정해 줌으로써 동영상을 재생하기 위한 준비가 끝났습니다!

    1️⃣ 번들 로딩: Bundle.main.url(forResource:withExtension:)으로 로컬에 있는 .mp4 파일을 찾아 AVAsset으로 감싼 뒤 AVPlayerItem으로 변환해 줍니다.

    2️⃣ 플레이어 설정:  AVQueuePlayer 위에 AVPlayerLooper를 적용하면 템플릿 아이템(동영상)이 끝날 때 자동으로 다시 재생되어 끊김 없는 루핑을 구현할 수 있습니다.

    3️⃣ 플레이어 상세 설정: 배경 영상의 사운드는 음소거 처리하고, 재생이 끝난 뒤 자동으로 일시정지되지 않도록 actionAtItemEnd를 .none으로 설정합니다.

     

    VideoPlayer View 배치

    import AVKit
    import SwiftUI
    
    struct VideoBackgroundView<Content: View>: View {
      private let loopingPlayer = LoopingPlayer(videoName: "winter")
      let content: () -> Content
      
      init(@ViewBuilder content: @escaping () -> Content) {
        self.content = content
      }
      
      var body: some View {
        GeometryReader { proxy in
          // 1) VideoPlayer를 전체 화면에 깔기
          VideoPlayer(player: loopingPlayer.player)
            .aspectRatio(contentMode: .fill) ✅
            .frame(width: proxy.size.width, height: proxy.size.height)
            .allowsHitTesting(false) ✅
            .onAppear {
              loopingPlayer.player.play()
            }
          
          // 2) 앞쪽에 올 UI
          content()
            .padding()
        }
        .ignoresSafeArea()
      }
    }
    import AVKit
    import SwiftUI
    
    struct ContentView: View {
      var body: some View {
        VideoBackgroundView {
          // TODO: 로그인 UI 배치하기
        }
      }
    }

     

    VideoPlayer에 위에서 생성한 플레이어를 전달하여 뷰가 나타날 때 재생하도록 지정해줍니다.

    이 상태에서 실행하면 동영상이 화면을 꽉 채우며 반복 재생되는 것을 확인할 수 있습니다.

     

    여기서 중요한 점✅ 이 두 가지가 있는데요!

    1. .aspectRatio(contentMode: .fill)
      • 제거하면 영상이 화면을 꽉 채우지 못해 어색해 보입니다.
      • 적용하면 VideoPlayer의 intrinsic size가 바뀌어 다른 뷰 배치에 영향을 줍니다.
      • 따라서 GeometryReader로 화면 크기를 읽어와 사이즈를 고정했습니다.
    2. .allowsHitTesting(false)
      • 제거하면 VideoPlayer가 기본적으로 터치 가능 상태가 되어, 화면을 탭 하면 10초 앞뒤, 재생/일시정지 버튼을 가진 비디오 컨트롤러가 나타납니다.
      • 적용하면 해당 뷰의 “히트 테스트(hit testing)”를 비활성화하여, 탭해도 아무 반응이 없도록 만듭니다.
      • 결과적으로 배경 비디오는 완전히 터치 불가능 상태가 되어, UI 위에 올려둔 버튼이나 제스처가 방해받지 않습니다.

     

    로그인 UI 배치

    만들어진 동영상 배경위에 로그인 UI들을 배치해 볼 차례입니다. 저는 SNS 로그인 버튼 3개와 간단한 텍스트를 1개 배치해 보았습니다.

    import AVKit
    import SwiftUI
    
    struct ContentView: View {
      let buttons: [(title: String, tint: Color, text: Color)] = [
        ("카카오로 시작하기", .yellow, .black),
        ("네이버로 시작하기", .green, .white),
        ("애플로 시작하기", .black, .white)
      ]
      
      var body: some View {
        VideoBackgroundView {
          VStack(spacing: 10) {
            Spacer()
            
            ForEach(buttons, id: \.title) { button in
              SocialLoginButton(
                title: button.title,
                tintColor: button.tint,
                textColor: button.text
              ) {
                print("\(button.title) tapped")
              }
            }
            
            Text("계정을 잊으셨나요?")
              .font(.system(size: 15))
              .foregroundColor(.white.opacity(0.7))
          }
          .padding(.bottom, 20)
        }
    }

     

    실행해 보니 버튼과 텍스트는 의도대로 배치됐지만, 동영상 배경 때문에 가독성과 가시성이 떨어져

    저는 배경 위에 어두운 그라데이션을 적용해 보았습니다🙂

    위에서 생성한 VideoBackgroundView에서 플레이어와 콘텐츠 사이에 LinearGradient를 구성해서 추가해 주면 🆗

    GeometryReader { proxy in
      VideoPlayer(player: loopingPlayer.player)
        .aspectRatio(contentMode: .fill)
        .frame(width: proxy.size.width, height: proxy.size.height)
        .allowsHitTesting(false)
        .onAppear {
          loopingPlayer.player.play()
        }
      
      LinearGradient(
        gradient: Gradient(colors: [
          Color.black.opacity(0.0),
          Color.black.opacity(0.4),
          Color.black.opacity(0.8),
          Color.black.opacity(1.0)
        ]),
        startPoint: .top,
        endPoint: .bottom
      )
      
      content()
        .padding()
    }
    .ignoresSafeArea()

     

     

    어느 정도 봐줄 만하게 완성된 것 같습니다!

    이제 끝난 걸까요?

    🙅🏻 아니요 그렇지 않습니다.

     

    다른 미디어 재생 방해 방지하기

    한번 앱을 종료하고 음악 앱으로 음악 틀어보고 다시 우리 앱을 열어볼까요?

    그럼 듣고 있던 음악이 정지되는 걸 확인할 수 있을 거예요! 왜 그럴까요? 🤔

     

    SwiftUI의 VideoPlayer를 사용할 때 오디오 세션을 명시적으로 설정하지 않으면, 기본적으로 .soloAmbient나 .playback을 적용하게 됩니다. 얘네들은 “내 앱의 오디오를 우선 재생” 하도록 동작하기 때문에 저희가 음악 앱에서 재생했던 음악이 정지되는 것이죠, 아래와 같이 해결해 보아요!

    func configureAudioSessionForBackgroundVideo() {
      do {
        // .ambient: 무음일 땐 벨소리에 따르고, 재생 중인 다른 앱 음악을 중지시키지 않음
        // mixWithOthers: 다른 백그라운드 오디오와 섞어 재생 허용
        try AVAudioSession.sharedInstance().setCategory(
          .ambient,
          mode: .default,
          options: [.mixWithOthers]
        )
        try AVAudioSession.sharedInstance().setActive(true)
      } catch {
        print("🔊 오디오 세션 설정 오류:", error)
      }
    }

     

    메서드를 하나 선언하고 오디오 세션의 카테고리를 .ambient + .mixWithOthers로 설정하여

    우리 앱의 배경 영상은 무음으로 재생되면서도, 음악 앱, 유튜브 같은 다른 미디어의 재생은 전혀 방해하지 않도록 합니다.

    그리고 이 메서드는 동영상을 재생 시켰던 .onAppear 에서 호출해 주도록 합니다.

    .onAppear {
       configureAudioSessionForBackgroundVideo()
       loopingPlayer.player.play()
     }

     

    이제 마지막으로 하나만 더 손 봐주면 될 것 같습니다. 다음은 어떤 문제가 있을까요?

     

    앱 라이프사이클 기반 동영상 재생 제어

    이번에는 앱을 실행하고 잠시 다른 앱이나 홈화면에 들렀다가 돌아와 볼게요

    그러면 동영상이 멈춰있는 걸 볼 수 있어요, 동영상이 다시 재생되도록 고쳐보겠습니다!

     

    iOS에서는 앱이 백그라운드나 비활성 상태가 되면 별도의 백그라운드 처리를 하지 않은 AVPlayer는 재생을 멈추게 되는데요

    따라서 저희는 다시 포그라운드로 돌아왔을 때 play를 실행해줘야 합니다.

    struct VideoBackgroundView<Content: View>: View {
      @Environment(\.scenePhase) private var scenePhase
      ... // 생략
      
      VideoPlayer(player: loopingPlayer.player)
        .onChange(of: scenePhase) { newPhase in
          switch newPhase {
          case .active:
            // 앱이 포그라운드로 돌아올 때 재생 재개
            loopingPlayer.player.play()
          case .background, .inactive:
            // (선택) 백그라운드에선 일시정지 할 수도 있고, 그대로 두어도 됨
            break
          @unknown default:
            break
          }
        }
    }
    • @Environment(\.scenePhase)
      • 앱의 현재 상태(.active, .inactive, .background)를 실시간으로 감지합니다.
    • .onChange(of: scenePhase)
      • 상태가 바뀔 때마다 호출되어, 포그라운드 복귀 시 player.play()를 실행해 부드럽게 동영상을 재생합니다.
    • 백그라운드 대응
      • 필요하다면 .background나 .inactive 케이스에 player.pause()를 넣어 리소스 사용을 줄일 수도 있습니다.

    이렇게 하면 사용자가 홈 화면으로 나갔다 돌아오거나, 다른 앱에서 다시 우리 앱으로 전환해도 로그인 배경 비디오가 항상 자연스럽게 이어집니다. 👏🏻👏🏻👏🏻

     

    마치며

    사이드 프로젝트에 이 기능을 구현하기 전에 미리 검증을 한번 해보았습니다.

    전에도 구현해 본 경험이 있었는데 시간이 좀 지나 가물가물해서.., 이번 기회에 확실히 정리가 된 것 같습니다ㅎㅎ

    전체 코드는 아래에서 확인 가능합니다!

     

    playground/MyLoopingVideoBackground at main · rakoonwc8561/playground

    Contribute to rakoonwc8561/playground development by creating an account on GitHub.

    github.com

    참조

     

    iOS에서의 Audio Session

    Apple의 Audio Session Documentation 을 참고하여 따로 정리해본 오디오 세션 정리글입니다. Audio Session 동작에 대한 개괄적인 설명과 play, record 등을 위한 Audio Session Setting 방법을 다룹니다. 더 상세히 공

    wlaxhrl.tistory.com

     

     

    Create a looping background video player with SwiftUI

    Learn how to add looping background videos to your SwiftUI app with this easy and straightforward tutorial.

    swiftyui.com

     

    'SwiftUI' 카테고리의 다른 글

    SwiftUI - SF Symbols 다루기  (1) 2025.06.26
Designed by Tistory.