Simple Layout Engine을 사용한 SwiftUI 사용자 정의 레이아웃
 Nov 28 2022
SwiftUI 사용자 정의 레이아웃에 필요한 수학은 AutoLayout 및 제약 조건 기반 시스템 이전의 일을 상기시킵니다. 좋은 점은 Simple Layout Engine이 관련된 모든 수학을 처리할 수 있는 멋진 시스템을 이미 제공하고 있다는 것입니다.
                    SwiftUI 사용자 정의 레이아웃에 필요한 수학은 AutoLayout 및 제약 조건 기반 시스템 이전의 일을 상기시킵니다. 좋은 점은 Simple Layout Engine 이 관련된 모든 수학을 처리할 수 있는 멋진 시스템을 이미 제공하고 있다는 것입니다. 시연을 위해 WWDC 세션에서 SwiftUI로 사용자 정의 레이아웃 작성 주제에 대한 데모 앱의 하위 집합을 빌드합니다 .
문제
아이디어는 모든 자식이 동일한 너비를 갖는 것과 유사한 컨테이너 뷰를 HStack갖는 것이지만 너비는 자식이 가진 최대값이어야 한다는 점을 제외하고는 예외입니다. 이것이 HStack기본적으로 자식을 배치하는 방법입니다.
HStack {
    WLText("hi")
    WLText("!")
    WLText("beautiful")
    WLText("world")
}
      
                
                    
                    
                
            
     
    우리가 실제로 원하는 것은 width = max(children.width) 와 같은 것을 갖는 것입니다. 그러면 아름다운 텍스트 의 너비와 동일한 너비를 갖는 모든 자식을 갖게 됩니다.
BalancedHStack {
    WLText("hi")
    WLText("!")
    WLText("beautiful")
    WLText("world")
}
      
                
                    
                    
                
            
     
    SwiftUI모든 사용자 정의 수학을 제공하기 위해 레이아웃 시스템에 연결하는 방법을 제공합니다. 우리의 경우 프로토콜 BalancedHStack을 준수하는 를 만들 수 있습니다. 프로토콜에는 두 가지 방법 Layout이 필요합니다.Layout
sizeThatFits:CGSize컨테이너 전체를 시스템에 제공하기 위해placeSubviews: 제공된 경계 내에서 자식의 위치를 업데이트하기 위해- 간단한 레이아웃 엔진
 - 형세
 - 맞는 보기
 
struct BalancedHStack: Layout {
  func sizeThatFits(proposal: ProposedViewSize,
                    subviews: Subviews,
                    cache: inout ()) -> CGSize {
    fatalError()
    // TODO
  }
  func placeSubviews(in bounds: CGRect,
                     proposal: ProposedViewSize,
                     subviews: Subviews,
                     cache: inout ()) {
    // TODO
  }
}
      
     
     struct BalancedHStack: Layout {
  struct CacheData {
    let childSize: CGSize
    let distances: [CGFloat]
  }
  func makeCache(subviews: Subviews) -> CacheData {
    let subviewSizes = subviews.map { $0.sizeThatFits(.unspecified) }
    let width = subviewSizes.map { $0.width }.max() ?? 0
    let height = subviewSizes.map { $0.height }.max() ?? 0
    let distances: [CGFloat] = (0..<subviews.count).map { idx in
      guard idx < subviews.count - 1 else { return 0 }
      return subviews[idx].spacing.distance(to: subviews[idx + 1].spacing, along: .horizontal)
    }
    return CacheData(
      childSize: CGSize(width: width, height: height),
      distances: distances
    )
  }
  // ...
}
      
     
     func sizeThatFits(proposal: ProposedViewSize,
                    subviews: Subviews,
                    cache: inout CacheData) -> CGSize {
    let totalDistance = cache.distances.reduce(0, +)
    return CGSize(
      width: cache.childSize.width * CGFloat(subviews.count) + totalDistance,
      height: cache.childSize.height
    )
  }
      
     
     func placeSubviews(in bounds: CGRect,
                     proposal: ProposedViewSize,
                     subviews: Subviews,
                     cache: inout CacheData) {
    let layout = SLELayout(parentFrame: bounds, direction: .row, alignment: .center)
    do {
      var items: [SLEItem] = []
      for idx in 0..<subviews.count {
        items.append(try layout.add(item: .size(cache.childSize)))
        try layout.add(item: .width(cache.distances[idx]))
      }
      for (idx, subview) in subviews.enumerated() {
        subview.place(
          at: try items[idx].frame().origin,
          proposal: ProposedViewSize(cache.childSize)
        )
      }
    }
    catch { print("Unable to layout \(error)") }
  }
       
                
                    
                    
                
            
      
     
     extension SLEDirection {
  var axis: Axis {
    switch self {
    case .row: return .horizontal
    case .column: return .vertical
    }
  }
}
struct BalancedStack: Layout {
  let direction: SLEDirection
  init(_ direction: SLEDirection) {
    self.direction = direction
  }
  struct CacheData {
    let childSize: CGSize
    let distances: [CGFloat]
  }
  func makeCache(subviews: Subviews) -> CacheData {
    let subviewSizes = subviews.map { $0.sizeThatFits(.unspecified) }
    let width = subviewSizes.map { $0.width }.max() ?? 0
    let height = subviewSizes.map { $0.height }.max() ?? 0
    let distances: [CGFloat] = (0..<subviews.count).map { idx in
      guard idx < subviews.count - 1 else { return 0 }
      return subviews[idx].spacing.distance(to: subviews[idx + 1].spacing, along: direction.axis)
    }
    return CacheData(
      childSize: CGSize(width: width, height: height),
      distances: distances
    )
  }
  func sizeThatFits(proposal: ProposedViewSize,
                    subviews: Subviews,
                    cache: inout CacheData) -> CGSize {
    let totalDistance = cache.distances.reduce(0, +)
    let containerWidth: CGFloat
    let containerHeight: CGFloat
    switch direction {
    case .row:
      containerWidth = cache.childSize.width * CGFloat(subviews.count) + totalDistance
      containerHeight = cache.childSize.height
    case .column:
      containerWidth = cache.childSize.width
      containerHeight = cache.childSize.height * CGFloat(subviews.count) + totalDistance
    }
    return CGSize(width: containerWidth, height: containerHeight)
  }
  func placeSubviews(in bounds: CGRect,
                     proposal: ProposedViewSize,
                     subviews: Subviews,
                     cache: inout CacheData) {
    let layout = SLELayout(parentFrame: bounds, direction: direction, alignment: .center)
    do {
      var items: [SLEItem] = []
      for idx in 0..<subviews.count {
        items.append(try layout.add(item: .size(cache.childSize)))
        try layout.add(item: .width(cache.distances[idx]))
      }
      for (idx, subview) in subviews.enumerated() {
        subview.place(
          at: try items[idx].frame().origin,
          proposal: ProposedViewSize(cache.childSize)
        )
      }
    }
    catch { print("Unable to layout \(error)") }
  }
}
       
                
                    
                    
                
            
      
     
     struct TextList: View {
  var body: some View {
    WLText("hi")
    WLText("!")
    WLText("beautiful")
    WLText("world")
  }
}
ViewThatFits {
    BalancedStack(.row) {
        TextList()
    }
    BalancedStack(.column) {
        TextList()
    }
}
      
     
![연결된 목록이란 무엇입니까? [1 부]](https://post.nghiatu.com/assets/images/m/max/724/1*Xokk6XOjWyIGCBujkJsCzQ.jpeg)



































