I have 3 wheel pickers inside a HStack to allow for selecting hours, minutes, and seconds.
Here's the playground code to help illustrate the issue:
import SwiftUI
import PlaygroundSupport
struct DurationPickers: View {
#State private var hourSelection = 0
#State private var minuteSelection = 0
#State private var secondSelection = 0
private let hoursArray = [Int](0..<24)
private let minutesArray = [Int](0..<61)
private let secondsArray = [Int](0..<61)
private let fullHeight: CGFloat = 256
var body: some View {
GeometryReader { geometry in
VStack(spacing: 0) {
HStack(spacing: 0) {
Text("Hours")
.frame(maxWidth: .infinity)
Text("Minutes")
.frame(maxWidth: .infinity)
Text("Seconds")
.frame(maxWidth: .infinity)
}
.frame(maxWidth: .infinity)
HStack(spacing: 0) {
Picker(selection: self.$hourSelection, label: Text("")) {
ForEach(0 ..< self.hoursArray.count, id:\.self) { index in
Text("\( self.hoursArray[index])").tag(index)
}
}
.pickerStyle(WheelPickerStyle())
.frame(width: geometry.size.width/3, alignment: .center)
Picker(selection: self.$minuteSelection, label: Text("")) {
ForEach(0 ..< self.minutesArray.count, id:\.self) { index in
Text("\( self.minutesArray[index])").tag(index)
}
}
.pickerStyle(WheelPickerStyle())
.frame(width: geometry.size.width/3, alignment: .center)
Picker(selection: self.self.$secondSelection, label: Text("")) {
ForEach(0 ..< self.secondsArray.count, id:\.self) { index in
Text("\( self.secondsArray[index])").tag(index)
}
}
.pickerStyle(WheelPickerStyle())
.frame(width: geometry.size.width/3, alignment: .center)
}
.frame(maxWidth: .infinity)
}
}
.frame(maxHeight: fullHeight)
.background(Color.gray)
}
}
struct ContentView: View {
var body: some View {
VStack {
Spacer()
DurationPickers()
}
.frame(width: 480, height: 600)
}
}
PlaygroundPage.current.setLiveView(ContentView())
The result of the code is this:
This looks exactly as I expect and want. However, when I try to select the hours for example, I have to scroll from the left half of the red area, otherwise minutes would start scrolling. The same happens if you scroll on minutes from the right half of the orange rectangle.
Any idea why it's behaving like this? Any suggestions to fix this?
In case someone else is having a similar issue, I just found a solution posted on Apple's forums two weeks ago.
Previously, this behaviour was fixed by adding:
.compositingGroup().clipped()
As suggested in some of Asperi's answers. However, this solution have stopped working (probably since the release of iOS 15.1, but maybe later).
The solution that works now on iOS 15.4 is adding the following extension:
extension UIPickerView {
open override var intrinsicContentSize: CGSize {
return CGSize(width: UIView.noIntrinsicMetric, height: super.intrinsicContentSize.height)
}
}
Not really sure what exactly is this doing and whether it might introduce other issues, but I'll go with it for now and keep testing!
Related
I am using the LazyVGrid in my app and a faced such problem
There are two columns in the grid and that is handling the tap in the wrong place
On the screenshot, red arrows - places of the click
I used to think that would open the MacBookAir's card, but that opens the iPadAir's one
struct GalleryView: View {
var ns: Namespace.ID
#EnvironmentObject private var model: Model
let columns = [
GridItem(.adaptive(minimum: 400))
]
var body: some View {
LazyVGrid(columns: columns, spacing: 20) {
ForEach(Card.list) { card in
if card != model.selectedCard {
GalleryCardView(card: card, ns: ns)
.onTapGesture {
withAnimation(.openCard) {
model.selectedCard = card
model.isDetailShown = true
}
}
} else {
Color(.clear)
.frame(width: 400, height: 400)
}
}
}
}
}
Here is the GalleryCardView:
struct GalleryCardView: View {
let card: Card
var ns: Namespace.ID
var body: some View {
ZStack {
Image(card.image)
.resizable()
.aspectRatio(contentMode: .fill)
.frame(width: 400, height: 400)
.clipped()
.matchedGeometryEffect(id: "\(card.id)-image", in: ns)
.overlay(alignment: .bottom) {
Text(card.title)
.font(.largeTitle)
.fontWeight(.bold)
.frame(maxWidth: .infinity)
.frame(height: 50)
.padding()
.background {
Rectangle()
.fill(.ultraThinMaterial)
}
.matchedGeometryEffect(id: "\(card.id)-title", in: ns)
}
}
.clipShape(RoundedRectangle(cornerRadius: 30))
.matchedGeometryEffect(id: "\(card.id)-card", in: ns)
}
}
UPD:
I had some work around and find out that the problem is with the GalleryCardView - even though the image is clipped, it took extra space outside the frame. If I change the aspect ratio to .fit, everything works fine
I'm trying to create a scroll view with my custom view, but when I add scroll to view it's not working as expected, without scroll view working fine.
struct ContentView: View {
var body: some View {
ScrollView(.vertical) {
VStack {
ForEach (0..<2) { _ in
ListItem()
}
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
// But the below code is working fine.
struct ContentView: View {
var body: some View {
VStack {
ForEach (0..<2) { _ in
ListItem()
}
}
}
}
// List Item
struct ListItem: View {
var body: some View {
VStack {
HStack {
Image("steve")
.resizable()
.clipShape(Circle())
.aspectRatio(contentMode: .fit)
.frame(maxWidth:44, maxHeight: 44)
VStack {
Text("Steve Jobs")
.font(.headline)
.frame(maxWidth: .infinity, alignment: .leading)
Text("1 hour ago")
.font(.footnote)
.frame(maxWidth: .infinity, alignment: .leading)
}
Spacer()
}
ZStack(alignment:.top) {
GeometryReader { geometry in
VStack {
ZStack {
Image("poster_1")
.resizable()
.aspectRatio(contentMode: .fit)
.cornerRadius(8)
.shadow(color: Color.black.opacity(0.12),
radius: 4, x: 1, y: 1)
.frame(width: geometry.size.width - 64,
height: geometry.size.height * 0.35)
.padding([.horizontal], 32)
.clipped()
ZStack {
Rectangle()
.fill(Color.black.opacity(0.75))
.frame(maxWidth:84 , maxHeight: 84)
.cornerRadius(12)
Image(systemName: "play.fill")
.font(.system(size: 44, weight: .bold))
.foregroundColor(.white)
}
}
VStack {
Text("Game of Thrones")
.accentColor(Color.gray.opacity(0.25))
.font(Font.subheadline.weight(.bold))
.padding([.horizontal], 32)
.padding([.bottom], 2)
.frame(maxWidth: .infinity,
alignment: .leading)
VStack {
Text("Game of Thrones is an American fantasy drama television series created by David Benioff and D. B. Weiss for HBO. ")
.accentColor(Color.gray.opacity(0.25))
.font(.footnote)
.frame(maxWidth: .infinity,
alignment: .leading)
.padding([.horizontal], 32)
Text("Show more...")
.accentColor(Color.gray.opacity(0.01))
.font(Font.footnote.weight(.bold))
.frame(maxWidth: .infinity,
alignment: .trailing)
.padding([.trailing], 32).onTapGesture {
print("okay")
}
}
}
}
}
}
}
}
}
ListItem contains multiple views which creates publisher info and movie information as shown below image.
Scrollview is scrolling but images are not shown in view as first image.
It is the geometry reader that you have in ListItem. Because neither a GeometryReader nor a Scrollview have their own size. Since neither no what size to render, they collapse. This is what you are seeing in your view. See this answer. The solution is to put the GeometryReader into ContentView outside the Scrollview and send the GeometryProxy that you called geometry into ListItem something like this:
struct ContentView: View {
var body: some View {
GeometryReader { geometry in
ScrollView(.vertical) {
VStack {
ForEach (0..<2) { _ in
ListItem(geometry: geometry)
}
}
} // Scrollview
} // GeometryReader
}
}
struct ListItem: View {
let geometry: GeometryProxy
var body: some View {
...
}
This seems to fix it in Preview, though you may have to change your multipliers in the .frame() that uses geometry to size it how you want.
I have a simple Rectangle which I put a VStack of Numbers in background of that, you can see it in code and pic! the problem is the VStack is taller than Rectangle there for it goes outside of Rectangle, I want the extra part be hidden, How could I solve it?
struct ContentView: View {
let arrayOfHours: [Int] = Array(0...23)
var body: some View {
Rectangle()
.fill(Color.black.opacity(0.25))
.frame(height: 200, alignment: .center)
.padding()
.background(
VStack(alignment: .center, spacing: 4) {
ForEach (arrayOfHours.indices, id: \.self) { index in
Text(arrayOfHours[index].description)
.font(Font.body.bold())
}
}
.background(Color.yellow)
)
}
}
If I understood your description correctly you need clipped, like
struct ContentView: View {
let arrayOfHours: [Int] = Array(0...23)
var body: some View {
Rectangle()
.fill(Color.black.opacity(0.25))
.frame(height: 200, alignment: .center)
.background(
VStack(alignment: .center, spacing: 4) {
ForEach (arrayOfHours.indices, id: \.self) { index in
Text(arrayOfHours[index].description)
.font(Font.body.bold())
}
}
.background(Color.yellow)
)
.clipped() // << here !!
.padding() // should be moved here !!
}
}
i have an image that i want to strech only it's height to fit different content, how do i do that in swiftUI? right now it looks like this
struct ContentView: View {
var body: some View {
VStack {
HStack {
VStack(alignment: .leading, spacing: 130) {
Text("Title")
.font(.headline)
.foregroundColor(.primary)
Text("text")
.font(.subheadline)
.foregroundColor(.secondary)
Text("padding")
}
.padding(.vertical)
Spacer()
Image("rightTag")
.resizable(capInsets: .init(top: 0, leading: 0, bottom: 0, trailing: 0), resizingMode: .stretch)
.aspectRatio(contentMode: .fit)
.frame(maxWidth: 20)
}
.frame(maxWidth: screen.width - 60)
.padding(.leading)
.background(.white)
.cornerRadius(20)
}
}
}
how can i stretch its height to fit this outer frame? ragular resizable and stuff can't get it done.
any helped would be wonderful! Thanks!
sry i didn't make myself clear earllier.
Here is possible approach. However as I see now you'd rather need to stretch not the entire original image, but only middle of it, so in real project it would be needed to make your image tri-part and apply below stretching approach only to middle (square) part.
Approach uses asynchronous state update, so works in Live Preview / Simulator / RealDevice. (Tested with Xcode 11.2 / iOS 13.2)
struct ContentView: View {
#State private var textHeigh: CGFloat = .zero
var body: some View {
VStack {
HStack(alignment: .top) {
VStack(alignment: .leading, spacing: 130) {
Text("Title")
.font(.headline)
.foregroundColor(.primary)
Text("text")
.font(.subheadline)
.foregroundColor(.secondary)
Text("padding")
}
.padding(.vertical)
.alignmentGuide(.top, computeValue: { d in
DispatchQueue.main.async {
self.textHeigh = d.height // !! detectable height of left side text
}
return d[.top]
})
Spacer()
Image("rightTag")
.resizable()
.frame(maxWidth: 20, maxHeight: max(60, textHeigh)) // 60 just for default
}
.frame(maxWidth: screen.width - 60)
.padding(.leading)
.background(Color.white)
}
}
}
Newbie here! I am building a quiz app using Swiftui, I built the view controller by previewing it in an iPhone 11 simulator.
And I thought the controlview would fit other iPhone sizes, like iPhone 8. Because Swiftui has a built-in auto layout.
But when I run the iPhone 8 simulator some of the content in the control view is not visible because they are below the screen.
Is there a way to fix it?
I tried to play with multiple Spacer() and different paddings but I can't seem to make it look good on both screen at the same time.
This is my code:
import SwiftUI
struct questionOne: View {
#State var totalClicked: Int = 0
#State var showDetails = false
#State var isSelected = false
var body: some View {
VStack {
TRpic().frame(width: 350.0, height: 233.0).cornerRadius(10).padding(.top, 80)
Spacer()
Text(verbatim: "What's the capital of Turkey?")
.font(.title)
.padding(.bottom, 60)
.frame(height: 100.0)
Button(action: {}) {
Text("Istanbul")
}.buttonStyle(MyButtonStyle())
Spacer()
Button(action: {self.isSelected.toggle()}) {
Text("Ankara")
}.buttonStyle(SelectedButtonStyle(isSelected: $isSelected))
Spacer()
Button(action: {}) {
Text("Athens")
} .buttonStyle(MyButtonStyle())
Spacer()
NavigationLink(destination: questionTwo()) {
VStack {
Text("Next Question")
Adview().frame(width: 150, height: 60)
}
}
}.edgesIgnoringSafeArea(.top)
}
}
struct MyButtonStyle: ButtonStyle {
func makeBody(configuration:
Self.Configuration) -> some View {
configuration.label
.padding(20)
.foregroundColor(.white)
.background(configuration.isPressed ? Color.red : Color.gray)
.cornerRadius(10.0)
}
}
struct SelectedButtonStyle: ButtonStyle {
#Binding var isSelected: Bool
public func makeBody(configuration: Self.Configuration) -> some View {
configuration.label
.padding(20)
.foregroundColor(.white)
.background(isSelected ? Color.green : Color.gray)
.cornerRadius(10.0)
}
}
enter image description here
Screenshot
Being in the given context I guess you do not want a scroll view, so regarding spacing I suggest using a VStack with spacing parameter VStack(alignment: .center, spacing: n){ ... } and remove the Spacers, if between 2 views you need another distance than n, just use padding to add some extra space.
This should adjust everything to fit the height of any screen, including the image, so do not need a fixed frame for it.
But, you might have a very wide image that could go beyond safe area, so, you could set a maximum width for the image as being the screen width
struct questionOne: View {
var body: some View {
GeometryReader { geometryProxy in
VStack(alignment: .center, spacing: 20) {
TRpic().frame(maxWidth: geometryProxy.size.width, alignment: .center)
.padding([.leading, .trailing], 10)
.......
}
}
}