在 SwiftUI 中的列表视图旁边绘制带有圆圈的垂直线

Posted

技术标签:

【中文标题】在 SwiftUI 中的列表视图旁边绘制带有圆圈的垂直线【英文标题】:Draw verticle Line with circle along side listview in SwiftUI 【发布时间】:2021-08-31 14:18:25 【问题描述】:

我正在努力实现

这将是与列表视图一起运行的垂直线。列表视图中每个单元格的中间都有一个小圆圈。

我试过了

GeometryReader  geometry in
                        VStack 
                            Path  path in
                                path.move(to: CGPoint(x: geometry.size.height / 2, y: 3))
                                path.addLine(to: CGPoint(x: geometry.size.height / 2, y: geometry.size.width))
                            
                            .stroke(style: StrokeStyle( lineWidth: 3))
                            .foregroundColor(Color(UIColor.systemBlue))
                        
                    

这会垂直创建一条实线,但无法达到预期的效果,也无法弄清楚如何在每个内容的中间放置圆圈。

更新: 我尝试了下面的代码,它接近我的 UX 要求

import SwiftUI

struct VerticleLineCircle: View 
let array: Array<Int> = Array(0...10)


public var body: some View 
    ScrollView
    VStack 
        ForEach(0..<array.count)  index in
                GeometryReader  geometry in
                        VStack 
                            Path  path in
                                path.move(to: CGPoint(x: geometry.size.height / 2, y: -10))
                                path.addLine(to: CGPoint(x: geometry.size.height / 2, y: geometry.size.width))
                            
                            .stroke(style: StrokeStyle( lineWidth: 2))
                            .foregroundColor(Color(UIColor.systemBlue))
                            .padding(EdgeInsets(top: 0, leading: 10, bottom: 0, trailing: 0))
                        
                    
                Text("Hello, here is some custom view!")
                HStack 
                        Circle().fill(Color.blue)
                            .frame(width: 10, height: 10)
                            .position(x: 15, y: 0)
                        Line()
                            .stroke(style: StrokeStyle(lineWidth: 0.5))
                            .frame(height: 1)
                            .foregroundColor(Color.gray.opacity(80))
                
            
        
    
 


 struct Line: Shape 
        func path(in rect: CGRect) -> Path 
        var path = Path()
        path.move(to: CGPoint(x: -150, y: -5))
        path.addLine(to: CGPoint(x: rect.width, y: 0))
        return path
         
  


  struct SwiftUIView3_Previews: PreviewProvider 
       static var previews: some View 
            VerticleLineCircle()
       
   

但仍然缺少一些东西 即在索引 0 和最后我希望线在视图中间以圆圈结束

【问题讨论】:

我认为使用 List 是不可能的,也许以自定义方式。 您已经更改了要求,并且您的代码有很多事情要做,这需要大量的重构。您的所有值都是硬编码的,并且可能会根据设备产生不一致的视图。 @loremipsum:当我们更新所有问题的答案时,我认为你和我都会成为百万富翁。这么好的付款!我不能忽视它。 @swiftPunk OP 在他们出现时删除了评论。这对我来说是拼图的快感。这是一个简单的。 @loremipsum:是的,他也对我做了同样的事,当我得到这样的 OP 时,我也会时不时地发生这种情况,他们在讨价还价,却忘记了我们花时间为了这!不知道!如果我在不使用路径的情况下云回答它,我只是把这个问题回答得很有趣!否则我会删除我的答案,也许我会保留它以供其他人查看或使用。 【参考方案1】:

这是另一种使用自定义方式的方法,我称之为 VerticalLineInForEachView,我们不能使用 List 来完成这项工作:

struct ContentView: View 
    
    let array: Array<Int> = Array(0...10)
    
    var body: some View 
        
        VerticalLineInForEachView(sizeOfCircle: 20.0, widthOfLine: 2.0, padding: 5.0, colorOfCircle: .black, colorOfLine: .red, showDivider: true, array: array)  index in
            
            HStack 
                
                CustomView(index: index)
                
                Spacer()
                
            
            .padding()
            .background(Color.yellow.opacity(0.2).cornerRadius(16))
            .padding(.vertical, 5.0)
            .padding(.trailing)
        
        .padding(.leading, 5.0)
        
        
    
    



struct VerticalLineInForEachView<Content: View, T>: View 
    
    let sizeOfCircle: CGFloat
    let widthOfLine: CGFloat
    let padding: CGFloat
    let colorOfCircle: Color
    let colorOfLine: Color
    let showDivider: Bool
    let array: Array<T>
    @ViewBuilder let content: (Int) -> Content
    
    var body: some View 
        
        ScrollView 
            
            LazyVStack(alignment: .leading, spacing: 0.0) 
                
                let upperBound: Int = array.count - 1
                
                ForEach(array.indices, id: \.self, content:  index in
                    
                    VStack  content(index) 
                        .padding(.leading, (sizeOfCircle/2.0 + padding))
                        .overlay(((index != upperBound) && (index != 0)) ? colorOfLine.frame(width: widthOfLine).offset(x: -widthOfLine/2.0) : nil , alignment: .leading)
                        .overlay(((index == upperBound) || (index == 0)) ?
                                    
                                    GeometryReader  proxy in
                                        
                                        colorOfLine
                                            .frame(width: widthOfLine, height: proxy.size.height/2.0)
                                            .offset(x: -widthOfLine/2.0, y: (index == 0) ? proxy.size.height/2.0 : 0.0)
                                        
                                    
                                    
                                    : nil , alignment: .leading)
                        .overlay(Circle().fill(colorOfCircle).frame(width: sizeOfCircle, height: sizeOfCircle).offset(x: -sizeOfCircle/2.0) , alignment: .leading)
                        .overlay(((index != upperBound) && showDivider) ? Color.secondary.opacity(0.2).frame(height: 2).offset(y: 1).padding(.horizontal).padding(.leading, sizeOfCircle) : nil, alignment: .bottom)
                    
                 )
                
            
            .padding(.leading, sizeOfCircle/2.0)
            
        
        
    
    



struct CustomView: View 
    
    let index: Int
    
    var body: some View 
        
        let randomSize: CGFloat = CGFloat.random(in: 50.0...250.0)
        
        if index.isMultiple(of: 2) 
            
            if Bool.random() 
                
                Circle().fill(Color.blue).frame(width: randomSize, height: randomSize, alignment: .center)
            
            else 
                Image(systemName: "books.vertical").resizable().scaledToFit().frame(width: randomSize, height: randomSize, alignment: .center)
            
            
        
        else 
            
            if Bool.random() 
                
                Text(" Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.")
                
            
            else 
                Text("Hello, World!")
                
            
            
        
        
    
    

【讨论】:

@AT9:我的回答很好,不需要更新,你不能为了其他问题不时更新你的问题,或者解决小问题!感谢意味着在这里投票或接受它作为答案。【参考方案2】:

不适用于List,因为它有自定义的padding 会留下空白,但您可以在ScrollView 中使用VStack 来做到这一点

import SwiftUI

struct CustomListView: View 
    ///How far from the left edge
    let start: CGFloat = 15
    ///Padding between margin and your row content
    let padding: CGFloat = 16
    var body: some View 
        ScrollView
            VStack(spacing: 0)
                ForEach(0..<10) n in
                    VStack
                        //if the item is the first element
                        if n == 0
                            CustomRowMarginView(start: start, rightPadding: padding, postion: .start)
                                Text("Your row Content goes here \(n)")
                            
                        
                        //if the item is the last element
                        else if n == 9
                            CustomRowMarginView(start: start, rightPadding: padding, postion: .end)
                                Text("Your row Content goes here \(n)")
                            
                        
                        //if the item is the middle element
                        else
                            CustomRowMarginView(start: start, rightPadding: padding, postion: .mid)
                                Text("Your row Content goes here \(n)")
                            
                        
                    
                    .frame(height: CGFloat(Int.random(in: 20...100)))
                    Divider().padding(.leading, start + padding)
                
            
        
    


struct CustomRowMarginView<Content:View>: View 
    ///How far from the left edge
    let start: CGFloat
    ///Padding between margin and your row content
    let rightPadding: CGFloat
    let color: Color = Color(UIColor.systemBlue)
    let content: Content
    let postion: CustomLineShape.Position
    init(start: CGFloat, rightPadding: CGFloat, postion: CustomLineShape.Position = .mid, @ViewBuilder _ content: @escaping () -> Content)
        self.start = start
        self.rightPadding = rightPadding
        self.postion = postion
        self.content = content()
    
    var body: some View 
        HStack
            ZStack(alignment: .leading)
                CustomLineShape(start: start, postion: postion)
                    .stroke(style: StrokeStyle( lineWidth: 3))
                    .foregroundColor(color)
                CustomDotShape(start: start, postion: postion)
                    .fill(color)
            
            .frame(width: start + rightPadding)
            content
            Spacer()
        
    
    
    

struct CustomLineShape: Shape 
    let start: CGFloat
    let postion: Position
    func path(in rect: CGRect) -> Path 
        var path = Path()
        if postion == .start || postion == .mid
            path.move(to: CGPoint(x: start, y: postion == .start ? rect.midY : rect.minY))
            path.addLine(to: CGPoint(x: start, y: rect.maxY))
        else
            path.move(to: CGPoint(x: start, y: rect.midY))
            path.addLine(to: CGPoint(x: start, y: rect.minY))
        
        return path
    
    enum Position
        case start
        case end
        case mid
    

struct CustomDotShape: Shape 
    let start: CGFloat
    let postion: CustomLineShape.Position
    func path(in rect: CGRect) -> Path 
        var path = Path()
        if postion == .start 
            path.move(to: CGPoint(x: start, y: rect.midY ))
        else
            path.move(to: CGPoint(x: start, y: rect.minY))
        
        
        path.addRelativeArc(center: CGPoint(x: start, y:  postion == .start || postion == .end ? rect.midY : rect.minY), radius: 5, startAngle: Angle(degrees: 0), delta: Angle(degrees: 360), transform: .identity)
        return path
    
    

struct CustomListView_Previews: PreviewProvider 
    static var previews: some View 
        CustomListView()
    

【讨论】:

以上是关于在 SwiftUI 中的列表视图旁边绘制带有圆圈的垂直线的主要内容,如果未能解决你的问题,请参考以下文章

SwiftUI:如何让列表视图中的图像与列表项的操作不同?

SwiftUI,列表行添加按钮

如何将页脚视图添加到 SwiftUI 中的列表?

SwiftUI:列表项跳转到选取器表单中的标题

SwiftUI:将列表添加到带有上方图标的滚动视图中

macOS 上的 SwiftUI:带有详细视图和多选的列表