Swift + Swift UI — исправление ошибок в клоне TikTok в приложении IOS «Hustles» [закрыто] ⇐ IOS
Swift + Swift UI — исправление ошибок в клоне TikTok в приложении IOS «Hustles» [закрыто]
Здравствуйте, я владелец Hustles (приложения для социальных сетей в магазине приложений iOS), и моя платформа включает в себя сервис, похожий на TikTok, который показывает короткометражки YouTube. Пользовательский интерфейс этого сервиса довольно глючный (ошибки указаны ниже) и не работает должным образом. Apple попросила меня немедленно исправить эти проблемы. Я был бы признателен всем за помощь в улучшении функциональности этого сервиса, поскольку мои знания в области веб-скрапинга и WebViews весьма ограничены.
Если вам поможет функциональность и ошибки, о которых я говорю, вы можете загрузить приложение здесь (https://apps.apple.com/us/app/hustle/id6452946210). Канал, похожий на TikTok, доступен на вкладке «Обзор», нажав на заголовок «Обнаружение».
Вот проблема, которую я хочу исправить:
[*]При загрузке видео экран мигает белым, а затем черным. Я хочу, чтобы обложка видео отображалась со значком загрузки вверху, пока видео не будет готово к воспроизведению.
Единый просмотр
импортировать SwiftUI импортировать WebKit импортировать UIKit структура SingleVideoView: Посмотреть { пусть ссылка: Строка @State Private var viewIsShowing = false @State Private var isVideoPlaying = false @EnvironmentObject var viewModel: VideoModel var body: some View { ZStack { Color.black //так что фон всегда черный SmartReelView (ссылка: ссылка, isPlaying: $isVideoPlaying, isChangingTime: $isChangingTime, totalLength: $totalLength, currentTime: $currentTime, viewIsShowing: $viewIsShowing) Button("", action: {}).disabled(true) //отключаем любое взаимодействие с видео на YouTube Цвет.серый.непрозрачность(0,001) .onTapGesture { isVideoPlaying.toggle() //воспроизведение видео на паузе } } .ignoresSafeArea() .onDisappear { //остановить воспроизведение, когда представление исчезнет isVideoPlaying = ложь viewIsShowing = ложь } .onAppear { //попробуем воспроизвести видео, когда появится представление если viewModel.selected == ссылка { viewIsShowing = правда isVideoPlaying = правда } } .onChange(of: viewModel.selected, выполните: { _ in //когда текущая ссылка на видео изменится, воспроизведите или приостановите если viewModel.selected == ссылка { viewIsShowing = правда isVideoPlaying = правда } иначе, если viewModel.selected != ссылка { viewIsShowing = ложь isVideoPlaying = ложь } }) } } структура SmartReelView: UIViewRepresentable { пусть ссылка: Строка @Binding var isPlaying: Bool @Binding var viewIsShowing: Bool func makeCoordinator() -> Координатор { Координатор (сам) } func makeUIView (контекст: Контекст) -> WKWebView { пусть userContentController = WKUserContentController() userContentController.add(context.coordinator, name: «наблюдать») пусть webConfiguration = WKWebViewConfiguration() webConfiguration.allowsInlineMediaPlayback = true webConfiguration.userContentController = userContentController пусть webview = WKWebView (кадр: .ноль, конфигурация: webConfiguration) webview.navigationDelegate = context.coordinator loadInitialContent (веб: веб-просмотр) вернуть веб-просмотр } func updateUIView(_ uiView: WKWebView, context: Context) { вар jsString = """ isPlaying = \((isPlaying) ? «истина» : "ЛОЖЬ"); смотретьPlayingState(); """ uiView.evaluateJavaScript (jsString, завершениеHandler: ноль) } Координатор класса: NSObject, WKNavigationDelegate, WKScriptMessageHandler { родительский вар: SmartReelView init (_ родитель: SmartReelView) { self.parent = родительский } func userContentController (_ userContentController: WKUserContentController, DidReceive сообщение: WKScriptMessage) { } func webView(_ webView: WKWebView, DidFinish Navigation: WKNavigation!) { //попробуем принудительно воспроизвести видео если self.parent.viewIsShowing { webView.evaluateJavaScript("clickReady()",completeHandler: ноль) //случайно повторим попытку, если загрузка заняла некоторое время Timer.scheduledTimer(withTimeInterval: 1,5, повторы: false) { _ in webView.evaluateJavaScript("clickReadySec()",completeHandler: ноль) } Timer.scheduledTimer(withTimeInterval: 3.0, повторы: false) { _ in webView.evaluateJavaScript("clickReadySec()",completeHandler: ноль) } } } } частная функция loadInitialContent (веб: WKWebView) { пусть встраиваетHTML = """ тело { маржа: 0; цвет фона: черный; } .iframe-контейнер iframe { верх: 0; слева: 0; ширина: 100%; высота: 100%; } var tag = document.createElement('script'); tag.src = "https://www.youtube.com/iframe_api"; var firstScriptTag = document.getElementsByTagName('script')[0]; firstScriptTag.parentNode.insertBefore(тег, firstScriptTag); вар-плеер; вар isPlaying = ложь; вар готов = ложь; функция onYouTubeIframeAPIReady() { игрок = новый YT.Player('игрок', { ширина: '100%', идентификатор видео: '\(ссылка)', playerVars: { 'playsinline': 1, 'controls': 0}, события: { 'onStateChange': функция (событие) { if (event.data === YT.PlayerState.ENDED) { //перезапускаем видео в конце player.seekTo(0); плеер.playVideo(); } } } }); } функция clickReady() { если (готов) { плеер.playVideo(); } } функция clickReadySec() { если (готов) { вар videoState = player.getPlayerState(); if (videoState !== YT.PlayerState.PLAYING) { плеер.playVideo(); } } } функция watchPlayingState() { если (isPlaying && готовый) { плеер.playVideo(); } еще { player.pauseVideo(); } } """ web.scrollView.isScrollEnabled = ложь web.loadHTMLString(embedHTML, baseURL: ноль) } } Просмотр вкладки, содержащей отдельные видео
импортировать SwiftUI структура AllVideoView: Посмотреть { @EnvironmentObject var viewModel: VideoModel var body: some View { ZStack { Color.black.edgesIgnoringSafeArea([.bottom, .top]) TabView(выбор: $viewModel.selected){ ForEach(viewModel.VideosToShow){ видео в SingleVideoView(ссылка: видео).tag(видео) } .rotationEffect(.init(градусы: -90)) .frame(ширина: widthOrHeight(ширина: true), высота: widthOrHeight(ширина: false)) } .frame(ширина: widthOrHeight(ширина: false), высота: widthOrHeight(ширина: true)) .rotationEffect(.init(градусы: 90)) .tabViewStyle(PageTabViewStyle(indexDisplayMode: .never)) } } } класс VideoModel: ObservableObject { @Published var selected = "" //Данные для проверки @Published вар VideosToShow: [String] = ["iYebqpQ_EBA", "ZVAo11HS2lc", "j7f704_t6-U", "sZVW6dJk7_I", "8v7Tx2re5Rs", "xn90M86iUGw", "XTg3S-Ncr3I", "gJodMlgxyuY", "4Crbau26xdM", "wjSrqX_In" Ук", " 4vaoZ0k7WC4", "wm-i3C2xi6E", "LOdHJKf8SaY", "T8SDP6rm-0k", "uIb5nrsWvxQ", "Qpbr5H34E_I", "MumEnIr_2vU", "6sTW7Q2uvo0", "ErnrcsM1CIk", "EuTUcRjtLbI", "xrovQjCSg" тМ", "DdlFT6ZPIR0" , "HcpuMXsgMfI", "Xxk4mdojVTQ", "NJue1Wh4GZg", "xECgbg3SKg4", "pmqznJ1Pr64", "KKYplXrrOW0", "YuaeouCu8z4", "--Ay4om9zdQ", "950tFcmRtMc", "k56Ry6FLuYU", "QDpYH" qDPYe0", "8JiB2ywr59c" " Fb8YYJdgnme", "ID1tfz4Hd1Q", " qSWzWYPOENY", "U-uIZLzgvfI", "R9r22u2gs_c", "3K0M69Npb1I", "DiHU8BJJ6jw", "4ZVsp4EkMb0", "SpHf4eBDjCk", "Rfudjg0jkvo", "EIFy41gYeVQ", "buQhVDlgbRg", "Rlz0" vCw3bYs", "5Tv5xBn1AoI", " 7Tv4RjbBSe8", "yhaFe_RzM14", "FDeMoHpyjJE", "h-RmBQ-GYY0", "e0rh5hgJJM0", "hjA4X_crpks", "wAVQ4u4V6vY", "NawgIgsyLTA", "OFtoXhXNEa0", "kPC9JW9c2Ro", "uhsy5vzmg" ls", "_Lm9ahDneqA" , "u6Y6rbblFek", "U-C_XW8sZl4", "RHqGlX3lIEo", "yl0AOc45tQg", "C2V8K5yMBdo", "j8ROZ7-aIE8", "FODrGNH1SjA", "JYpWudy4jaQ", "QvqyeUaExNo", "WDYG2DwfJDk", "zz2Fxk" f9u8M", " 8bFp8CNyEus", "oLdPRJ8qI84", "dowoyaGmTgE", "GtLbiDqrs-0", "MyoH8blacsE", "7LS879KOdK0", "D4Qpeliwk4w", "nmMI3BM1ea8", "VGWgFary4Es", "9qqfxtOiTJ0", "Vt61xtJilHA", "UR ХФПуСНвт8", " pzX-PSaIV-c", "QaK8XcvOEE", "pgk3d2VNbbo", "vKtZ5yqKY9A", "lVl-1ZTN8UY", "iKX79_6ZVpQ", "6FNjp772iyg", "ppaktgRUw4M", "qaf7uO5Hs0E", "3PfBSRfDa-8", "OG GLLoSyBCw" , "_GXpYnTjjCY", "TDACktZbs9s", "qRzix60msYs", "UUBKHJB5xm0", "tuFsPpkyjes", "woSAr1TY-rs", "3mCkBvpIuYc", "O2YoEcY51SY", "af3dkkGqIH4", "Clo8frpXQRk", "JrCaYLvp" ТТо", "ag8MAvd6t94" , "VOJVNyUtjUI", "ye5rxzDS_Bo", "SAZtnEuCnj0", "gO7bDxe-tWI", "iINSdcdhjBQ", "ysNIsT-1emU", "y97KR2XjMHA", "PkEeowOtqgE", "Z5NsgZQjjoc", "CeoIIi6oJiU", "-yMm ZT0XyBg", "B7Uz78okItk", "IZieI2VnpXM", "Vsb4yho_hTk", "Lu4Ie_lJsKg", "NBgOY-kHWVQ", "YnFI3IS8duA", "3fIWRHQYRb8", "prLyItjvb0w", "-gSbmK1eN_Q", "3Zkfq9JYaf4", "2 CvCIFPdgMo", "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", "oyswMIUFr" 4w", "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", "7ПлШвых70" , "epbrJg-LTOE", "9gE7MOnmHfs", "F5m2Qg2cst4", "kikGvjKFN8Q", "Kbvk8T6QXBE", "k-IXHTPWV8M", "uGiclr_zW2M", "rGIL7Yal2WA", "7-_LCq2UTA8", "VRvBcnQ7Yyo", "5R9zmI" 5Мбрк" , "HaUm23dTR5g", "otUIqpojXqA", "5Jq0IlNzvM4", "21EofWX0uuo", "PacSMXd1FIc", "sEWZ7TPXXDM", "zbElpS_OiGc", "0jSmuCKvj7U", "uoiQChfa-c8", "jv1IKf_NQ50", "j4Yl886jRSU", "E6JSDl70zEY", "-fyklDM48ig", "7amjvAwv" Xes", "agwGF1FeWOQ" , "tTft0J__BrA", "n_rf3Jjclrw", "gJDKloPhiMk", "vK3MG9yhn4o", "-UGDDKQE2FI", "a5RNe4vITws", "gf1Bvx6oVKw", "DCwSyZGQbEQ", "7G6ipKZfeLM", "UftB2CCWyHs", "Uf2FAF1r" SJQ", "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", "HctLr" 25JFx4", "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", "ndzHTk1t Jtk", "0MMQSjoLIVc ", "_-YsXJ72eeo", "zBP4TsOWbH0", "LecyAUI93c0", "C0OA0WcN0BY", "DAKxPMg6oPw", "UeSum0pCm_U", "L8GxXFW_y6Q", "Nm0WQ26euoc", "dwEIVP6kKLY", "kd2g1xBs90g", "rl YBLoYUiPQ", "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", "oxWowAGygq". 4", "qkkptG9UXg4 ", "fudFlhI43JQ", "6IoJ3iaT_2Q", "hTNADe1fGIs", "L6z3TZaTKl8", "dlXqMvzye24", "8iXzfysURSc", "ukBKppVJYLo", "sPkkWBckt44", "ecB8x_pP-8M", "xjMAlSs5Ayg", "tna_m" ХХтЧв", "https ://youtube.com/shorts/xyhY92K0RDI?si=DZAYC6285acLMJud", "ASEZtFsbjVE", "HFpHOoPa3yo", "DJvQzI9bae4", "-uUP5YUBJ-0", "0sx9QHubgqc", "7OeKeFisbgg", "6Kp30WYyF5M", "K709BAWg qs4" , "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", "3oy2hklC" dZM", "Yzs1NecYPPw" , "PTWTkfzQI5o", "0zEyTOb7Tr0", "KbzlC01D83c", "FQNP-gbyjZk", "JMWuo1XylM4", "t9_Nz91jxmY", "iYtdYc_WLM0", "Ak1lmnMzf-Y", "9sPRNYitLLY", "KpjTXmm7pGE", "K9Y" m44tJbQE", " lg_VwVnpqIE", "kv9PCqLj3vo", "aSd1IQNm4_s", "gjCz0BBGtQE", "2Rl9TivKS34", "HArdqJHadtE", "Xs0mbQUy9qg", "5ksMWUPhG3M", "JnDR5lQMYb4", "Iwck0KLLTAw", "-Z9d9-4I 21с", "f6W8DsmIC5k", "ibRhoFfbgZI", "KoW3Qr_Uttk", "HFgadwtV2U0", "tPgSLUBxwlo", "hJtrIFhHoiE", "q0_53pX1V40", "sQ64plrMdjY", "UIeOjGR1ols", "y2PAOVxHyzw", "ENfQ5Q9hzd4", "S81byfkCb" zc", "TBNfnC8lj2c", "2X -pqstQ9Mg", "BlEWAH17wus", "DLKn3np-QXA", "M1jGiFhPyvc", "uaxvB2YcccA", "aJIA0FM-ko4", "1CZgcxiSS4w", "HjFoOMpMQ6c", "IpLvmKbWDEQ", "220CEbaowoA", "CK2cCr4At" 1о", «cjA2TVjx6Xg», «wY8IjPPWqbg», «5o7Fe1P078s», «VwQtD-ZVbzs», «CUPvgecdsck»] }
Здравствуйте, я владелец Hustles (приложения для социальных сетей в магазине приложений iOS), и моя платформа включает в себя сервис, похожий на TikTok, который показывает короткометражки YouTube. Пользовательский интерфейс этого сервиса довольно глючный (ошибки указаны ниже) и не работает должным образом. Apple попросила меня немедленно исправить эти проблемы. Я был бы признателен всем за помощь в улучшении функциональности этого сервиса, поскольку мои знания в области веб-скрапинга и WebViews весьма ограничены.
Если вам поможет функциональность и ошибки, о которых я говорю, вы можете загрузить приложение здесь (https://apps.apple.com/us/app/hustle/id6452946210). Канал, похожий на TikTok, доступен на вкладке «Обзор», нажав на заголовок «Обнаружение».
Вот проблема, которую я хочу исправить:
[*]При загрузке видео экран мигает белым, а затем черным. Я хочу, чтобы обложка видео отображалась со значком загрузки вверху, пока видео не будет готово к воспроизведению.
Единый просмотр
импортировать SwiftUI импортировать WebKit импортировать UIKit структура SingleVideoView: Посмотреть { пусть ссылка: Строка @State Private var viewIsShowing = false @State Private var isVideoPlaying = false @EnvironmentObject var viewModel: VideoModel var body: some View { ZStack { Color.black //так что фон всегда черный SmartReelView (ссылка: ссылка, isPlaying: $isVideoPlaying, isChangingTime: $isChangingTime, totalLength: $totalLength, currentTime: $currentTime, viewIsShowing: $viewIsShowing) Button("", action: {}).disabled(true) //отключаем любое взаимодействие с видео на YouTube Цвет.серый.непрозрачность(0,001) .onTapGesture { isVideoPlaying.toggle() //воспроизведение видео на паузе } } .ignoresSafeArea() .onDisappear { //остановить воспроизведение, когда представление исчезнет isVideoPlaying = ложь viewIsShowing = ложь } .onAppear { //попробуем воспроизвести видео, когда появится представление если viewModel.selected == ссылка { viewIsShowing = правда isVideoPlaying = правда } } .onChange(of: viewModel.selected, выполните: { _ in //когда текущая ссылка на видео изменится, воспроизведите или приостановите если viewModel.selected == ссылка { viewIsShowing = правда isVideoPlaying = правда } иначе, если viewModel.selected != ссылка { viewIsShowing = ложь isVideoPlaying = ложь } }) } } структура SmartReelView: UIViewRepresentable { пусть ссылка: Строка @Binding var isPlaying: Bool @Binding var viewIsShowing: Bool func makeCoordinator() -> Координатор { Координатор (сам) } func makeUIView (контекст: Контекст) -> WKWebView { пусть userContentController = WKUserContentController() userContentController.add(context.coordinator, name: «наблюдать») пусть webConfiguration = WKWebViewConfiguration() webConfiguration.allowsInlineMediaPlayback = true webConfiguration.userContentController = userContentController пусть webview = WKWebView (кадр: .ноль, конфигурация: webConfiguration) webview.navigationDelegate = context.coordinator loadInitialContent (веб: веб-просмотр) вернуть веб-просмотр } func updateUIView(_ uiView: WKWebView, context: Context) { вар jsString = """ isPlaying = \((isPlaying) ? «истина» : "ЛОЖЬ"); смотретьPlayingState(); """ uiView.evaluateJavaScript (jsString, завершениеHandler: ноль) } Координатор класса: NSObject, WKNavigationDelegate, WKScriptMessageHandler { родительский вар: SmartReelView init (_ родитель: SmartReelView) { self.parent = родительский } func userContentController (_ userContentController: WKUserContentController, DidReceive сообщение: WKScriptMessage) { } func webView(_ webView: WKWebView, DidFinish Navigation: WKNavigation!) { //попробуем принудительно воспроизвести видео если self.parent.viewIsShowing { webView.evaluateJavaScript("clickReady()",completeHandler: ноль) //случайно повторим попытку, если загрузка заняла некоторое время Timer.scheduledTimer(withTimeInterval: 1,5, повторы: false) { _ in webView.evaluateJavaScript("clickReadySec()",completeHandler: ноль) } Timer.scheduledTimer(withTimeInterval: 3.0, повторы: false) { _ in webView.evaluateJavaScript("clickReadySec()",completeHandler: ноль) } } } } частная функция loadInitialContent (веб: WKWebView) { пусть встраиваетHTML = """ тело { маржа: 0; цвет фона: черный; } .iframe-контейнер iframe { верх: 0; слева: 0; ширина: 100%; высота: 100%; } var tag = document.createElement('script'); tag.src = "https://www.youtube.com/iframe_api"; var firstScriptTag = document.getElementsByTagName('script')[0]; firstScriptTag.parentNode.insertBefore(тег, firstScriptTag); вар-плеер; вар isPlaying = ложь; вар готов = ложь; функция onYouTubeIframeAPIReady() { игрок = новый YT.Player('игрок', { ширина: '100%', идентификатор видео: '\(ссылка)', playerVars: { 'playsinline': 1, 'controls': 0}, события: { 'onStateChange': функция (событие) { if (event.data === YT.PlayerState.ENDED) { //перезапускаем видео в конце player.seekTo(0); плеер.playVideo(); } } } }); } функция clickReady() { если (готов) { плеер.playVideo(); } } функция clickReadySec() { если (готов) { вар videoState = player.getPlayerState(); if (videoState !== YT.PlayerState.PLAYING) { плеер.playVideo(); } } } функция watchPlayingState() { если (isPlaying && готовый) { плеер.playVideo(); } еще { player.pauseVideo(); } } """ web.scrollView.isScrollEnabled = ложь web.loadHTMLString(embedHTML, baseURL: ноль) } } Просмотр вкладки, содержащей отдельные видео
импортировать SwiftUI структура AllVideoView: Посмотреть { @EnvironmentObject var viewModel: VideoModel var body: some View { ZStack { Color.black.edgesIgnoringSafeArea([.bottom, .top]) TabView(выбор: $viewModel.selected){ ForEach(viewModel.VideosToShow){ видео в SingleVideoView(ссылка: видео).tag(видео) } .rotationEffect(.init(градусы: -90)) .frame(ширина: widthOrHeight(ширина: true), высота: widthOrHeight(ширина: false)) } .frame(ширина: widthOrHeight(ширина: false), высота: widthOrHeight(ширина: true)) .rotationEffect(.init(градусы: 90)) .tabViewStyle(PageTabViewStyle(indexDisplayMode: .never)) } } } класс VideoModel: ObservableObject { @Published var selected = "" //Данные для проверки @Published вар VideosToShow: [String] = ["iYebqpQ_EBA", "ZVAo11HS2lc", "j7f704_t6-U", "sZVW6dJk7_I", "8v7Tx2re5Rs", "xn90M86iUGw", "XTg3S-Ncr3I", "gJodMlgxyuY", "4Crbau26xdM", "wjSrqX_In" Ук", " 4vaoZ0k7WC4", "wm-i3C2xi6E", "LOdHJKf8SaY", "T8SDP6rm-0k", "uIb5nrsWvxQ", "Qpbr5H34E_I", "MumEnIr_2vU", "6sTW7Q2uvo0", "ErnrcsM1CIk", "EuTUcRjtLbI", "xrovQjCSg" тМ", "DdlFT6ZPIR0" , "HcpuMXsgMfI", "Xxk4mdojVTQ", "NJue1Wh4GZg", "xECgbg3SKg4", "pmqznJ1Pr64", "KKYplXrrOW0", "YuaeouCu8z4", "--Ay4om9zdQ", "950tFcmRtMc", "k56Ry6FLuYU", "QDpYH" qDPYe0", "8JiB2ywr59c" " Fb8YYJdgnme", "ID1tfz4Hd1Q", " qSWzWYPOENY", "U-uIZLzgvfI", "R9r22u2gs_c", "3K0M69Npb1I", "DiHU8BJJ6jw", "4ZVsp4EkMb0", "SpHf4eBDjCk", "Rfudjg0jkvo", "EIFy41gYeVQ", "buQhVDlgbRg", "Rlz0" vCw3bYs", "5Tv5xBn1AoI", " 7Tv4RjbBSe8", "yhaFe_RzM14", "FDeMoHpyjJE", "h-RmBQ-GYY0", "e0rh5hgJJM0", "hjA4X_crpks", "wAVQ4u4V6vY", "NawgIgsyLTA", "OFtoXhXNEa0", "kPC9JW9c2Ro", "uhsy5vzmg" ls", "_Lm9ahDneqA" , "u6Y6rbblFek", "U-C_XW8sZl4", "RHqGlX3lIEo", "yl0AOc45tQg", "C2V8K5yMBdo", "j8ROZ7-aIE8", "FODrGNH1SjA", "JYpWudy4jaQ", "QvqyeUaExNo", "WDYG2DwfJDk", "zz2Fxk" f9u8M", " 8bFp8CNyEus", "oLdPRJ8qI84", "dowoyaGmTgE", "GtLbiDqrs-0", "MyoH8blacsE", "7LS879KOdK0", "D4Qpeliwk4w", "nmMI3BM1ea8", "VGWgFary4Es", "9qqfxtOiTJ0", "Vt61xtJilHA", "UR ХФПуСНвт8", " pzX-PSaIV-c", "QaK8XcvOEE", "pgk3d2VNbbo", "vKtZ5yqKY9A", "lVl-1ZTN8UY", "iKX79_6ZVpQ", "6FNjp772iyg", "ppaktgRUw4M", "qaf7uO5Hs0E", "3PfBSRfDa-8", "OG GLLoSyBCw" , "_GXpYnTjjCY", "TDACktZbs9s", "qRzix60msYs", "UUBKHJB5xm0", "tuFsPpkyjes", "woSAr1TY-rs", "3mCkBvpIuYc", "O2YoEcY51SY", "af3dkkGqIH4", "Clo8frpXQRk", "JrCaYLvp" ТТо", "ag8MAvd6t94" , "VOJVNyUtjUI", "ye5rxzDS_Bo", "SAZtnEuCnj0", "gO7bDxe-tWI", "iINSdcdhjBQ", "ysNIsT-1emU", "y97KR2XjMHA", "PkEeowOtqgE", "Z5NsgZQjjoc", "CeoIIi6oJiU", "-yMm ZT0XyBg", "B7Uz78okItk", "IZieI2VnpXM", "Vsb4yho_hTk", "Lu4Ie_lJsKg", "NBgOY-kHWVQ", "YnFI3IS8duA", "3fIWRHQYRb8", "prLyItjvb0w", "-gSbmK1eN_Q", "3Zkfq9JYaf4", "2 CvCIFPdgMo", "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", "oyswMIUFr" 4w", "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", "7ПлШвых70" , "epbrJg-LTOE", "9gE7MOnmHfs", "F5m2Qg2cst4", "kikGvjKFN8Q", "Kbvk8T6QXBE", "k-IXHTPWV8M", "uGiclr_zW2M", "rGIL7Yal2WA", "7-_LCq2UTA8", "VRvBcnQ7Yyo", "5R9zmI" 5Мбрк" , "HaUm23dTR5g", "otUIqpojXqA", "5Jq0IlNzvM4", "21EofWX0uuo", "PacSMXd1FIc", "sEWZ7TPXXDM", "zbElpS_OiGc", "0jSmuCKvj7U", "uoiQChfa-c8", "jv1IKf_NQ50", "j4Yl886jRSU", "E6JSDl70zEY", "-fyklDM48ig", "7amjvAwv" Xes", "agwGF1FeWOQ" , "tTft0J__BrA", "n_rf3Jjclrw", "gJDKloPhiMk", "vK3MG9yhn4o", "-UGDDKQE2FI", "a5RNe4vITws", "gf1Bvx6oVKw", "DCwSyZGQbEQ", "7G6ipKZfeLM", "UftB2CCWyHs", "Uf2FAF1r" SJQ", "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", "HctLr" 25JFx4", "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", "ndzHTk1t Jtk", "0MMQSjoLIVc ", "_-YsXJ72eeo", "zBP4TsOWbH0", "LecyAUI93c0", "C0OA0WcN0BY", "DAKxPMg6oPw", "UeSum0pCm_U", "L8GxXFW_y6Q", "Nm0WQ26euoc", "dwEIVP6kKLY", "kd2g1xBs90g", "rl YBLoYUiPQ", "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", "oxWowAGygq". 4", "qkkptG9UXg4 ", "fudFlhI43JQ", "6IoJ3iaT_2Q", "hTNADe1fGIs", "L6z3TZaTKl8", "dlXqMvzye24", "8iXzfysURSc", "ukBKppVJYLo", "sPkkWBckt44", "ecB8x_pP-8M", "xjMAlSs5Ayg", "tna_m" ХХтЧв", "https ://youtube.com/shorts/xyhY92K0RDI?si=DZAYC6285acLMJud", "ASEZtFsbjVE", "HFpHOoPa3yo", "DJvQzI9bae4", "-uUP5YUBJ-0", "0sx9QHubgqc", "7OeKeFisbgg", "6Kp30WYyF5M", "K709BAWg qs4" , "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", "3oy2hklC" dZM", "Yzs1NecYPPw" , "PTWTkfzQI5o", "0zEyTOb7Tr0", "KbzlC01D83c", "FQNP-gbyjZk", "JMWuo1XylM4", "t9_Nz91jxmY", "iYtdYc_WLM0", "Ak1lmnMzf-Y", "9sPRNYitLLY", "KpjTXmm7pGE", "K9Y" m44tJbQE", " lg_VwVnpqIE", "kv9PCqLj3vo", "aSd1IQNm4_s", "gjCz0BBGtQE", "2Rl9TivKS34", "HArdqJHadtE", "Xs0mbQUy9qg", "5ksMWUPhG3M", "JnDR5lQMYb4", "Iwck0KLLTAw", "-Z9d9-4I 21с", "f6W8DsmIC5k", "ibRhoFfbgZI", "KoW3Qr_Uttk", "HFgadwtV2U0", "tPgSLUBxwlo", "hJtrIFhHoiE", "q0_53pX1V40", "sQ64plrMdjY", "UIeOjGR1ols", "y2PAOVxHyzw", "ENfQ5Q9hzd4", "S81byfkCb" zc", "TBNfnC8lj2c", "2X -pqstQ9Mg", "BlEWAH17wus", "DLKn3np-QXA", "M1jGiFhPyvc", "uaxvB2YcccA", "aJIA0FM-ko4", "1CZgcxiSS4w", "HjFoOMpMQ6c", "IpLvmKbWDEQ", "220CEbaowoA", "CK2cCr4At" 1о", «cjA2TVjx6Xg», «wY8IjPPWqbg», «5o7Fe1P078s», «VwQtD-ZVbzs», «CUPvgecdsck»] }
-
- Похожие темы
- Ответы
- Просмотры
- Последнее сообщение
-
-
У меня ужасный сбой текстуры с pygame + python в моем маленьком клоне рендерера Doom.
Anonymous » » в форуме Python - 0 Ответы
- 14 Просмотры
-
Последнее сообщение Anonymous
-