你好,我正在用YouTube短片制作TikTok的克隆.我在垂直选项卡视图中呈现视频,允许用户滚动视频列表.由于这些视频都在网络上,所以我使用Webview来渲染它们.当用户滚动选项卡视图时,将为新视频创建新的Web视图实例.当用户向后滚动时,他们可以在相同的持续时间内看到之前的视频(已经渲染).这意味着当用户滑动离开它们时,Web视图不会被 destruct .滚动几分钟后,设备会明显变暖,因为许多Web视图实例需要大量资源.我如何才能摧毁这些网络浏览时,用户是2个以上的视频.

import SwiftUI
import WebKit
import UIKit

struct AllVideoView: View {
    @State private var selected = ""


    @State private var arr = ["-q6-DxWZnlQ", "Bp3iu47RRJQ", "lXJdgDjw1Ks", "It3ecCpuzgc", "7WNJjr8QM1w", "z2t0W8YSzZo", "w8RBGoH_6BM", "DJNAUBoxW5g", "Gv0X34FZ_8M", "EUTsaD1JFZE",
    "yM9iLvOL2v4", "lnqhfn2n-Jo", "qkUpWwUAFPA", "Uz21KTMGwAI", "682rP7VrMUI",
    "4AOcYT6tnsE", "DEz9ngMqVT0", "VOY2MviU5ig", "F8DvoxgP77M", "LGiRWOawMiw",
    "Ub8j6l35VEM", "0xEQbJxR2hw", "SVow553Lluc", "0cPTM7v0vlw", "G12vO9ziK0k"]


    var body: some View {
        ZStack {
            Color.black.edgesIgnoringSafeArea([.bottom, .top])
            TabView(selection: $selected){
                ForEach(arr, id: \.self){ id in
                    SingleVideoView(link: id).tag(id)
                }
                .rotationEffect(.init(degrees: -90))
                .frame(width: widthOrHeight(width: true), height: widthOrHeight(width: false))
            }
            .offset(x: -10.5) 
            .frame(width: widthOrHeight(width: false), height: widthOrHeight(width: true))
            .rotationEffect(.init(degrees: 90))
            .tabViewStyle(PageTabViewStyle(indexDisplayMode: .never))
        }
    }
}

struct SingleVideoView: View {
    let link: String
    @State private var viewIsShowing = false
    @State private var isVideoPlaying = false
    var body: some View {
        ZStack {
            Color.black
            
            SmartReelView(link: link, isPlaying: $isVideoPlaying, viewIsShowing: $viewIsShowing)

            Button("", action: {}).disabled(true)
            
            Color.gray.opacity(0.001)
                .onTapGesture {
                    isVideoPlaying.toggle()
                }
            
        }
        .ignoresSafeArea()
        .onDisappear {
            isVideoPlaying = false
            viewIsShowing = false
        }
        .onAppear {
            viewIsShowing = true
            isVideoPlaying = true
        }
    }
}

struct SmartReelView: UIViewRepresentable {
    let link: String
    @Binding var isPlaying: Bool
    @Binding var viewIsShowing: Bool
    
    func makeCoordinator() -> Coordinator {
        Coordinator(self)
    }
    
    func makeUIView(context: Context) -> WKWebView {
        let webConfiguration = WKWebViewConfiguration()
        webConfiguration.allowsInlineMediaPlayback = true
        let webView = WKWebView(frame: .zero, configuration: webConfiguration)
        webView.navigationDelegate = context.coordinator

        let userContentController = WKUserContentController()
        
        webView.configuration.userContentController = userContentController

        loadInitialContent(in: webView)
        
        return webView
    }

    func updateUIView(_ uiView: WKWebView, context: Context) {
        var jsString = """
                isPlaying = \((isPlaying) ? "true" : "false");
                watchPlayingState();
            """
        uiView.evaluateJavaScript(jsString, completionHandler: nil)
    }
    
    class Coordinator: NSObject, WKNavigationDelegate {
        var parent: SmartReelView

        init(_ parent: SmartReelView) {
            self.parent = parent
        }
        
        func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
            if self.parent.viewIsShowing {
                webView.evaluateJavaScript("clickReady()", completionHandler: nil)
            }
        }
    }
    
    private func loadInitialContent(in webView: WKWebView) {
        let embedHTML = """
        <style>
            body {
                margin: 0;
                background-color: black;
            }
            .iframe-container iframe {
                top: 0;
                left: 0;
                width: 100%;
                height: 100%;
            }
        </style>
        <div class="iframe-container">
            <div id="player"></div>
        </div>
        <script>
            var tag = document.createElement('script');
            tag.src = "https://www.youtube.com/iframe_api";
            var firstScriptTag = document.getElementsByTagName('script')[0];
            firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);

            var player;
            var isPlaying = false;
            function onYouTubeIframeAPIReady() {
                player = new YT.Player('player', {
                    width: '100%',
                    videoId: '\(link)',
                    playerVars: { 'playsinline': 1, 'controls': 0},
                    events: {
                        'onStateChange': function(event) {
                            if (event.data === YT.PlayerState.ENDED) {
                                player.seekTo(0);
                                player.playVideo();
                            }
                        }
                    }
                });
            }
        
            function clickReady() {
                player.playVideo();
            }
            
            function watchPlayingState() {
                if (isPlaying) {
                    player.playVideo();
                } else {
                    player.pauseVideo();
                }
            }
        
        </script>
        """
        
        webView.scrollView.isScrollEnabled = false
        webView.loadHTMLString(embedHTML, baseURL: nil)
    }
}

func widthOrHeight(width: Bool) -> CGFloat {
    let scenes = UIApplication.shared.connectedScenes
    let windowScene = scenes.first as? UIWindowScene
    let window = windowScene?.windows.first
    
    if width {
        return window?.screen.bounds.width ?? 0
    } else {
        return window?.screen.bounds.height ?? 0
    }
}

推荐答案

为了进行优化,您可以考虑一种视图回收机制("视图池"),以便在每次要显示新视频时重用Web视图实例,而不是创建新的实例.

但是,由于您特别询问的是当用户超过2个视频时如何销毁Web浏览量,您可以实现逻辑来手动释放这些Web浏览量并清除其内容.

要手动销毁WKWebView,您需要:

  • 将Web视图从其Superview(如果有)中删除.
  • 将其navigationDelegateUIDelegate设置为nil.
  • 对其调用stopLoading方法.
  • 将Web视图本身设置为nil(如果没有留下对Web视图的强引用,则通常由ARC处理).

首先,在SmartReelView中添加一个标志,以判断Web视图是否处于活动状态:

@Binding var isActive: Bool

更新updateUIViewmakeUIView方法以考虑活动状态:

func makeUIView(context: Context) -> WKWebView {
    let webView = WKWebView()
    // existing code
    if isActive {
        loadInitialContent(in: webView)
    }
    return webView
}

func updateUIView(_ uiView: WKWebView, context: Context) {
    if isActive {
        // existing code
    } else {
        destroyWebView(uiView)
    }
}

private func destroyWebView(_ webView: WKWebView) {
    webView.navigationDelegate = nil
    webView.stopLoading()
    webView.removeFromSuperview()
}

然后,在SingleVideoView中,引入基于用户滚动的距离来更新isActive绑定的逻辑.您可能需要传递一个索引,并计算该视频是否在当前活动视频的2个视频范围内:

struct SingleVideoView: View {
    // existing properties
    @Binding var activeIndex: Int // The index of the currently active (visible) video
    let index: Int  // The index of this particular video

    var body: some View {
        // existing code

        SmartReelView(link: link, isPlaying: $isVideoPlaying, viewIsShowing: $viewIsShowing, isActive: .constant(shouldActivate))
    }

    private var shouldActivate: Bool {
        return abs(activeIndex - index) <= 2
    }
}

AllVideoView中,保持当前活动视频索引的状态:

@State private var activeIndex = 0

将此索引传递给每个SingleVideoView:

ForEach(arr.indices, id: \.self) { index in
    SingleVideoView(link: arr[index], activeIndex: $activeIndex, index: index)
}

最后,只要TabView‘S的 Select 发生变化,就更新activeIndex.

这些更改应该会将内存中的Web浏览量限制在当前活动视频的2个视频范围内,这应该会缓解资源问题.


相反,我仍然会考虑WKWebView个实例池,其中包括维护可重用视图的集合,在需要时分发它们,并在它们不再使用时将它们返回到池中.

A simplified example (focusing on the WebView pool) would include first a WebViewPool Manager.
That manager will handle the logic for pooling:

class WebViewPool {
    private var pool: [WKWebView] = []
    
    func getWebView() -> WKWebView {
        if let webView = pool.first {
            pool.removeFirst()
            return webView
        } else {
            // Create a new web view, configure it as needed
            let webView = WKWebView()
            return webView
        }
    }
    
    func returnWebView(_ webView: WKWebView) {
        // Optionally clear the webView content
        webView.loadHTMLString("", baseURL: nil)
        
        pool.append(webView)
    }
}

然后,您可以在需要Web视图的SwiftUI视图中创建此管理器的实例,例如在AllVideoView中.

struct AllVideoView: View {
    @State private var webViewPool = WebViewPool()
    //... existing code
}

SingleVideoViewSmartReelView中,当视图出现时,您可以使用泳池获取Web视图,当视图消失时,您可以将其返回.

struct SingleVideoView: View {
    let link: String
    @Binding var webViewPool: WebViewPool
    //... existing code
    
    var body: some View {
        // existing code
        SmartReelView(link: link, webViewPool: $webViewPool)
            .onAppear {
                // Check out a WebView when appearing
            }
            .onDisappear {
                // Return WebView when disappearing
            }
    }
}

struct SmartReelView: UIViewRepresentable {
    let link: String
    @Binding var webViewPool: WebViewPool
    var webView: WKWebView?
    
    func makeUIView(context: Context) -> WKWebView {
        webView = webViewPool.getWebView()
        // existing code
        return webView!
    }
    
    func updateUIView(_ uiView: WKWebView, context: Context) {
        // existing code
    }
    
    func dismantleUIView(_ uiView: WKWebView, coordinator: Coordinator) {
        webViewPool.returnWebView(uiView)
    }
}

这并不包括所有的边缘情况.管理Web视图的生命周期(检出和返回)需要更加细致入微.根据您的需要,您不仅可以在显示Web视图时检出该视图,还可以在需要加载新视频时检出该视图.

尽管如此,这个 idea 仍然存在:通过以这种方式重用Web视图,您将最大限度地减少创建和销毁Web视图实例的开销,这应该会提高应用程序的性能.

Html相关问答推荐

将文本区域标签的内容着色为SON(带有 colored颜色 )

如何只混合部分的背景而不是内容<>

在Apps Script中连接CSS与HTML

使用网格时图像溢出容器高度

如何删除在小屏幕上单击按钮时出现的蓝色方块?

如何最大限度地减少Cookie同意代码对性能的影响?

创建带有弯曲边框的水平时间线

水平卷轴的内容保持堆叠

如何保持嵌套进度条的背景色?

如何将文本环绕心形(而不仅仅是圆形)?

文本css后面未显示背景 colored颜色

是否可以只使用css而不使用像&;nbsp;这样的HTML符号来实现断字而不换行空格?

浮动Div在CSS中未按预期工作

视频重新加载而不是在 Swift 中暂停 WKWebView

是否有语义 HTML 可以澄清含义?

在DIV上应用梯度模糊未如预期工作

调整窗口大小时文本重叠导航栏

W3Schools 幻灯片相同高度

当我希望它只横向滚动时,可滚动菜单也会上下移动

隐藏在标题后面的下拉菜单