I have encountered this problem during the Swift programming. I have this long frame that I created, here it is.
The main idea that this frame will be used in a horizontal Scroll View in different view, like this. It will be opening different view on tap.
Here's the catch. If we want to transition to different view, we need NavigationLink. In order to work NavigationLink needs NavigationView. When we add our LongFrame in NavigationView, this happens
If we tap on it, it will display View, but in small frame
And If we, for example, add our LongFrameScrollView somewhere, It won't even show up sometimes
I will provide the code here. My guess that should be connected to .frame, but without this line of code I can't create this frame(.
// FRAME ITSELF
import SwiftUI
struct LongFrameView: View {
var body: some View {
NavigationView {
NavigationLink {
PlayerView()
} label: {
ZStack {
Rectangle()
.fill(LinearGradient(gradient: Gradient(colors: [Color(red: 0.268, green: 0.376, blue: 0.587), Color(red: 0.139, green: 0.267, blue: 0.517)]),
startPoint: .leading,
endPoint: .trailing))
.frame(width: 310, height: 62)
.cornerRadius(8)
HStack {
Image("mountains")
.resizable()
.aspectRatio(contentMode: .fill)
.frame(width: 70, height: 62)
.cornerRadius(8, corners: [.topLeft, .bottomLeft])
VStack(alignment: .leading) {
Text("Sense of anxiety")
.font(.custom("Manrope-Bold", size: 14))
.foregroundColor(.white)
Text("11 MIN")
.font(.custom("Manrope-Medium", size: 12))
.foregroundColor(.white)
}
Spacer()
}
}
.frame(width: 310, height: 62)
}
}
}
}
struct LongFrameView_Previews: PreviewProvider {
static var previews: some View {
LongFrameView()
}
}
// MARK: - WITH THIS CODE WE CAN DEFINE WHERE CORNER RADIUS WILL BE CHANGED OR NOT. DO NOT MODIFY
extension View {
func cornerRadius(_ radius: CGFloat, corners: UIRectCorner) -> some View {
clipShape( RoundedCorner(radius: radius, corners: corners) )
}
}
struct RoundedCorner: Shape {
var radius: CGFloat = .infinity
var corners: UIRectCorner = .allCorners
func path(in rect: CGRect) -> Path {
let path = UIBezierPath(roundedRect: rect, byRoundingCorners: corners, cornerRadii: CGSize(width: radius, height: radius))
return Path(path.cgPath)
}
}
// SCROLL VIEW WITH FRAMES
import SwiftUI
struct LongFrameScrollView: View {
let rows = Array(repeating: GridItem(.fixed(60), spacing: 10, alignment: .leading), count: 2)
var body: some View {
ScrollView(.horizontal, showsIndicators: false) {
LazyHGrid(rows: rows, spacing: 10) {
// PLACEHOLDER UNTIL API IS READY
LongFrameView()
LongFrameView()
LongFrameView()
LongFrameView()
}
}
.padding([.horizontal, .bottom], 10)
}
}
struct LongFrameScrollView_Previews: PreviewProvider {
static var previews: some View {
LongFrameScrollView()
}
}
NavigationView should be added as the first/top view. So embed your ScrollView with NavigationView inside LongFrameScrollView and removed it from LongFrameView. Inside LongFrameView you just need NavigationLink.
NavigationView {
ScrollView(.horizontal, showsIndicators: false) {
LazyHGrid(rows: rows, spacing: 10) {
// PLACEHOLDER UNTIL API IS READY
LongFrameView()
LongFrameView()
LongFrameView()
LongFrameView()
}
}
.padding([.horizontal, .bottom], 10)
}
Related
I've got a simple HStack with subviews inside. How can I tell the first subview to be 60% the size of the HStack without using a GeometryReader?
struct ContentView: View {
var body: some View {
HStack {
Color.red.opacity(0.3)
Color.brown.opacity(0.4)
Color.yellow.opacity(0.6)
}
}
}
The code above makes each subview the same size. But I want the first one to be 60% regardless of it's content. In this example, it is a color, but it could be anything.
The HStack is dynamic in size.
Edit: Why no GeometryReader?
When I want to place multiple of those HStacks inside a ScrollView, they overlap, because the GeometryReader's height is only 10 Point. As mentioned above, the Color views could be anything, so I used VStacks with cells in it that have dynamic heights.
struct ContentView: View {
var body: some View {
ScrollView(.vertical) {
ProblematicView()
ProblematicView()
}
}
}
struct ProblematicView: View {
var body: some View {
GeometryReader { geo in
HStack(alignment: .top) {
VStack {
Rectangle().frame(height: 20)
Rectangle().frame(height: 30)
Rectangle().frame(height: 20)
Rectangle().frame(height: 40)
Rectangle().frame(height: 20)
}
.foregroundColor(.red.opacity(0.3))
.frame(width: geo.size.width * 0.6)
.overlay(Text("60%").font(.largeTitle))
VStack {
Rectangle().frame(height: 10)
Rectangle().frame(height: 30)
Rectangle().frame(height: 20)
}
.foregroundColor(.brown.opacity(0.4))
.overlay(Text("20%").font(.largeTitle))
VStack {
Rectangle().frame(height: 5)
Rectangle().frame(height: 10)
Rectangle().frame(height: 24)
Rectangle().frame(height: 10)
Rectangle().frame(height: 17)
Rectangle().frame(height: 13)
Rectangle().frame(height: 10)
}
.foregroundColor(.yellow.opacity(0.6))
.overlay(Text("20%").font(.largeTitle))
}
}
.border(.blue, width: 3.0)
}
}
As you can see, the GeometryReader's frame is too small in height. It should be as high as the HStack. That causes the views to overlap.
I don't know the exact reason (might be a bug in GeometryReader), but placing the GeometryReader outside the ScrollView, and passing down its width makes your code behave as you expect.
struct ContentView: View {
var body: some View {
GeometryReader { geo in
ScrollView {
ProblematicView(geoWidth: geo.size.width)
ProblematicView(geoWidth: geo.size.width)
}
}
.border(.blue, width: 3.0)
}
}
struct ProblematicView: View {
let geoWidth: CGFloat
var body: some View {
// same code, but using geoWidth to compute the relative width
Result:
You can set by .frame & UIScreen.main.bounds.size.width * (your width ratio) calculation.
Example
struct ContentView: View {
var body: some View {
HStack {
Color.red.opacity(0.3)
.frame(width: UIScreen.main.bounds.size.width * 0.6, height: nil)
Color.purple.opacity(0.4)
.frame(width: UIScreen.main.bounds.size.width * 0.2, height: nil)
Color.yellow.opacity(0.6)
.frame(width: UIScreen.main.bounds.size.width * 0.2, height: nil)
}
}
}
Using GeometryReader
struct ContentView: View {
var body: some View {
GeometryReader { geo in
HStack {
Color.red.opacity(0.3)
.frame(width: geo.size.width * 0.6, height: nil)
Color.brown.opacity(0.4)
.frame(width: geo.size.width * 0.2, height: nil)
Color.yellow.opacity(0.6)
.frame(width: geo.size.width * 0.2, height: nil)
}
}
}
}
I need to create a list view with item like a square on a scrollview.
But I need to make the scroll only work to one item to another.
I could see the article allowing to set up this system on a HStack but I can't adapt it on a VStack
This article : https://levelup.gitconnected.com/snap-to-item-scrolling-debccdcbb22f
If someone has an idea to help me in this research?
I join an example of scrollview with the square :
struct ScrollingSnapped: View {
var colors: [Color] = [.blue, .green, .red, .orange, .gray, .brown, .yellow, .purple]
var body: some View {
ScrollView {
VStack(alignment: .trailing, spacing: 30) {
ForEach(0..<colors.count) { index in
colors[index]
.frame(width: 250, height: 250, alignment: .center)
.cornerRadius(10)
}
}
}
}
}
struct ScrollingSnapped_Previews: PreviewProvider {
static var previews: some View {
ScrollingSnapped()
}
}
I tried to work with a ScrollViewReader that allows to scrollTo. It works when clicking on a button but I can't get it to work when scrolling :
import SwiftUI
struct ScrollingSnapped: View {
var colors: [Color] = [.blue, .green, .red, .orange, .gray, .brown, .yellow, .purple]
var body: some View {
ScrollViewReader { proxy in
ScrollView {
Button {
proxy.scrollTo(4, anchor: .top)
} label: {
Text("Scroll to square 5")
}
VStack(alignment: .trailing, spacing: 30) {
ForEach(0..<colors.count) { index in
colors[index]
.frame(width: 250, height: 250, alignment: .center)
.cornerRadius(10)
.id(index)
}
}
}
}
}
}
struct ScrollingSnapped_Previews: PreviewProvider {
static var previews: some View {
ScrollingSnapped()
}
}
I am developing an App that supports multiple Profiles. I really like the way Apple displays the Profile Icon next to the Large Navigation Bar Title in all their Apps. See the Screenshot below:
My Question is the following:
Is it possible to achieve this in SwiftUI? And if so, how?
If it's not possible in pure SwiftUI, how can I achieve it including UIKit Code?
Thanks for your help.
I solved this by using SwiftUI-Introspect, to "Introspect underlying UIKit components from SwiftUI".
Here is an example of a view:
struct ContentView: View {
#State private var lastHostingView: UIView!
var body: some View {
NavigationView {
ScrollView {
ForEach(1 ... 50, id: \.self) { index in
Text("Index: \(index)")
}
.frame(maxWidth: .infinity)
}
.navigationTitle("Large title")
.introspectNavigationController { navController in
let bar = navController.navigationBar
let hosting = UIHostingController(rootView: BarContent())
guard let hostingView = hosting.view else { return }
// bar.addSubview(hostingView) // <--- OPTION 1
// bar.subviews.first(where: \.clipsToBounds)?.addSubview(hostingView) // <--- OPTION 2
hostingView.backgroundColor = .clear
lastHostingView?.removeFromSuperview()
lastHostingView = hostingView
hostingView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
hostingView.trailingAnchor.constraint(equalTo: bar.trailingAnchor),
hostingView.bottomAnchor.constraint(equalTo: bar.bottomAnchor, constant: -8)
])
}
}
}
}
Bar content & profile picture views:
struct BarContent: View {
var body: some View {
Button {
print("Profile tapped")
} label: {
ProfilePicture()
}
}
}
struct ProfilePicture: View {
var body: some View {
Circle()
.fill(
LinearGradient(
gradient: Gradient(colors: [.red, .blue]),
startPoint: .topLeading,
endPoint: .bottomTrailing
)
)
.frame(width: 40, height: 40)
.padding(.horizontal)
}
}
The .frame(width: 40, height: 40) & hostingView.bottomAnchor constant will need to be adjusted to your needs.
And the results for each option (commented in the code):
Option 1
Option 2
View sticks when scrolled
View disappearing underneath on scroll
Without NavigationView
I done this with pure SwiftUI. You have to replace the Image("Profile") line with your own image (maybe from Assets or from base64 data with UIImage).
HStack {
Text("Apps")
.font(.largeTitle)
.fontWeight(.bold)
Spacer()
Image("Profile")
.resizable()
.scaledToFit()
.frame(width: 40, height: 40)
.clipShape(Circle())
}
.padding(.all, 30)
This products following result:
With NavigationView
Let's assume that you have NavigationView and inside that there's only ScrollView and .navigationTitle. You can add that profile image there by using overlay.
NavigationView {
ScrollView {
//your content here
}
.overlay(
ProfileView()
.padding(.trailing, 20)
.offset(x: 0, y: -50)
, alignment: .topTrailing)
.navigationTitle(Text("Apps"))
}
Where ProfileView could be something like this:
struct ProfileView: View {
var body: some View {
Image("Profile")
.resizable()
.scaledToFit()
.frame(width: 40, height: 40)
.clipShape(Circle())
}
}
The result will be like this...
...which is pretty close to the App Store:
As the title explained, I need make the shadow of subview bleeding out from parent bounds.
We can see the shadow is clipped by the container (parent view). How to ignore it?
ScrollView(axis, showsIndicators: false) {
HStack{
ForEach(d_keys.indices) { index -> DownloadOptionView in
let d_key = d_keys[index]
let d_info = asset.downloads[d_key]!
return DownloadOptionView(d_key: d_key, d_info: d_info)
}.padding(.vertical, 10) // ForEach
}
}.frame(minHeight:130)
// View structure for DownloadOptionView, modified, may not compile
struct DownloadOptionView: View {
let d_key: String
let d_info: DownloadInfo
// some #ObservedObject ...........
// some #State ...........
var body: some View {
return NavigationLink(destination: SceneKitCanvas(textureMap: textureMap), isActive: self.$present) {
Button(action: {
// Download / storage / animation
}) {
ZStack{
LinearGradient(gradient: Gradient(colors: [Neumorphism.background, Neumorphism.light]),
startPoint: .topLeading, endPoint: .bottomTrailing)
Color.green.scaleEffect(x: 1.0, y: CGFloat(scale), anchor: .bottom) // progress bar
Color.green.scaleEffect(x: 1.0, y: CGFloat(increased), anchor: lineAnchor)
VStack(alignment: .leading, spacing: 5) {
HStack(alignment: .center) {
Image(systemName: imageName).aspectRatio(contentMode: .fit)
Text(file_type).fontWeight(.light)
}.padding(.bottom, 5)
Text(d_key).font(.footnote).fontWeight(.light)
Text(size_final).font(.footnote).fontWeight(.light)
}.padding() // VStack
} // ZStack
}
.cornerRadius(20)
.shadow(color: Color.gray, radius: 3, x: 3, y: 3)
.shadow(color: Color.white, radius: 3, x: -3, y: -3)
}
}
}
I have done some guesswork about how you created DownloadOptionView but the answer can be used in your context as it's a general technique.
Where you are creating DownloadOptionView you need to add the .blendMode(.overlay) modifier:
struct DownloadOptionView: Shape {
func path(in rect: CGRect) -> Path {
return RoundedRectangle(cornerRadius: 8, style: .continuous).path(in: rect)
}
var body: some View {
RoundedRectangle(cornerSize: CGSize(width: 8, height: 8))
.frame(minWidth: 200, minHeight: 200)
.shadow(radius: 10)
.blendMode(.overlay) //Add here.
}
}
Using SwiftUI, I am trying to center a View on the screen and then give it a header and/or footer of variable heights.
Using constraints it would look something like this:
let view = ...
let header = ...
let footer = ...
view.centerInParent()
header.pinBottomToTop(of: view)
footer.pinTopToBottom(of: view)
This way, the view would always be centered on the screen, regardless of the size of the header and footer.
I cannot figure out how to accomplish this with SwiftUI. Using any type of HStack or VStack means the sizes of the header and footer push around the view. I would like to avoid hardcoding any heights since the center view may vary in size as well.
Any ideas? New to SwiftUI so advice is appreciated!
If I correctly understood your goal (because, as #nayem commented, at first time seems I missed), the following approach should be helpful.
Code snapshot:
extension VerticalAlignment {
private enum CenteredMiddleView: AlignmentID {
static func defaultValue(in dimensions: ViewDimensions) -> CGFloat {
return dimensions[VerticalAlignment.center]
}
}
static let centeredMiddleView = VerticalAlignment(CenteredMiddleView.self)
}
extension Alignment {
static let centeredView = Alignment(horizontal: HorizontalAlignment.center,
vertical: VerticalAlignment.centeredMiddleView)
}
struct TestHeaderFooter: View {
var body: some View {
ZStack(alignment: .centeredView) {
Rectangle().fill(Color.clear) // !! Extends ZStack to full screen
VStack {
Header()
Text("I'm on center")
.alignmentGuide(.centeredMiddleView) {
$0[VerticalAlignment.center]
}
Footer()
}
}
// .edgesIgnoringSafeArea(.top) // uncomment if needed
}
}
struct Header: View {
var body: some View {
Rectangle()
.fill(Color.blue)
.frame(height: 40)
}
}
struct Footer: View {
var body: some View {
Rectangle()
.fill(Color.green)
.frame(height: 200)
}
}
struct SwiftUIView_Previews: PreviewProvider {
static var previews: some View {
TestHeaderFooter()
}
}
Here's the code:
struct ContentView: View {
var body: some View {
GeometryReader { geometry in
VStack(alignment: .leading) {
Rectangle()
.fill(Color.gray)
.frame(width: geometry.size.width, height: geometry.size.height * 0.1, alignment: .center)
Text("Center")
.frame(width: geometry.size.width, height: geometry.size.height * 0.2, alignment: .center)
Rectangle()
.fill(Color.gray)
.frame(width: geometry.size.width, height: geometry.size.height * 0.1, alignment: .center)
}
}
}
}
using GeometryReader you can apply the dynamic size for your views.
also here is screenshot for above code
put Spacer() between header view and footer view.
headerview()
Spacer()
footerview()