I have a question about views in swiftui.
I have created a file CircleView which shows me a circle and a text in the middle. Additionally I have created a list in ContentView and a DetailView for details.
I now use the created circle in the list and on the detail view. Unfortunately the size of the circle is defined in the CircleView and I don't know how to change it individually so that it is displayed small in the list and uses the full width on the detail view.
So how can I change the size of a view individually?
Here is my code
CircleView:
struct CircleView: View {
var body: some View {
ZStack{
Circle()
.fill(Color.blue)
.frame(width: 250, height: 250)
Text("VIEW")
.frame(width: 100, height: 100)
.font(.largeTitle)
.foregroundColor(.white)
}
}
}
ContentView:
struct ContentView: View {
var body: some View {
NavigationView {
List{
NavigationLink(destination: DetailsView()) {
HStack{
Text("Hello")
Spacer()
CircleView()
}
}.navigationTitle("Home")
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
DetailsView:
struct DetailsView: View {
var body: some View {
VStack{
Text("Hello, World!")
.padding(10)
CircleView()
}
}
}
You should remove the set frame from your view and apply it when using your view.
Your code edited:
struct Sample: View {
var body: some View {
NavigationView {
List{
NavigationLink(destination: DetailsView()) {
HStack{
Text("Hello")
Spacer()
CircleView()
.frame(width: 100 , height: 100) // <-- Update frame here
}
}.navigationTitle("Home")
}
}
}
}
struct CircleView: View {
var body: some View {
ZStack{
Circle()
.fill(Color.blue)
Text("VIEW")
.font(.largeTitle)
.foregroundColor(.white)
}
}
}
struct Sample_Previews: PreviewProvider {
static var previews: some View {
Sample()
}
}
struct DetailsView: View {
var body: some View {
VStack{
Text("Hello, World!")
.padding(10)
CircleView().padding()
}
}
}
Related
I want to customize the navigation bar, but the refresh control is covered by the custom navigation bar after hiding the navigation. How can I adjust the position of the refresh control?
import SwiftUI
struct ContentView: View {
var body: some View {
NavigationView {
HomeNavigationBarView {
List {
NavigationLink {
EmptyView()
} label: {
Text("Hello, world!")
}
}
.listStyle(.insetGrouped)
.refreshable {
}
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Here is my custom navbar
struct HomeNavigationBarView<Content: View>: View {
let content: () -> Content
var body: some View {
ZStack(alignment: .top) {
content()
HStack {
Image("logo")
.resizable()
.frame(width: 44, height: 44)
}
.frame(height: insetTop(), alignment: .bottom)
.frame(maxWidth: .infinity)
.background(.red.opacity(0.1))
}
.navigationBarHidden(true)
}
}
private func insetTop() -> CGFloat {
let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene
let window = windowScene?.windows.first
return window?.safeAreaInsets.top ?? 0 // 20或47
}
Padding should be there in List .padding(.top, insetTop()) to fix position of refresh control.
struct ContentView: View {
var body: some View {
NavigationView {
HomeNavigationBarView {
List {
NavigationLink {
EmptyView()
} label: {
Text("Hello, world!")
}
}
.padding(.top, insetTop())
.refreshable {
}
}
}
}
}
The way of adding safe area in iOS15+ perfectly solves this problem
List {
}
.safeAreaInset(edge: .top, spacing: 0) {
Rectangle()
.fill(.clear)
.frame(height: insetTop() + 44)
}
I have been working through the SwiftUI Scrumdigger tutorial (https://developer.apple.com/tutorials/app-dev-training/getting-started-with-scrumdinger) and have reached Step 7 of "Creating a Card View". Here is where my experience deviates from what is presented by the tutorial.
Here is the code both I and the tutorial are using:
import SwiftUI
struct CardView: View {
let scrum: DailyScrum
var body: some View {
VStack(alignment: .leading) {
Text(scrum.title)
.font(.headline)
Spacer()
HStack {
Label("\(scrum.attendees.count)", systemImage: "person.3")
}
}
}
}
struct CardView_Previews: PreviewProvider {
static var scrum = DailyScrum.sampleData[0]
static var previews: some View {
CardView(scrum: scrum)
.background(scrum.theme.mainColor)
.previewLayout(.fixed(width: 400, height: 60))
}
}
Here is what I see:
And here is what the tutorial displays:
I have tried moving the Spacer() from line 9 to line 13 but the issue is the same.
Can reproduce without the reliance on the external Scrum data using this:
import SwiftUI
struct CardView: View {
//let scrum: DailyScrum
var body: some View {
VStack(alignment: .leading) {
Text("Title")
.font(.headline)
Spacer()
HStack {
Label("3", systemImage: "person.3")
}
}
}
}
struct CardView_Previews: PreviewProvider {
//static var scrum = DailyScrum.sampleData[0]
static var previews: some View {
CardView()
.previewLayout(.fixed(width: 400, height: 60))
}
}
Can anyone advise as to what might be going wrong here?
I have the problem that my sheet is not working as it should. When using a second NavigationView inside the SecondView then the sheet works but it looks crazy (first screenshot). When I comment out the NavigationView in SecondView the app looks correct (second screenshot), but the sheet is only working once when I open it from the Add button in navigation bar. When I use the "open sheet"-button in SecondView then the sheet works correct.
Did I do something wrong with this:
.navigationBarItems(trailing: Button("Add") {
showSheet.toggle()
})
SecondView with NavigationView:
SecondView without NavigationView (how it should look like):
This is the code I'm using:
import SwiftUI
struct ContentView: View {
var body: some View {
NavigationView {
VStack {
Text("Start View")
.font(.title)
NavigationLink(destination: SecondView()) {
CustomButton()
}
}
}
}
}
struct CustomButton: View {
var body: some View {
HStack {
Spacer()
Text("Go To Second View")
.font(.headline)
.foregroundColor(.red)
.padding()
Spacer()
}
.background(Color.white)
.overlay(
RoundedRectangle(cornerRadius: 20)
.stroke(Color.red, lineWidth: 2)
)
.padding()
}
}
struct SecondView: View {
#State private var showSheet = false
var body: some View {
//NavigationView{
Text("Second View")
Button("open sheet", action: {
showSheet.toggle()
})
.navigationBarBackButtonHidden(true)
.navigationBarItems(trailing: Button("Add") {
showSheet.toggle()
})
.sheet(isPresented: $showSheet, content: {
SecondViewsSheet()
})
//}
}
}
struct SecondViewsSheet: View {
var body: some View {
Text("Sheet")
.navigationBarItems(trailing: Button("Test"){
})
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
I use Xcode 12.2 with an iPhone 12 Pro simulator.
My goal is to have custom modals present over an root view that is essentially a tabbed view. So, I wrapped the TabView in a ZStack and am using an ObservableOBject. But I don't feel I'm doing it the right way.
In my other file, I have the Custom modal "subviews" which has an enum, too, which I think is the right approach to take. But I cannot figure out how to dismiss a modal after it is visible.
It must be #EnvironmentObject, but I don't know what if anything to put in the scene delegate, etc. ("Hacking with Swift" is failing me here, although it's a great resource.)
My idea is that views from the tabbed view will have various buttons which present different modal views, populated later with data specific to say a user and set of fields for data entry.
Right now, I just want to understand how to present and dismiss them.
Here is my root view
import SwiftUI
struct ContentView: View {
#ObservedObject var modal = CustomModal()
var body: some View {
ZStack {
TabView {
ZStack {
Color.pink.opacity(0.2)
Button(action: {
withAnimation{
self.modal.visibleModal = VisibleModal.circle
}
}) {
Text("Circle").font(.headline)
}
.frame(width: 270, height: 64)
.background(Color.pink.opacity(0.5)).foregroundColor(.white)
.cornerRadius(12)
}
.tabItem{
VStack{
Image(systemName: "1.square.fill")
Text("One")
}
}.tag(1)
ZStack {
Color.blue.opacity(0.2)
Button(action: {
self.modal.visibleModal = VisibleModal.squircle
}) {
Text("Square").font(.headline)
}
.frame(width: 270, height: 64)
.background(Color.blue.opacity(0.5)).foregroundColor(.white)
.cornerRadius(12)
}
.tabItem{
VStack{
Image(systemName: "2.square.fill")
Text("Two")
}
}.tag(2)
}.accentColor(.purple)
VStack {
containedView()
}
}
}
func containedView() -> AnyView {
switch modal.visibleModal {
case .circle: return AnyView(CircleView())
case .squircle: return AnyView(SquircleView())
case .none: return AnyView(Text(""))
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
And here is my second file with the enum and "subview" modals
import SwiftUI
class CustomModal: ObservableObject {
#Published var visibleModal: VisibleModal = VisibleModal.none
}
enum VisibleModal {
case circle, squircle, none
}
struct CircleView: View {
var body: some View {
ZStack {
Color.pink.blur(radius: 0.4)
Circle().fill()
.frame(width: 300)
.foregroundColor(Color.white.opacity(0.75))
dismissButton()
}.edgesIgnoringSafeArea(.all)
}
}
struct SquircleView: View {
var body: some View {
ZStack{
Color.green.blur(radius: 0.4)
RoundedRectangle(cornerRadius: 48, style: .continuous)
.frame(width: 300, height: 300).foregroundColor(Color.white.opacity(0.75))
dismissButton()
}.edgesIgnoringSafeArea(.all)
}
}
struct dismissButton: View {
#ObservedObject var modal = CustomModal()
var body: some View {
VStack{
Spacer()
Button(action: {
self.modal.visibleModal = VisibleModal.none
}) {
Text("Dismiss").font(.headline)
}
.frame(width: 270, height: 64)
.background(Color.white.opacity(0.35)).foregroundColor(.white)
.cornerRadius(12)
.padding(.bottom, 44)
}
}
}
Are you just trying to pass your observable object to the new view?
func containedView() -> some View {
switch modal.visibleModal {
case .circle: return CircleView()
.environmentObject(self.modal)
case .squircle: return SquircleView()
.environmentObject(self.modal)
case .none: return Text("")
}
}
Unless I am misunderstanding the question.
Okay, after a lot of fiddling, it works.
Now my code is as follows.
Root view
struct ContentView: View {
#EnvironmentObject var isModalVisible: CustomModal
#ObservedObject var modal = CustomModal()
var body: some View {
ZStack {
TabView {
ZStack {
Color.pink.opacity(0.2)
Button(action: {
withAnimation{
self.isModalVisible.isModalVisible.toggle()
self.modal.currentModal = VisibleModal.circle
}
}) {
Text("Circle").font(.headline)
}
.frame(width: 270, height: 64)
.background(Color.pink.opacity(0.5)).foregroundColor(.white)
.cornerRadius(12)
}
.tabItem{
VStack{
Image(systemName: "1.square.fill")
Text("One")
}
}.tag(1)
ZStack {
Color.blue.opacity(0.2)
Button(action: {
self.isModalVisible.isModalVisible.toggle()
self.modal.currentModal = VisibleModal.squircle
}) {
Text("Square").font(.headline)
}
.frame(width: 270, height: 64)
.background(Color.blue.opacity(0.5)).foregroundColor(.white)
.cornerRadius(12)
}
.tabItem{
VStack{
Image(systemName: "2.square.fill")
Text("Two")
}
}.tag(2)
}.accentColor(.purple)
if self.isModalVisible.isModalVisible {
VStack {
containedView()
}
}
}
}
func containedView() -> AnyView {
switch modal.currentModal {
case .circle: return AnyView(CircleView())
case .squircle: return AnyView(SquircleView())
case .none: return AnyView(Text(""))
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView().environmentObject(CustomModal())
}
}
and the second file with the supporting views and classes and enums:
import SwiftUI
class CustomModal: ObservableObject {
#Published var isModalVisible = false
#Published var currentModal: VisibleModal = .none
}
enum VisibleModal {
case circle, squircle, none
}
struct CircleView: View {
#EnvironmentObject var env: CustomModal
var body: some View {
ZStack {
Color.pink.blur(radius: 0.4)
Circle().fill()
.frame(width: 300)
.foregroundColor(Color.white.opacity(0.75))
dismissButton()
}.edgesIgnoringSafeArea(.all)
}
}
struct SquircleView: View {
var body: some View {
ZStack{
Color.green.blur(radius: 0.4)
RoundedRectangle(cornerRadius: 48, style: .continuous)
.frame(width: 300, height: 300).foregroundColor(Color.white.opacity(0.75))
dismissButton()
}.edgesIgnoringSafeArea(.all)
}
}
struct dismissButton: View {
#EnvironmentObject var env: CustomModal
var body: some View {
VStack{
Spacer()
Button(action: {
self.env.isModalVisible.toggle()
print("TAPPED")
}) {
Text("Dismiss").font(.headline)
}
.frame(width: 270, height: 64)
.background(Color.white.opacity(0.35)).foregroundColor(.white)
.cornerRadius(12)
.padding(.bottom, 44)
}
}
}
It still can be refactored. I'm sure. I'd also be happy to hear any comments on how to improve it. But it seems to work.
NOTE: This code ContentView().environmentObject(CustomModal()) is put in the previewP{rovider code and in SceneDelegate.
I build a macOS app in swiftui
i try to create a listview where the first item is preselected. i tried it with the 'selected' state of the navigationLink but it didn't work.
Im pretty much clueless and hope you guys can help me.
The code for creating this list view looks like this.
//personList
struct PersonList: View {
var body: some View {
NavigationView
{
List(personData) { person in
NavigationLink(destination: PersonDetail(person: person))
{
PersonRow(person: person)
}
}.frame(minWidth: 300, maxWidth: 300)
}
}
}
(Other views at the bottom)
This is the normal View when i open the app.
When i click on an item its open like this. Thats the state i want as default opening state when i render this view.
The Code for this view looks like this:
//PersonRow
struct PersonRow: View {
//variables definied
var person: Person
var body: some View {
HStack
{
person.image.resizable().frame(width:50, height:50)
.cornerRadius(25)
.padding(5)
VStack (alignment: .leading)
{
Text(person.firstName + " " + person.lastName)
.fontWeight(.bold)
.padding(5)
Text(person.nickname)
.padding(5)
}
Spacer()
}
}
}
//personDetail
struct PersonDetail: View {
var person : Person
var body: some View {
VStack
{
HStack
{
VStack
{
CircleImage(image: person.image)
Text(person.firstName + " " + person.lastName)
.font(.title)
Text("Turtle Rock")
.font(.subheadline)
}
Spacer()
Text("Subtitle")
.font(.subheadline)
}
Spacer()
}
.padding()
}
}
Thanks in advance!
working example. See how selection is initialized
import SwiftUI
struct Detail: View {
let i: Int
var body: some View {
Text("\(self.i)").font(.system(size: 150)).frame(maxWidth: .infinity)
}
}
struct ContentView: View {
#State var selection: Int?
var body: some View {
HStack(alignment: .top) {
NavigationView {
List {
ForEach(0 ..< 10) { (i) in
NavigationLink(destination: Detail(i: i), tag: i, selection: self.$selection) {
VStack {
Text("Row \(i)")
Divider()
}
}
}.onAppear {
if self.selection != nil {
self.selection = 0
}
}
}.frame(width: 100)
}
}.background(Color.init(NSColor.controlBackgroundColor))
}
}
screenshot
You can define a binding to the selected row and used a List reading this selection. You then initialise the selection to the first person in your person array.
Note that on macOS you do not use NavigationLink, instead you conditionally show the detail view with an if statement inside your NavigationView.
If person is not Identifiable you should add an id: \.self in the loop. This ressembles to:
struct PersonList: View {
#Binding var selectedPerson: Person?
var body: some View {
List(persons, id: \.self, selection: $selectedPerson) { person in // persons is an array of persons
PersonRow(person: person).tag(person)
}
}
}
Then in your main window:
struct ContentView: View {
// First cell will be highlighted and selected
#State private var selectedPerson: Person? = person[0]
var body: some View {
NavigationView {
PersonList(selectedPerson: $selectedPerson)
if selectedPerson != nil {
PersonDetail(person: person!)
}
}
}
}
Your struct person should be Hashable in order to be tagged in the list. If your type is simple enough, adding Hashable conformance should be sufficient:
struct Person: Hashable {
var name: String
// ...
}
There is a nice tutorial using the same principle here if you want a more complete example.
Thanks to this discussion, as a MacOS Beginner, I managed a very basic NavigationView with a list containing two NavigationLinks to choose between two views. I made it very basic to better understand. It might help other beginners.
At start up it will be the first view that will be displayed.
Just modify in ContentView.swift, self.selection = 0 by self.selection = 1 to start with the second view.
FirstView.swift
import SwiftUI
struct FirstView: View {
var body: some View {
Text("(1) Hello, I am the first view")
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
}
struct FirstView_Previews: PreviewProvider {
static var previews: some View {
FirstView()
}
}
SecondView.swift
import SwiftUI
struct SecondView: View {
var body: some View {
Text("(2) Hello, I am the second View")
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
}
struct SecondView_Previews: PreviewProvider {
static var previews: some View {
SecondView()
}
}
ContentView.swift
import SwiftUI
struct ContentView: View {
#State var selection: Int?
var body: some View {
HStack() {
NavigationView {
List () {
NavigationLink(destination: FirstView(), tag: 0, selection: self.$selection) {
Text("Click Me To Display The First View")
} // End Navigation Link
NavigationLink(destination: SecondView(), tag: 1, selection: self.$selection) {
Text("Click Me To Display The Second View")
} // End Navigation Link
} // End list
.frame(minWidth: 350, maxWidth: 350)
.onAppear {
self.selection = 0
}
} // End NavigationView
.listStyle(SidebarListStyle())
.frame(maxWidth: .infinity, maxHeight: .infinity)
} // End HStack
} // End some View
} // End ContentView
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Result:
import SwiftUI
struct User: Identifiable {
let id: Int
let name: String
}
struct ContentView: View {
#State private var users: [User] = (1...10).map { User(id: $0, name: "user \($0)")}
#State private var selection: User.ID?
var body: some View {
NavigationView {
List(users) { user in
NavigationLink(tag: user.id, selection: $selection) {
Text("\(user.name)'s DetailView")
} label: {
Text(user.name)
}
}
Text("Select one")
}
.onAppear {
if let selection = users.first?.ID {
self.selection = selection
}
}
}
}
You can use make the default selection using onAppear (see above).