Swiftui .Rotationeffect() Framing and Offsetting

SwiftUI .rotationEffect() framing and offsetting

You need to adjust the frame yourself in this case. That requires capturing what the frame is, and then applying the adjustment.

First, to capture the existing frame, create a preference, which is a system for passing data from child views to their parents:

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

extension View {
func captureSize(in binding: Binding<CGSize>) -> some View {
overlay(GeometryReader { proxy in
Color.clear.preference(key: SizeKey.self, value: proxy.size)
})
.onPreferenceChange(SizeKey.self) { size in binding.wrappedValue = size }
}
}

This creates a new .captureSize(in: $binding) method on Views.

Using that, we can create a new kind of View that rotates its frame:

struct Rotated<Rotated: View>: View {
var view: Rotated
var angle: Angle

init(_ view: Rotated, angle: Angle = .degrees(-90)) {
self.view = view
self.angle = angle
}

@State private var size: CGSize = .zero

var body: some View {
// Rotate the frame, and compute the smallest integral frame that contains it
let newFrame = CGRect(origin: .zero, size: size)
.offsetBy(dx: -size.width/2, dy: -size.height/2)
.applying(.init(rotationAngle: CGFloat(angle.radians)))
.integral

return view
.fixedSize() // Don't change the view's ideal frame
.captureSize(in: $size) // Capture the size of the view's ideal frame
.rotationEffect(angle) // Rotate the view
.frame(width: newFrame.width, // And apply the new frame
height: newFrame.height)
}
}

And for convenience, an extension to apply it:

extension View {
func rotated(_ angle: Angle = .degrees(-90)) -> some View {
Rotated(self, angle: angle)
}
}

And now your code should work as you expect:

struct TextAloneView: View {

var body: some View {
VStack {
Text("Horizontal text")
Text("Vertical text").rotated()
}
}
}

SwiftUI: Text has not the full width of screen after rotation

Here is a demo of possible approach. Prepared with Xcode 12.1 / iOS 14.1

demo

struct DemoView: View {
var body: some View {
HStack {
RotatedText(text: lorem, angle: Angle(degrees: 90))
RotatedText(text: lorem, angle: Angle(degrees: -90))
}
}
}

struct RotatedText: View {
let text: String
let angle: Angle
var color: Color = .blue

var body: some View {
color.overlay(
GeometryReader { gp in
VStack {
Text(text)
.frame(width: gp.size.height, height: gp.size.width)
.rotationEffect(angle)
}.frame(width: gp.size.width, height: gp.size.height)
}
)
}
}

How to build scrollable side menu using text rotation (or something similar) in SwiftUI?

I did found a good solution using Attempt 2 and rotating the entire Text view using @robniper detailed answer here : https://stackoverflow.com/a/59802487/5541378

How to make SwiftUI Picker Wrap Text

A solution appears a bit complicated because Picker is not native SwiftUI control, but has UIPickerView at backend, so we need combination of already mentioned one to get rid of compression contains and another one to calculate most long label and compress picker explicitly.

demo

Main part is

@State private var maxWidth = CGFloat.zero
@State private var width = CGFloat.infinity

var body: some View {
Picker("", selection: $selection) {
ForEach( ...

// Row/Label view is here

.background(GeometryReader {
Color.clear.preference(key: ViewWidthKey.self,
value: $0.frame(in: .local).size.width)
})
.onPreferenceChange(ViewWidthKey.self) {
self.maxWidth = max($0, maxWidth)
width = max($0, maxWidth)
}
}
}
.pickerStyle(.wheel)
.frame(maxWidth: width + 2 * 20 /* padding on both sides */)
}

Tested with Xcode 13.4 / iOS 15.5

Test code on GitHub

Automatic adjust font size in Text() of Swiftui?

Use minimumScaleFactor(_:) https://developer.apple.com/documentation/swiftui/text/minimumscalefactor(_:)

struct ContentView: View {
var body: some View {
VStack{
Text("Test string")
.minimumScaleFactor(0.1) //<--Here
.frame(width: 15, height: 15)
}
}
}


Related Topics



Leave a reply



Submit