Reducing Line Spacing of Text in SwiftUI
Asked Answered
A

6

40

For some fonts, the built-in line spacing is unpleasantly large.

SwiftUI gives us the Text modifier .lineSpacing() to adjust the spacing between lines of text (also called the leading, unrelated to leading/trailing). Its value specifies the number of points of additional spacing to place between consecutive lines of text, so that .lineSpacing(0) results in no change. Unfortunately, it does not appear to respond to negative values; .lineSpacing(-10) yields the same result as lineSpacing(0).

Does anyone know of a way to reduce line spacing in SwiftUI without resorting to UIKit?

Accra answered 7/9, 2020 at 14:56 Comment(6)
It is not a replacement, it is addition. Here is doc: "This value is always nonnegative. This value is included in the line fragment heights in the layout manager."Gaberdine
Yes, we know the value is in addition to normal spacing, not a replacement for it, because .lineSpacing(0) is intended to have no effect. Thank you @Gaberdine - but still looking for a solution.Accra
Alternatively, you could wrap UILabel to reduce line height: https://mcmap.net/q/408868/-fix-line-spacing-in-custom-font-in-swiftuiSpitler
Thanks, @TotoMinai—but, as I mentioned in the original post, I'm looking for a SwiftUI solution that doesn't use UIKit.Accra
I don't think it's possible to gain free control over negative line spacing in SwiftUI (as it ships in iOS 16 atm). My reasoning for this assessment:r as Text() seems to insist that the only compression you can make to the leading is down to Font.leading.tightEnding
I found this when searching for the solution: openradar.appspot.com/FB9842474 It uses an undocumented (but maybe not private) _lineHeightMultiple modifierPiscator
E
2

While I don't think it's possible to gain free control over negative line spacing in SwiftUI atm — as of Fall '22 all negative values create the same, only marginally tighter leading, no matter the value — you can reduce the leading of the Font itself, by applying .leading(.tight) to the Font. This did tighten a quick test example beyond what the negative value cap of .lineSpacing(-X) seems to achieve.

Ending answered 23/11, 2022 at 21:45 Comment(0)
O
5

EDIT: Unfortunately this stopped working in Xcode 15 as the function is now marked internal and there is no other option available. Damn Apple!


Based on tadelv comment.

lineSpacing does not accept negative values, but you have a hidden API _lineHeightMultiple that works. It says it's deprecated but the suggested one does not exist!

var body: some View {
  let font = UIFont.systemFont(ofSize: 12)
  let fontLineHeight = font.lineHeight
  let desiredLineHeight = 24.0

  Text("text")
    .font(Font(font))
    ._lineHeightMultiple(desiredLineHeight / fontLineHeight)
}

Remember that using private apis could be rejected by App Store Review or break on future ios versions. Use at your own risk! (Damn Apple!)

Obliteration answered 27/3, 2023 at 13:57 Comment(2)
This now produces a compilation error in Xcode 15 because it has been marked as internalCasie
Has anyone found a workaround on this?Ator
L
4

Introduced in iOS 17 there's an Environment modifyier that you use to increase/decrease line spacing:

MyView()
.environment(\._lineHeightMultiple, 0.9) 

Not sure if its a 100% public API but it does the job.

Lempres answered 20/3 at 0:4 Comment(1)
Probably not part of the public API because of the underscore.Pigfish
E
2

While I don't think it's possible to gain free control over negative line spacing in SwiftUI atm — as of Fall '22 all negative values create the same, only marginally tighter leading, no matter the value — you can reduce the leading of the Font itself, by applying .leading(.tight) to the Font. This did tighten a quick test example beyond what the negative value cap of .lineSpacing(-X) seems to achieve.

Ending answered 23/11, 2022 at 21:45 Comment(0)
S
0

You can use '\n' to denote line breaks and use the following:

struct CustomTextView: View {
  var text: String
  var font: Font
  var lineSpacing: CGFloat

  var body: some View {
    VStack(alignment: .leading, spacing: lineSpacing) {
      ForEach(text.components(separatedBy: "\n"), id: \.self) { line in
        Text(line)
          .font(font)
      }
    }
  }
}
Silda answered 2/11, 2023 at 8:24 Comment(0)
T
-1

I think it works using negative values in some way.

Example

First image has lineSpacing set to -3, second set to 0.

-3 is the less value where I can see some effects.

Toponymy answered 4/8, 2023 at 10:34 Comment(1)
As it’s currently written, your answer is unclear. Please edit to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers in the help center.Reconvert
F
-4

You can split up the text in multiple lines and apply negative padding around each one like this:

Text("This is the first Line")
    .padding(-10)
Text("This is the second Line")
    .padding(-10)
Frazee answered 6/1, 2021 at 13:54 Comment(2)
Thanks, @user14952184. A good idea for the special case where you don't need any automatic text wrapping. Though not for the general case.Accra
This leads to undesirable behavior with VoiceOver and DynamicType.Jawbone

© 2022 - 2024 — McMap. All rights reserved.