我是iOS应用商店上一个社交媒体应用的所有者,我的平台包括一个类似TikTok的服务,可以显示YouTube短片。这个服务的用户界面是相当bug(bug包括下面),并不能正常工作。苹果要求我立即解决这些问题。如何改进此服务的功能?我在网页抓取和WebViews方面的知识非常有限。
下面是我想解决的问题:屏幕 Flink 白色,然后黑色的视频加载,我想有一个加载图标以上的视频封面显示,直到视频准备播放。
单视图
import SwiftUI
import WebKit
import UIKit
struct SingleVideoView: View {
let link: String
@State private var viewIsShowing = false
@State private var isVideoPlaying = false
@EnvironmentObject var viewModel: VideoModel
var body: some View {
ZStack {
Color.black //so background always black
SmartReelView(link: link, isPlaying: $isVideoPlaying, isChangingTime: $isChangingTime, totalLength: $totalLength, currentTime: $currentTime, viewIsShowing: $viewIsShowing)
Button("", action: {}).disabled(true) //disable any interaction with youtube video
Color.gray.opacity(0.001)
.onTapGesture {
isVideoPlaying.toggle() //play pause video
}
}
.ignoresSafeArea()
.onDisappear { //stop playing when view leaves
isVideoPlaying = false
viewIsShowing = false
}
.onAppear { //try and play the video when view appears
if viewModel.selected == link {
viewIsShowing = true
isVideoPlaying = true
}
}
.onChange(of: viewModel.selected, perform: { _ in //when the current video link changes play or pause
if viewModel.selected == link {
viewIsShowing = true
isVideoPlaying = true
} else if viewModel.selected != link {
viewIsShowing = false
isVideoPlaying = false
}
})
}
}
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 userContentController = WKUserContentController()
userContentController.add(context.coordinator, name: "observe")
let webConfiguration = WKWebViewConfiguration()
webConfiguration.allowsInlineMediaPlayback = true
webConfiguration.userContentController = userContentController
let webview = WKWebView(frame: .zero, configuration: webConfiguration)
webview.navigationDelegate = context.coordinator
loadInitialContent(web: 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, WKScriptMessageHandler {
var parent: SmartReelView
init(_ parent: SmartReelView) {
self.parent = parent
}
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage){
}
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { //try and force play the video
if self.parent.viewIsShowing {
webView.evaluateJavaScript("clickReady()", completionHandler: nil)
//randomly try again incase took some time to load
Timer.scheduledTimer(withTimeInterval: 1.5, repeats: false) { _ in
webView.evaluateJavaScript("clickReadySec()", completionHandler: nil)
}
Timer.scheduledTimer(withTimeInterval: 3.0, repeats: false) { _ in
webView.evaluateJavaScript("clickReadySec()", completionHandler: nil)
}
}
}
}
private func loadInitialContent(web: 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;
var ready = 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) { //restart video on end
player.seekTo(0);
player.playVideo();
}
}
}
});
}
function clickReady() {
if (ready) {
player.playVideo();
}
}
function clickReadySec() {
if (ready) {
var videoState = player.getPlayerState();
if (videoState !== YT.PlayerState.PLAYING) {
player.playVideo();
}
}
}
function watchPlayingState() {
if (isPlaying && ready) {
player.playVideo();
} else {
player.pauseVideo();
}
}
</script>
"""
web.scrollView.isScrollEnabled = false
web.loadHTMLString(embedHTML, baseURL: nil)
}
}
标签视图包含单个视频
import SwiftUI
struct AllVideoView: View {
@EnvironmentObject var viewModel: VideoModel
var body: some View {
ZStack {
Color.black.edgesIgnoringSafeArea([.bottom, .top])
TabView(selection: $viewModel.selected){
ForEach(viewModel.VideosToShow){ video in
SingleVideoView(link: video).tag(video)
}
.rotationEffect(.init(degrees: -90))
.frame(width: widthOrHeight(width: true), height: widthOrHeight(width: false))
}
.frame(width: widthOrHeight(width: false), height: widthOrHeight(width: true))
.rotationEffect(.init(degrees: 90))
.tabViewStyle(PageTabViewStyle(indexDisplayMode: .never))
}
}
}
class VideoModel: ObservableObject {
@Published var selected = ""
//Data to test
@Published var VideosToShow: [String] = ["iYebqpQ_EBA", "ZVAo11HS2lc", "j7f704_t6-U", "sZVW6dJk7_I", "8v7Tx2re5Rs", "xn90M86iUGw", "XTg3S-Ncr3I", "gJodMlgxyuY", "4Crbau26xdM", "wjSrqX_InUc", "4vaoZ0k7WC4", "wm-i3C2xi6E", "LOdHJKf8SaY", "T8SDP6rm-0k", "uIb5nrsWvxQ", "Qpbr5H34E_I", "MumEnIr_2vU", "6sTW7Q2uvo0", "ErnrcsM1CIk", "EuTUcRjtLbI", "xrovQjCSgtM", "DdlFT6ZPIR0", "HcpuMXsgMfI", "Xxk4mdojVTQ", "NJue1Wh4GZg", "xECgbg3SKg4", "pmqznJ1Pr64", "KKYplXrrOW0", "YuaeouCu8z4", "--Ay4om9zdQ", "950tFcmRtMc", "k56Ry6FLuYU", "QDpYHqDPYe0", "8JiB2ywr59c", "vAXR73OYU7c", "4tdS35_jUNc", "KYZeNx451Eo", "iEQQpdNbjdo", "KqwWLwKXUSA", "IDAmXhMj6Lo", "kd2g1xBs90g", "LFOIqZVv1Rc", "zCIeG6ht7jQ", "4co3RUJeebw", "Fb8YYJdgnmE", "ID1tfz4Hd1Q", "qSWzWYPOENY", "U-uIZLzgvfI", "R9r22u2gs_c", "3K0M69Npb1I", "DiHU8BJJ6jw", "4ZVsp4EkMb0", "SpHf4eBDjCk", "Rfudjg0jkvo", "EIFy41gYeVQ", "buQhVDlgbRg", "Rlz0vCw3bYs", "5Tv5xBn1AoI", "7Tv4RjbBSe8", "yhaFe_RzM14", "FDeMoHpyjJE", "h-RmBQ-GYY0", "e0rh5hgJJM0", "hjA4X_crpks", "wAVQ4u4V6vY", "NawgIgsyLTA", "OFtoXhXNEa0", "kPC9JW9c2Ro", "uhsy5vzmgls", "_Lm9ahDneqA", "u6Y6rbblFek", "U-C_XW8sZl4", "RHqGlX3lIEo", "yl0AOc45tQg", "C2V8K5yMBdo", "j8ROZ7-aIE8", "FODrGNH1SjA", "JYpWudy4jaQ", "QvqyeUaExNo", "WDYG2DwfJDk", "zz2Fxkf9u8M", "8bFp8CNyEus", "oLdPRJ8qI84", "dowoyaGmTgE", "GtLbiDqrs-0", "MyoH8blacsE", "7LS879KOdK0", "D4Qpeliwk4w", "nmMI3BM1ea8", "VGWgFary4Es", "9qqfxtOiTJ0", "Vt61xtJilHA", "URHFPuSNvt8", "pzX-PSaIV-c", "QaK8XcvvOEE", "pgk3d2VNbbo", "vKtZ5yqKY9A", "lVl-1ZTN8UY", "iKX79_6ZVpQ", "6FNjp772iyg", "ppaktgRUw4M", "qaf7uO5Hs0E", "3PfBSRfDa-8", "OGGLLoSyBCw", "_GXpYnTjjCY", "TDACktZbs9s", "qRzix60msYs", "UUBKHJB5xm0", "tuFsPpkyjes", "woSAr1TY-rs", "3mCkBvpIuYc", "O2YoEcY51SY", "af3dkkGqIH4", "Clo8frpXQRk", "JrCaYLvpTTo", "ag8MAvd6t94", "VOJVNyUtjUI", "ye5rxzDS_Bo", "SAZtnEuCnj0", "gO7bDxe-tWI", "iINSdcdhjBQ", "ysNIsT-1emU", "y97KR2XjMHA", "PkEeowOtqgE", "Z5NsgZQjjoc", "CeoIIi6oJiU", "-yMmZT0XyBg", "B7Uz78okItk", "IZieI2VnpXM", "Vsb4yho_hTk", "Lu4Ie_lJsKg", "NBgOY-kHWVQ", "YnFI3IS8duA", "3fIWRHQYRb8", "prLyItjvb0w", "-gSbmK1eN_Q", "3Zkfq9JYaf4", "2CvCIFPdgMo", "hUsDOf8s61o", "ha2_x5eJ1OY", "sqJ5fEqmh1w", "Q8nUj8EH8qM", "TXLA-qtWlj4", "uFu8YxtoZaU", "JTQ-zkZ9WQQ", "bYuLW07tGmI", "bCPuLceW-yE", "ot0d3ZgPrN8", "m0myvDUD5Js", "HJQ4TiCFvUY", "TS6xiAV1qHQ", "-38yIw4IhAY", "x-n0FwD8HFs", "uwbZ4UWbuhQ", "EhuAMPjuEks", "Nk5HgYdTRdg", "ztLTqCjrXrc", "q0VUZ2hAIuw", "S_vOu6oqCDE", "sh0hHSq5Ogc", "oyswMIUFr4w", "zoSjLJqJPDQ", "BLTnIBQJlFc", "pUYDN-stzck", "g1ucPFztCrc", "u4Pav23IzS4", "7eaAN0X_evA", "GIRrfccZvLI", "fVP7IansDAI", "41QxPIfWFN8", "7kFhNLEI7yY", "NH2OI2Q0q14", "as2WgzniPVo", "rjiteE2rKFs", "yz2TumE1ws0", "d-XwLHM8sFE", "uN4h4OmtfEk", "u2-7hhlMb6w", "-JaSHN3aIyY", "pZTjhMxas_s", "zN8fSWxHxVc", "D_uXLM3VJYs", "nk0hOKS_zfA", "EaImidqT0xQ", "7PlSHvyqh70", "epbrJg-LTOE", "9gE7MOnmHfs", "F5m2Qg2cst4", "kikGvjKFN8Q", "Kbvk8T6QXBE", "k-IXHTPWV8M", "uGiclr_zW2M", "rGIL7Yal2WA", "7-_LCq2UTA8", "VRvBcnQ7Yyo", "5R9zmI5Mbrc", "HaUm23dTR5g", "otUIqpojXqA", "5Jq0IlNzvM4", "21EofWX0uuo", "PacSMXd1FIc", "sEWZ7TPXXDM", "zbElpS_OiGc", "0jSmuCKvj7U", "uoiQChfa-c8", "jv1IKf_NQ50", "j4Yl886jRSU", "E6JSDl70zEY", "-fyklDM48ig", "7amjvAwvXes", "agwGF1FeWOQ", "tTft0J__BrA", "n_rf3Jjclrw", "gJDKloPhiMk", "vK3MG9yhn4o", "-UGDDKQE2FI", "a5RNe4vITws", "gf1Bvx6oVKw", "DCwSyZGQbEQ", "7G6ipKZfeLM", "UftB2CCWyHs", "Uf2FAF1rSJQ", "YC9hLAinWig", "g-5wvzYR8MY", "UsUoqDLJOnA", "BtkwBEts0Jg", "cKDbx1Udj_Q", "ax7kwHO4fvc", "xMYdyz5JteE", "VZjj1D3DGrw", "fW7dfJ7_PkE", "sEFUk8sb6vk", "4sTxfxxEZW0", "l2k1vzNno3A", "ZYe0k_dk5D4", "IBMshfQ-jCo", "pYJjIzj-t4c", "vK5riR3lbIM", "TiBAmEW03mM", "pLBPbJcJ_F4", "PQLJhhpXIDs", "ZRSjBQ_qIhE", "V9zTAsfN9Bw", "4dqiSzf-y-I", "L16S2yCRo10", "HctLr25JFx4", "l5WyvXvYRzc", "sbYofIDT4Y0", "3WEPgIitq6Y", "GBjoifXMkEc", "D7cdGKevHYk", "wU0oqTncwSc", "uCIYARYeaF8", "fzChmE4wzME", "dNmFhyMzckg", "bbvQBEtAJrU", "GMC57RwqPDg", "CRueXlxg24s", "2lpcP8LyZrs", "Y8n5LmBz8Vk", "WkfEO9aoNuc", "Vi1jtwUc0kU", "irVXM6JDUQg", "o1XPcaokECE", "5bMPf_E1-Vw", "RYMXdvLOIls", "Ksb5gWSJPQ8", "hEP5XqEu7P4", "rf5qRQugqgY", "ndzHTk1tJtk", "0MMQSjoLIVc", "_-YsXJ72eeo", "zBP4TsOWbH0", "LecyAUI93c0", "C0OA0WcN0BY", "DAKxPMg6oPw", "UeSum0pCm_U", "L8GxXFW_y6Q", "Nm0WQ26euoc", "dwEIVP6kKLY", "kd2g1xBs90g", "rlYBLoYUiPQ", "2BqPHZWT86Q", "T9ZuTZRxgag", "epRrztb3VHc", "_V0oZOOHvOw", "rLlbIz-UsW0", "U_eoUo7tEAE", "LHpUQdQDtyQ", "GGbzmEoTPWA", "nxmKRgO_a44", "iuB4OwPTa7M", "MI8nv441Q78", "pkETcBX15Ns", "A9xx98UqDd8", "2LTR5SstIhA", "OdAPkzyetZY", "B_SMTxXIAT4", "516ESehbjcw", "GqCBB_9drtg", "1mwybHblSoU", "AwPcbIGG9DQ", "9C7Rjy3GgqU", "AquwGe-LRNI", "l3QDF8KfMlQ", "oxWowAGygq4", "qkkptG9UXg4", "fudFlhI43JQ", "6IoJ3iaT_2Q", "hTNADe1fGIs", "L6z3TZaTKl8", "dlXqMvzye24", "8iXzfysURSc", "ukBKppVJYLo", "sPkkWBckt44", "ecB8x_pP-8M", "xjMAlSs5Ayg", "tna_mHHtChw", "https://youtube.com/shorts/xyhY92K0RDI?si=DZAYC6285acLMJud", "ASEZtFsbjVE", "HFpHOoPa3yo", "DJvQzI9bae4", "-uUP5YUBJ-0", "0sx9QHubgqc", "7OeKeFisbgg", "6Kp30WYyF5M", "K709BAWgqs4", "H5vLWyWMBgA", "iDgetgvbk7E", "xwakzmjkNe0", "ddjQMQyOqJU", "IThk6vqrVs8", "0cKQsIUOXx4", "CkCpplHbDzs", "dOQsXA30RWU", "01P0hQQgq-c", "Mt4hOuodhvo", "MSG_J4nZoA0", "TndspK834ms", "ivLRbslmuzE", "33fsguWtdXo", "ulQHG4CtIcc", "Jp9zpUtafxA", "HWy4w8QWrJc", "uvRWJr-WUTs", "MhlOPtLxBM0", "qvBaaK8voyw", "EW7FTWV1H0c", "X220GWe_lmQ", "3oy2hklCdZM", "Yzs1NecYPPw", "PTWTkfzQI5o", "0zEyTOb7Tr0", "KbzlC01D83c", "FQNP-gbyjZk", "JMWuo1XylM4", "t9_Nz91jxmY", "iYtdYc_WLM0", "Ak1lmnMzf-Y", "9sPRNYitLLY", "KpjTXmm7pGE", "K9Ym44tJbQE", "lg_VwVnpqIE", "kv9PCqLj3vo", "aSd1IQNm4_s", "gjCz0BBGtQE", "2Rl9TivKS34", "HArdqJHadtE", "Xs0mbQUy9qg", "5ksMWUPhG3M", "JnDR5lQMYb4", "Iwck0KLLTAw", "-Z9d9-4I21s", "f6W8DsmIC5k", "ibRhoFfbgZI", "KoW3Qr_Uttk", "HFgadwtV2U0", "tPgSLUBxwlo", "hJtrIFhHoiE", "q0_53pX1V40", "sQ64plrMdjY", "UIeOjGR1ols", "y2PAOVxHyzw", "ENfQ5Q9hzd4", "S81byfkCbzc", "TBNfnC8lj2c", "2X-pqstQ9Mg", "BlEWAH17wus", "DLKn3np-QXA", "M1jGiFhPyvc", "uaxvB2YcccA", "aJIA0FM-ko4", "1CZgcxiSS4w", "HjFoOMpMQ6c", "IpLvmKbWDEQ", "220CEbaowoA", "CK2cCr4At1o", "cjA2TVjx6Xg", "wY8IjPPWqbg", "5o7Fe1P078s", "VwQtD-ZVbzs", "CUPvgecdsck"]
}
1条答案
按热度按时间iszxjhcz1#
视频嵌入在WKWebView中,相关的UI组件使用SwiftUI构建。但屏幕 Flink 白色,然后黑色的视频加载,和所需的结果是有视频封面显示与加载图标,直到视频准备播放。
为此,您可以考虑使用视频封面和加载指示器:
View
,它覆盖在您的SingleVideoView
之上。该覆盖视图可以包含加载指示符和用于显示视频封面的图像视图。ZStack
将叠加视图堆叠在SmartReelView
之上。isPlaying
状态或引入新状态来专门处理加载状态来实现。在
SmartReelView
中的JavaScript代码中,您可以考虑引入一种机制,在视频准备好播放时通知Swift代码。这可以使用YouTube Iframe Player API的onReady
事件来完成。使用
WKScriptMessageHandler
协议接收来自JavaScript代码的消息,并相应地更新SwiftUI视图。这将创建一个新的叠加视图来处理加载指示器和视频封面,沿着调整JavaScript和Swift交互,以便在视频准备播放时隐藏叠加。确保视频封面图像可用并在
ImageView
中正确显示至关重要。