I have two simple ui components a Rectangle and an Image.
I just want a slide animation for Rectangle and scale animation for Image.
However, I got a default value animation for both of these.
Is there a problem in my code? I don't have any error. I'm using beta SF Symbols tho. Could this be the problem?
import SwiftUI
struct ContentView: View {
#State var animate: Bool = false
var body: some View {
VStack(alignment: .center) {
HStack() {
if animate {
ZStack() {
Rectangle()
.frame(height: 50)
.transition(.slide)
Image(systemName: "figure.mixed.cardio")
.foregroundColor(.white)
.transition(.scale)
}
}
}
Button("animate") {
withAnimation {
animate.toggle()
}
}
}
}
}
Transition works when specific view appeared, but in your code ZStack loads views and then appears as a whole.
Here is a fix:
HStack() {
ZStack() {
if animate { // << here !!
Rectangle()
.frame(height: 50)
.transition(.slide)
Image(systemName: "figure.mixed.cardio")
.foregroundColor(.white)
.transition(.scale)
}
}
}
The problem here is neither your SF Symbols or Compiling Error.
Animation is a bit tricky especially if you want different animations for different views inside the same stack.
To achieve your goal, you need to wrap each sub view inside an independent stack, then they will have their own unique animation.
Try this code:
import SwiftUI
struct TestView: View {
#State var animate: Bool = false
var body: some View {
VStack(alignment: .center) {
HStack() {
ZStack() {
//added
if animate {
ZStack {
Rectangle()
.frame(height: 50)
} .transition(.slide)
}
//added
if animate {
ZStack {
Image(systemName: "figure.mixed.cardio")
.foregroundColor(.white)
}.transition(.scale)
}
}
}
Button("animate") {
withAnimation {
animate.toggle()
}
}
}
}
}
I'm trying to connect 2 views. One is a ScrollView and the other is a List. Does anyone know how to do that? Putting both into a singular ScrollView doesn't work, and calling APIResponseView().environmentObject(Model()) inside of ContentView() also doesn't work.
struct MyApp: App {
var body: some Scene {
WindowGroup {
VStack{
ContentView() // this is a ScrollView
APIResponseView().environmentObject(Model()) // this is a List
}
}
}
}
As you can see, the views aren't connected:
Image found here
If you want to have the ScrollView move out of view when scrolling the list, you have to put the ScrollView INSIDE the list:
struct ContentView: View {
#State private var test = false
var body: some View {
// List
List {
// ScrollView
ScrollView(.horizontal) {
HStack {
ForEach(0..<6) { i in
Text("ScrollView \(i)")
.frame(width: 150, height: 200)
.background(
RoundedRectangle(cornerRadius: 10)
.fill(.blue)
)
}
}
}
ForEach(0..<10) { item in
HStack {
VStack {
Text("Overheadline")
.font(.caption)
.foregroundColor(.secondary)
Text("List Item \(item)")
}
Spacer()
RoundedRectangle(cornerRadius: 10)
.fill(.gray)
.frame(width: 50, height: 50)
}
}
}
.listStyle(.plain)
}
}
I am trying to understand MatchGeometryEffect a little better. I am trying to put it on each cell of a list in a scrollView. I want it to be where if showAlternate is false, the main cell is show but if showAlternate is true , then the individual cell will show the else of the conditional which would be a larger cell ( not part of the code yet). I have the matchedGeometryEffect on the ForEach right now but how can I do each cell indvidually?
struct ContentView: View {
#Binding var countries: [Country]
#State var showAlternate = false;
#Namespace var namespace;
var body: some View {
NavigationView {
ScrollView {
ForEach(Array(countries.enumerated()), id: \.1.id) { (index,country) in
LazyVStack {
HStack {
NavigationLink(
destination: CountryView(country: country),
label: {
HStack {
Image(country.image)
.resizable()
.scaledToFit()
Text(country.display_name)
.foregroundColor(Color.black)
.padding(.leading)
Spacer()
}
.padding(.top, 12.0)
.frame(height: 80)
}
).buttonStyle(FlatLinkStyle())
}
.padding(.horizontal, 16.0)
.overlay(Divider(), alignment: .top)
}
}
.background(Rectangle().opacity(0.2).matchedGeometryEffect(id: "shape", in: namespace))
}
}
.font(Font.custom("Avenir", size: 22))
}
}
I have two views in a HStack - the left one is a simple Text view.
For the right view, i'm experimenting with using GeometryReader. I cannot get the baselines of the text aligned when I use geometry reader, but I can when I don't use it.
Here is some code:
struct CompositeView : View {
var body : some View {
VStack {
Button(action: {
}) {
Text("Another text")
.padding(5)
.overlay(RoundedRectangle(cornerRadius: 5).stroke(Color.blue, lineWidth: 2.0))
}
}
}
}
struct GeometryReaderTest : View {
var tags: [String]
init(tags : [String]) {
self.tags = tags
}
#State private var viewHeight = CGFloat.zero
var body: some View {
VStack {
GeometryReader { geometry in
ZStack {
ForEach(self.tags, id: \.self) { tag in
Button(action: {}) {
Text(tag)
.padding(5)
.overlay(RoundedRectangle(cornerRadius: 5).stroke(Color.blue, lineWidth: 2.0))
}
}
}
.background(getHeight(self.$viewHeight))
}
}
.frame(height: viewHeight)
}
/// Get the height of the Geometry view
///
private func getHeight(_ height: Binding<CGFloat>) -> some View {
return GeometryReader { geometry -> Color in
let rect = geometry.frame(in: .local)
DispatchQueue.main.async {
height.wrappedValue = rect.size.height
}
return .clear
}
}
}
struct MyTestView : View {
var body : some View {
VStack {
HStack (alignment: .firstTextBaseline) {
ZStack {
Text("Hello")
}
GeometryReaderTest(tags: ["One"])
}
HStack (alignment: .firstTextBaseline) {
ZStack {
Text("Hello")
}
CompositeView()
}
}
}
}
Here is the outcome:
As you can see, the baseline alignment doesn't work when the right hand view is a GeometryReader, but it does work when the right hand view is a simple button.
Does anyone know how I can get the baselines to line up?
I'm using GeometryReader because my right-view will eventually be more complex - I'm reducing the issue I'm facing to as small a repro as possible.
Thank you!
The question is as simple as in the title. I am trying to put a Picker which has the style of SegmentedPickerStyle to NavigationBar in SwiftUI. It is just like the native Phone application's history page. The image is below
I have looked for Google and Github for example projects, libraries or any tutorials and no luck. I think if nativa apps and WhatsApp for example has it, then it should be possible. Any help would be appreciated.
SwiftUI 2 + toolbar:
struct DemoView: View {
#State private var mode: Int = 0
var body: some View {
Text("Hello, World!")
.toolbar {
ToolbarItem(placement: .principal) {
Picker("Color", selection: $mode) {
Text("Light").tag(0)
Text("Dark").tag(1)
}
.pickerStyle(SegmentedPickerStyle())
}
}
}
}
You can put a Picker directly into .navigationBarItems.
The only trouble I'm having is getting the Picker to be centered. (Just to show that a Picker can indeed be in the Navigation Bar I put together a kind of hacky solution with frame and Geometry Reader. You'll need to find a proper solution to centering.)
struct ContentView: View {
#State private var choices = ["All", "Missed"]
#State private var choice = 0
#State private var contacts = [("Anna Lisa Moreno", "9:40 AM"), ("Justin Shumaker", "9:35 AM")]
var body: some View {
GeometryReader { geometry in
NavigationView {
List {
ForEach(self.contacts, id: \.self.0) { (contact, time) in
ContactView(name: contact, time: time)
}
.onDelete(perform: self.deleteItems)
}
.navigationBarTitle("Recents")
.navigationBarItems(
leading:
HStack {
Button("Clear") {
// do stuff
}
Picker(selection: self.$choice, label: Text("Pick One")) {
ForEach(0 ..< self.choices.count) {
Text(self.choices[$0])
}
}
.frame(width: 130)
.pickerStyle(SegmentedPickerStyle())
.padding(.leading, (geometry.size.width / 2.0) - 130)
},
trailing: EditButton())
}
}
}
func deleteItems(at offsets: IndexSet) {
contacts.remove(atOffsets: offsets)
}
}
struct ContactView: View {
var name: String
var time: String
var body: some View {
HStack {
VStack {
Image(systemName: "phone.fill.arrow.up.right")
.font(.headline)
.foregroundColor(.secondary)
Text("")
}
VStack(alignment: .leading) {
Text(self.name)
.font(.headline)
Text("iPhone")
.foregroundColor(.secondary)
}
Spacer()
Text(self.time)
.foregroundColor(.secondary)
}
}
}
For those who want to make it dead center, Just put two HStack to each side and made them width fixed and equal.
Add this method to View extension.
extension View {
func navigationBarItems<L, C, T>(leading: L, center: C, trailing: T) -> some View where L: View, C: View, T: View {
self.navigationBarItems(leading:
HStack{
HStack {
leading
}
.frame(width: 60, alignment: .leading)
Spacer()
HStack {
center
}
.frame(width: 300, alignment: .center)
Spacer()
HStack {
//Text("asdasd")
trailing
}
//.background(Color.blue)
.frame(width: 100, alignment: .trailing)
}
//.background(Color.yellow)
.frame(width: UIScreen.main.bounds.size.width-32)
)
}
}
Now you have a View modifier which has the same usage of navigationBatItems(:_). You can edit the code based on your needs.
Usage example:
.navigationBarItems(leading: EmptyView(), center:
Picker(selection: self.$choice, label: Text("Pick One")) {
ForEach(0 ..< self.choices.count) {
Text(self.choices[$0])
}
}
.pickerStyle(SegmentedPickerStyle())
}, trailing: EmptyView())
UPDATE
There was the issue of leading and trailing items were violating UINavigationBarContentView's safeArea. While I was searching through, I came across another solution in this answer. It is little helper library called SwiftUIX. If you do not want install whole library -like me- I created a gist just for navigationBarItems. Just add the file to your project.
But do not forget this, It was stretching the Picker to cover all the free space and forcing StatusView to be narrower. So I had to set frames like this;
.navigationBarItems(center:
Picker(...) {
...
}
.frame(width: 150)
, trailing:
StatusView()
.frame(width: 70)
)
If you need segmentcontroll to be in center you need to use GeometryReader, below code will provide picker as title, and trailing (right) button.
You set up two view on the sides left and right with the same width, and the middle view will take the rest.
5 is the magic number depends how width you need segment to be.
You can experiment and see the best fit for you.
GeometryReader {
Text("TEST")
.navigationBarItems(leading:
HStack {
Spacer().frame(width: geometry.size.width / 5)
Spacer()
picker
Spacer()
Button().frame(width: geometry.size.width / 5)
}.frame(width: geometry.size.width)
}
But better solution is if you save picker size and then calculate other frame sizes, so picker will be same on ipad & iphone
#State var segmentControllerWidth: CGFloat = 0
var body: some View {
HStack {
Spacer()
.frame(width: (geometry.size.width / 2) - (segmentControllerWidth / 2))
.background(Color.red)
segmentController
.fixedSize()
.background(PreferenceViewSetter())
profileButton
.frame(width: (geometry.size.width / 2) - (segmentControllerWidth / 2))
}
.onPreferenceChange(PreferenceViewKey.self) { preferences in
segmentControllerWidth = preferences.width
}
}
struct PreferenceViewSetter: View {
var body: some View {
GeometryReader { geometry in
Rectangle()
.fill(Color.clear)
.preference(key: PreferenceViewKey.self,
value: PreferenceViewData(width: geometry.size.width))
}
}
}
struct PreferenceViewData: Equatable {
let width: CGFloat
}
struct PreferenceViewKey: PreferenceKey {
typealias Value = PreferenceViewData
static var defaultValue = PreferenceViewData(width: 0)
static func reduce(value: inout PreferenceViewData, nextValue: () -> PreferenceViewData) {
value = nextValue()
}
}
Simple answer how to center segment controller and hide one of the buttons.
#State var showLeadingButton = true
var body: some View {
HStack {
Button(action: {}, label: {"leading"})
.opacity(showLeadingButton ? true : false)
Spacer()
Picker(selection: $selectedStatus,
label: Text("SEGMENT") {
segmentValues
}
.id(UUID())
.pickerStyle(SegmentedPickerStyle())
.fixedSize()
Spacer()
Button(action: {}, label: {"trailing"})
}
.frame(width: UIScreen.main.bounds.width)
}