将SwiftUI文本分成两列

gzjq41n4  于 2023-02-28  发布在  Swift
关注(0)|答案(2)|浏览(286)

我正在构建一个小部件,其中包含一些文本,这些文本是一个简短的单词和短语列表。

酒吧
巴兹
困惑
添加另一个
还有一个
目前的结果是:

因为它是一个简短项目的列表,所以如果它可以分成两列,效果会更好,如下所示

Foo           Add another
Bar           And one more
Baz
Bewildered

下面是当前的简单代码(去掉了字体和空格):

struct WidgetEntryView : View {
    
  var entry: Provider.Entry
  
  var body: some View {
    ZStack {
      
      Color(entry.color)
      
      VStack(alignment: .leading) {
        Text(entry.name)
        
        Text("Updated in 6 hours")
        
        Text(entry.content)
      }
    }
  }
}

我发现this guide可以告诉我文本是否被截断了,但是我需要知道 * 什么 * 文本被截断了,这样我就可以在右边添加另一个Text视图来显示剩余的字符,或者最好使用一些本地方法来在两个文本视图之间延续文本。

xtfmy6hx

xtfmy6hx1#

这当然不是理想的,但这是我的想法。要点是我使用问题中链接的截断文本范例来获得可用高度。然后我使用小部件的宽度减去填充来迭代文本,直到它不再适合一半的宽度。
一些缺点是:(1)左列必须是小部件宽度的一半或更小,而实际上,如果它更大,有时可以容纳更多的内容,(2)很难100%确定所有的间距都被考虑在内,以及(3)必须硬编码小部件的尺寸。
无论如何,希望这对任何寻找类似解决方案的人有所帮助!
下面是为清晰起见删除了间距和颜色的代码:

struct SizePreferenceKey: PreferenceKey {
  static var defaultValue: CGSize = .zero
  static func reduce(value: inout CGSize, nextValue: () -> CGSize) {}
}

extension View {
  func readSize(onChange: @escaping (CGSize) -> Void) -> some View {
    background(
      GeometryReader {
        geometryProxy in
        Color.clear
          .preference(key: SizePreferenceKey.self, value: geometryProxy.size)
      })
      .onPreferenceChange(SizePreferenceKey.self, perform: onChange)
  }
}

struct TruncableText: View {
  let text: Text
  @State private var intrinsicSize: CGSize = .zero
  @State private var truncatedSize: CGSize = .zero
  let isTruncatedUpdate: (_ isTruncated: Bool, _ truncatedSize: CGSize) -> Void
  var body: some View {
    text
      .readSize { size in
        truncatedSize = size
        isTruncatedUpdate(truncatedSize != intrinsicSize, size)
      }
      .background(
        text
          .fixedSize(horizontal: false, vertical: true)
          .hidden()
          .readSize { size in
            intrinsicSize = size
            if truncatedSize != .zero {
              isTruncatedUpdate(truncatedSize != intrinsicSize, truncatedSize)
            }
          })
  }
}

/**
 - Parameter text: The entire contents of the note
 - Parameter size: The size of the text area that was used to initially render the first note
 - Parameter widgetWidth: exact width of the widget for the current family/screen size
 */
func partitionText(_ text: String, size: CGSize, widgetWidth: CGFloat) -> (String, String)? {
  var part1 = ""
  var part2 = text
  
  let colWidth = widgetWidth / 2 - 32 // padding
  let colHeight = size.height
  
  // Shouldn't happen but just block against infinite loops
  for i in 0...100 {
    // Find the first line, or if that doesn't work the first space
    var splitAt = part2.firstIndex(of: "\n")
    if (splitAt == nil) {
      splitAt = part2.firstIndex(of: "\r")
      if (splitAt == nil) {
        splitAt = part2.firstIndex(of: " ")
      }
    }
    
    // We have a block of letters remaining. Let's not split it.
    if splitAt == nil {
      if i == 0 {
        // If we haven't split anything yet, just show the text as a single block
        return nil
      } else {
        // Divide what we had
        break
      }
    }
    
    let part1Test = String(text[...text.index(splitAt!, offsetBy: part1.count)])
    let part1TestSize = part1Test
      .trimmingCharacters(in: .newlines)
      .boundingRect(with: CGSize(width: colWidth, height: .infinity),
                    options: .usesLineFragmentOrigin,
                    attributes: [.font: UIFont.systemFont(ofSize: 12)],
                    context: nil)
    
    if (part1TestSize.height > colHeight) {
      // We exceeded the limit! return what we have
      break;
    }
    
    part1 = part1Test
    part2 = String(part2[part2.index(splitAt!, offsetBy: 1)...])
  }

  return (part1.trimmingCharacters(in: .newlines), part2.trimmingCharacters(in: .newlines))
}

func getWidgetWidth(_ family: WidgetFamily) -> CGFloat {
  switch family {
    case .systemLarge, .systemMedium:
      switch UIScreen.main.bounds.size {
        case CGSize(width: 428, height: 926):   return 364
        case CGSize(width: 414, height: 896):   return 360
        case CGSize(width: 414, height: 736):   return 348
        case CGSize(width: 390, height: 844):   return 338
        case CGSize(width: 375, height: 812):   return 329
        case CGSize(width: 375, height: 667):   return 321
        case CGSize(width: 360, height: 780):   return 329
        case CGSize(width: 320, height: 568):   return 292
        default:                                return 330
      }
    default:
      switch UIScreen.main.bounds.size {
        case CGSize(width: 428, height: 926):   return 170
        case CGSize(width: 414, height: 896):   return 169
        case CGSize(width: 414, height: 736):   return 159
        case CGSize(width: 390, height: 844):   return 158
        case CGSize(width: 375, height: 812):   return 155
        case CGSize(width: 375, height: 667):   return 148
        case CGSize(width: 360, height: 780):   return 155
        case CGSize(width: 320, height: 568):   return 141
        default:                                return 155
      }
    }
}

struct NoteWidgetEntryView : View {
  
  @State var isTruncated: Bool = false
  @State var colOneText: String = ""
  @State var colTwoText: String = ""
  
  var entry: Provider.Entry

  @Environment(\.widgetFamily) var family: WidgetFamily
  
  var body: some View {
    ZStack{
      Color(entry.color)
      
      VStack {
        
        Text(entry.name)
        Text("Updated 6 hours ago")
        
        if entry.twoColumn {
          if (isTruncated) {
            HStack {
              Text(colOneText).font(.system(size:12))
              Text(colTwoText).font(.system(size:12))
            }
          } else {
            TruncableText(text: Text(entry.content).font(.system(size:12))) {
              let size = $1
              if ($0 && colTwoText == "") {
                if let (part1, part2) = partitionText(entry.content, size: size, widgetWidth: getWidgetWidth(family)) {
                  colOneText = part1
                  colTwoText = part2
                  
                  // Only set this if we successfully partitioned the text
                  isTruncated = true
                }
              }
            }
          }
        } else {
          Text(entry.content).font(.system(size:12))
        }
      }
    }
  }
}
vfh0ocws

vfh0ocws2#

使用视图的框架修饰符

这可以使用帧修改器完成。
尝试创建一个HStack,其中的每个视图将获得与.frame(minWidth: 0, maxWidth: .infinity)相同的帧修改器。
这将平均分配视图。
看了你的代码我觉得这个可以用。

struct WidgetEntryView : View {
    
  var entry: Provider.Entry
  
  var body: some View {
    ZStack {
      
      Color(entry.color)
      
      VStack(alignment: .leading) {
        Text(entry.name)
        
        Text("Updated in 6 hours")
        // your entry.content needs to be formatted to an HStack

        HStack {
            Text(entry.content)
            .frame(minWidth: 0, maxWidth: .infinity)
            
            Text(entry.content)
            .frame(minWidth: 0, maxWidth: .infinity)
        }
    }
  }
}

请参阅这篇文章:
SwiftUI: Two equal width columns

相关问题