SwiftUIতে টিকটক স্টাইল ইন্টারফেস তৈরি

টিকটক স্টাইল ইন্টারফেস কিভাবে তৈরি করা যায়, তা দেখব। আমরা ফোকাস করব ভিডিও প্লেয়ারটায়। আর একটার পর আরেকটা ভিডিও স্ক্রল কিভাবে করা যায়, এসব।

উদাহরণে আমরা লোকাল ভিডিও থেকে ভিডিও প্লে করব। লোকাল ভিডিও এর পরিবর্তে অনলাইন ভিডিও URL দিলেও একই রকম কাজ করবে।

এর আগে একটা লেখা লিখেছি SwifUI অ্যাপে ফুল উইডথ ভিডিও ব্যাকগ্রাউন্ড নিয়ে। ঐখানে দেখেছি কিভাবে ভিডিও প্লে করা যায়। কঠিন কিছু না। ভিডিও প্লে করার জন্য অ্যাপলের নিজস্ব ফ্রেমওয়ার্ক AVkit রয়েছে। তা ইম্পোর্ট করে নিলেই হবে।

import SwiftUI
import AVKit
struct ContentView: View {
    var body: some View {
        if let url = Bundle.main.url(forResource: "videoName", withExtension: "mp4") {
            VideoPlayer(player: AVPlayer(url: url))
        } else {
            Text("Video Not Found")
        }
    }
}

এখানে VideoPlayer এর ভেতর AVPlayer পাস করেছি। আর AVPlayer এ ভিডিও URL পাস করেছি। যদি প্রজেক্টে নির্দিষ্ট নামে ভিডিও রাখি, তাহলে দেখব ভিডিও প্লে করতে পারছি।

উপরের উদাহরণে আমরা লোকাল ভিডিও প্লে করেছি। চাইলে রিমোট ভিডিও প্লে করতে পারি। একই রকমঃ

import SwiftUI
import AVKit
struct ContentView: View {
    var body: some View {
        
        if let url = URL(string: "https://download.samplelib.com/mp4/sample-30s.mp4") {
            VideoPlayer(player: AVPlayer(url: url))
        } else {
            Text("Video Not Found")
        }
    }
}

দুই ভাবেই দেখেছি ভিডিও প্লে করতে পেরেছি। টিকটকে আমরা দেখি ভিডিও অটো প্লে হয়। তাও করতে পারব। কোডটা এভাবে লিখতে হবেঃ

import SwiftUI
import AVKit
struct ContentView: View {
    @State private var player: AVPlayer?
    var body: some View {
        if let url = URL(string: "https://download.samplelib.com/mp4/sample-30s.mp4") {
            VideoPlayer(player: player)
                .onAppear {
                    player = AVPlayer(url: url)
                    player?.play()
                }
        } else {
            Text("Video Not Found")
        }
    }
}

মানে প্লেয়ার ভিউ লোড হওয়ার সাথে সাথে অটোমেটিক্যালি ভিডিও প্লে হওয়া শুরু হবে।

এবার টিকটকের ইন্টারফেস দেখি। যেখানে ভিডিও প্লেয়ার ভিউ স্ক্রল করা যায়। ভিডিও অটোমেটিক প্লে হয়। কিছু বাটন থাকে। এই তো। যা এভাবে লিখতে পারিঃ

import SwiftUI
import AVKit

struct VideoPlayerView: View {
    let videoName: String
    @State private var player: AVPlayer?
    @State private var isVisible: Bool = false
    @State private var isLiked: Bool = false
    
    var body: some View {
        GeometryReader { geo in
            ZStack {
                if let url = Bundle.main.url(forResource: videoName, withExtension: "mp4") {
                    VideoPlayer(player: player)
                        .onAppear {
                            setupPlayer(url: url)
                        }
                        .onDisappear {
                            stopPlayer()
                        }
                        .edgesIgnoringSafeArea(.all)
                } else {
                    Text("Video Not Found")
                        .foregroundColor(.white)
                        .background(Color.black.edgesIgnoringSafeArea(.all))
                }
                
                // Overlay UI
                VStack {
                    Spacer()
                    HStack {
                        Spacer()
                        VStack(spacing: 20) {
                            // Like Button
                            Button(action: { isLiked.toggle() }) {
                                VStack {
                                    Image(systemName: isLiked ? "heart.fill" : "heart")
                                        .font(.system(size: 40))
                                        .foregroundColor(isLiked ? .red : .white)
                                    Text("662.7K")
                                        .foregroundColor(.white)
                                        .font(.caption)
                                }
                            }
                            // Comment Button
                            Button(action: { print("Comment tapped") }) {
                                VStack {
                                    Image(systemName: "message.fill")
                                        .font(.system(size: 40))
                                        .foregroundColor(.white)
                                    Text("22.1K")
                                        .foregroundColor(.white)
                                        .font(.caption)
                                }
                            }
                            // Share Button
                            Button(action: { print("Share tapped") }) {
                                VStack {
                                    Image(systemName: "arrowshape.turn.up.right.fill")
                                        .font(.system(size: 40))
                                        .foregroundColor(.white)
                                    Text("64K")
                                        .foregroundColor(.white)
                                        .font(.caption)
                                }
                            }
                        }
                        .padding(.bottom, 100)
                        .padding(.trailing, 20)
                    }
                }
            }
            .onChange(of: geo.frame(in: .global).minY) { old, newValue in
                handleVisibility(newValue)
            }
        }
        .frame(width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height)
        .edgesIgnoringSafeArea(.all)
    }
    
    // MARK: - Helper Methods
    private func setupPlayer(url: URL) {
        player = AVPlayer(url: url)
        player?.play()
        player?.isMuted = false
    }
    
    private func stopPlayer() {
        player?.pause()
        player?.replaceCurrentItem(with: nil)
        player = nil
    }
    
    private func handleVisibility(_ yPosition: CGFloat) {
        // Detect if the video is visible
        if yPosition > -UIScreen.main.bounds.height && yPosition < UIScreen.main.bounds.height {
            if !isVisible {
                player?.play()
                isVisible = true
            }
        } else {
            if isVisible {
                stopPlayer()
                isVisible = false
            }
        }
    }
}

#Preview{
    VideoPlayerView(videoName: "video1")
}

এটা হচ্ছে প্লেয়ার ভিউ। যেখানে একটা ভিডিও প্লে হবে। এবং ভিডিও এর উপর থাকা বাটন গুলো যোগ করেছি। কোড কিন্তু কমপ্লেক্স কিছু না।

এখানে GeometryReader ব্যবহার করেছি ভিডিও স্ক্রল করে নেক্সট ভিডিওতে গিয়েছে কিনা, তা ডিটেক্ট করার জন্য। যখন অন্য আরেকটা ভিডিওতে চলে যাবে, তখন আগের ভিডিও প্লে বন্ধ করে দিবে।

এবার লিস্ট অনুযায়ী স্ক্রল ভিউ তৈরি করবঃ

import SwiftUI

struct HomeView: View {
    let videos : [String]
    var body: some View {
        ScrollView(showsIndicators: false) {
            LazyVStack(spacing: 0) {
                ForEach(videos, id: \.self) { video in
                    VideoPlayerView(videoName: video)
                }
            }
            .scrollTargetLayout()
        }
 
        .scrollTargetBehavior(.paging)
        .edgesIgnoringSafeArea(.all)
    }
}

স্ক্রল ভিউ স্মুথ হয়। মানে একটা ভিডিও যদি স্ক্রল করে উপরের দিকে উঠাই, তাহলে অর্ধেক উপরে উঠবে। কিন্তু টিকটকে আমরা দেখব একটা ভিডিও অল্প একটু স্ক্রল করলে পুরোটাই স্ক্রল হবে। এটাকে বলে পেইজিং স্ক্রল। যা করেছি .scrollTargetBehavior(.paging) মডিফায়ার ব্যবহার করে। এই তো।

টিকটকের হোম মূল ভিউ একটা ট্যাব ভিউর মত। যা এভাবে লিখতে পারিঃ

import SwiftUI

struct ContentView: View {
    // Ensure videos are in the app bundle
    let videos =     ["video1", "video2", "video3"]
    var body: some View {
        
        TabView{
            
            HomeView(videos: videos)
                .tabItem {
                    Label("Home", systemImage: "house.fill")
                }
            Text("Friends")
                .tabItem {
                    Label("Friends", systemImage: "person.2.fill")
                }
            
            Text("Add video")
                .tabItem {
                    Label("Add", systemImage: "plus")
                }
            
            Text("Inbox")
                .tabItem {
                    Label("Inbox", systemImage: "message.fill")
                }
            Text("Profile")
                .tabItem {
                    Label("Profile", systemImage: "person.fill")
                }
        }
    }
}

আউটপুট পাবো এমনঃ

কোড গুলো গিটহাবে আপলোড করে দিয়েছি। আমি লোকাল ভিডিও থেকে প্লে করেছি। একই ভাবে চাইলে রিমোট ভিডিও প্লে করা যাবে। iOS রিলেটেড অন্যান্য লেখা গুলো এখানে পাওয়া যাবে

যখন রিয়েল প্রজেক্ট করবেন, তখন হয়তো API ব্যবহার করতে হবে। মূল কনসেফট প্রায় একই। SwiftUI নিয়ে ইংরেজিতে বেশি কিছু লেখা লিখেছি। বাংলাতে কমই লিখেছি। আস্তে আস্তে লেখার চেষ্টা করছি। ঐখানে বেশ কিছু টপিক্স নিয়ে লিখেছি। আশা করি কাজে আসবে।

Leave a Reply