I have been designing this view according to this YouTube video https://youtu.be/ytRim2TSdyY?list=PLyJ4OqMEvBQwxJGWIdV6y0VbeKVGkDbCT, with some little difference :
Below is the code i have write this is somewhat different than code written in youtube adding the code for reference
struct allArticlesListView: View {
@State var title = "Sports"
@Environment(\.colorScheme) var colorScheme
@State var isGotData = false
// let fetchData = FetchNews()
@State var articles : [Article] = []
var body: some View {
if isGotData {
ScrollView(.vertical){
VStack(spacing : 0){
VStack(alignment: .leading,spacing: 15) {
HStack{
Text("TOP NEWS")
.font(.largeTitle.bold())
.frame(height: 45)
.padding(.horizontal,15)
}
GeometryReader{
let rect = $0.frame(in: .scrollView(axis : .vertical))
let minY = rect.minY
let topValue : CGFloat = 75.0
let offset = min(minY - topValue, 0)
let progress = max(min( -offset / topValue, 1), 0)
let scale = 1 + progress
ZStack{
Rectangle()
.fill(Color.blue)
.overlay(alignment: .leading) {
Circle()
.fill(Color.blue)
.overlay {
Circle()
.fill(.white.opacity(0.2))
}
.scaleEffect(2,anchor: .topLeading)
.offset(x: -50,y: -40)
}
.clipShape(RoundedRectangle(cornerRadius: 25,style: .continuous))
.scaleEffect(scale,anchor: .bottom)
VStack(alignment: .center, spacing: 4){
Text("\(title)")
.font(.title.bold())
}
.foregroundStyle(.white)
.frame(maxWidth: .infinity, alignment : .leading)
.padding(15)
.offset(y : progress * -25 )
}
.offset(y : -offset)
//Moving till top value
.offset(y : progress * -topValue)
}
.padding(.horizontal,15)
.containerRelativeFrame(.horizontal)
.frame(height: 125)
LazyVStack(spacing: 15) {
ForEach(articles, id: \.self) { article in
NavigationLink(destination: detailedArticleView(singleArticle: article)) {
VStack(alignment: .leading) {
ArticleView(article: article, category: title)
}
}
}
}
.padding(15)
.mask{
Rectangle()
.visualEffect { content, geometryProxy in
content
.offset(y : backgroundLimitOffset(geometryProxy))
}
}
.background{
GeometryReader {
let rect = $0.frame(in: .scrollView)
let minY = min(rect.minY - 125, 0)
let progress = max(min(-minY / 25, 1), 0)
RoundedRectangle(cornerRadius: 30 * progress,style: .continuous)
.fill(colorScheme == .dark ? .black : .white)
.overlay(alignment: .top){
}
/// Limiting Background Scroll below the header
.visualEffect { content, geometryProxy in
content
.offset(y : backgroundLimitOffset(geometryProxy))
}
}
}
}
.padding(.vertical,15)
}
}
.scrollTargetBehavior(CustomScrollBehaviourSample())
.scrollIndicators(.hidden)
.background {
LinearGradient(colors: [.green, .cyan, .cyan ,.indigo], startPoint: .topLeading, endPoint: .bottomTrailing)
.ignoresSafeArea()
}
}else{
ProgressView()
.onAppear {
// fetchData.fetchData(category: title) { allArticles in
// self.articles = allArticles
// print("fetched the data")
// isGotData = true
// }
fetch()
}
}
}
/// Background Limit Offset
func backgroundLimitOffset(_ proxy : GeometryProxy) -> CGFloat {
let minY = proxy.frame(in: .scrollView).minY
return minY < 75 ? -minY + 75 : 0
}
func fetch(){
if let filePath = Bundle.main.url(forResource: "dummy.json", withExtension: ""){
if let data = try? Data(contentsOf: filePath){
if let content = try? JSONDecoder().decode(Welcome.self, from: data){
articles = content.articles
isGotData = true
}else{
print("error in data decoding")
}
}else{
print("error in data fetching")
}
}else{
print("error in file path")
}
}
}
/// Custom Scroll Target Behaviour
/// AKA scrollWillEndDragging in UIKit
struct CustomScrollBehaviourSample : ScrollTargetBehavior {
func updateTarget(_ target: inout ScrollTarget, context: TargetContext) {
if target.rect.minY < 75 {
target.rect = .zero
}
}
}
I managed to reproduce this problem by taking your code and replacing the articles with plain colors. I saw from your screenshot that there was a "Back" button, so I tried using allArticlesListView
as the destination for a NavigationLink
.
The problem is seen if the parent view (with the NavigationStack
) has a large title. The reason is because, space is being reserved for the title at the top of the detail view.
To fix, apply .navigationBarTitleDisplayMode(.inline)
to the detail view. It's probably a good idea to clear the navigation title at the same time:
NavigationStack {
NavigationLink("Show list") {
allArticlesListView()
.navigationTitle("")
.navigationBarTitleDisplayMode(.inline)
}
.navigationBarTitleDisplayMode(.large)
}
Alternatively, you can move these modifiers into the body
of the view itself (put them at the end).
BTW, normal convention is for the names of structs and classes to begin with uppercase. So I would suggest changing it to AllArticlesListView
.