你好,我已经通过旋转选项卡视图和内部视图制作了一个垂直滚动的选项卡视图.在这样做的过程中,视角似乎向右移动了约10.5个点(或多或少地在更大和更小的设备上).我可以简单地添加一个偏移量,将视图向右移动10.5点,但这在所有设备上看起来都不是很好.单独显示视图而不显示任何选项卡视图看起来很好.就在我将视图放在垂直的tabView中之后,它看起来向右移动了.我希望能深入了解一种更有效的居中方法,而不是基本的偏移量修饰符.您可以忽略除AllVideoView中的代码之外的所有代码.

import SwiftUI
import WebKit
import UIKit

struct AllVideoView: View {
    @State private var selected = ""
    @State private var arr = ["-q6-DxWZnlQ", "Bp3iu47RRJQ", "lXJdgDjw1Ks", "It3ecCpuzgc", "7WNJjr8QM1w"]
    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)       // this makes it align right 
            .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
    }
}

推荐答案

假设您已经旋转了TabView及其内部的视图以创建垂直滚动的选项卡,旋转可能会导致一些空间调整,从而可能会移动视图.这种转变就是你正在经历的向右移动10.5个百分点的过程.

要使您的垂直TabView在不同的屏幕大小上保持一致,请考虑以下替代方案:

使用GeometryReader可获取父视图的尺寸.然后可以动态计算偏移以确保其居中.

GeometryReader { geometry in
    TabView(selection: $selected) {
        // your code
    }
    .rotationEffect(.init(degrees: 90))
    .offset(x: -(geometry.size.width - geometry.size.height) / 2)
}

另请参阅"How to use Geometry Reader in SwiftUI"以获取插图.

此外,请确保内部视图和旋转的TabView的框架的定义方式不会留下任何移动的空间.仔细判断您是在旋转之后而不是在旋转之前定义帧.


您可以使用.alignmentGuide根据TabView的尺寸调整内部视图的水平对齐方式.

假设您要基于某个计算的偏移(calculatedOffset)移动视图.

首先,您将定义一个HorizontalAlignment:

extension HorizontalAlignment {
    private enum MyAlignment: AlignmentID {
        static func defaultValue(in d: ViewDimensions) -> CGFloat {
            return d[HorizontalAlignment.center]
        }
    }

    static let myAlignment = HorizontalAlignment(MyAlignment.self)
}

然后,您可以将此自定义对齐指南应用于您的TabView,如下所示:

TabView(selection: $selected) {
    // Your existing code
}
.rotationEffect(.init(degrees: 90))
.alignmentGuide(HorizontalAlignment.myAlignment) { d in
    // calculatedOffset would be the value you have determined you need to move your view by
    let calculatedOffset: CGFloat = 10.5  // replace this with your calculated value
    return d[HorizontalAlignment.center] - calculatedOffset
}

最后,您需要确保您的ZStack使用此自定义对齐来定位TabView:

ZStack(alignment: Alignment(horizontal: .myAlignment, vertical: .center)) {
    // your existing code, including the TabView
}

我使用了硬编码的calculatedOffset值10.5作为占位符.但您可以将其替换为动态计算的任何值,以根据需要调整视图的位置.

通过使用对齐辅助线,您可以实现对视图布局的细粒度控制.这可能会帮助您解决您正在经历的问题.


作为另一种方法,您可以将TabView包装在另一个视图中,然后操作TabView在该容器中的位置.这允许对其放置进行更多控制.

HStack {
    Spacer()
    TabView(selection: $selected){
        // your code
    }
    .rotationEffect(.init(degrees: 90))
    Spacer()
}

或者创建封装旋转和平移逻辑的自定义SwiftUI修改器,使其更易于维护和跨不同视图apply.

其思想是根据实际尺寸动态调整视图布局,从而消除不能跨不同屏幕大小调整的硬编码值.

Swift相关问答推荐

如何使用RxSwift根据数据数绘制UICollectionView单元格

如何在realityKit中反转USDZ动画

字符串目录不适用于SWIFT包

如何在SWIFT中轻松格式化公制和英制使用的UnitVolume

按SWIFT中每个ID的最大值进行筛选/排序

UICollectionViewCompostionalLayout Collectionview单元格宽度在重新加载数据后未正确调整大小

如何使用AsyncTimerSequence获取初始时钟,然后在指定的时间间隔内开始迭代?

在数组中查找最大y值的x值

计算 CoreData 中所有唯一对象的数量?

在这个使用 URLSession 的简单 case 中是否创建了一个强引用循环?

一组可散列的自定义元素插入相等的元素

如何测试可选 struct 的协议一致性

关于变量的视图的 SwiftUI 生命周期

iOS16 Bug 键盘在表单解雇 SwiftUI 时打破布局

无法增加系统镜像的大小

如何让 SwiftUI 中的文本在旋转框架的同时侧向显示?

更改该函数的参数之一时,如何将这些 SwiftUI 异步任务合并到一个任务中以从函数中获得正确的结果

Subclass.fetchRequest() Swift 3.0,扩展并没有真正帮助 100%?

如何快速截取 UIView 的屏幕截图?

Swift 2.0 按属性对对象数组进行排序