SwiftUI Hero Animation (matchedGeometryEffect) Problem - swiftui

I've been looking at different tutorials online trying to piece together a "simple" hero animation. The images are in a LazyHGrid and when tapped, should expand up to the second image. I tried to condense and simplify the code below as much as possible.
I have gotten it to work outside of the grid with just one image.
import SwiftUI
struct Item: Hashable {
let id = UUID()
var text = "TEXT"
}
struct ContentView: View {
var items = [Item(text: "One"), Item(text: "Two"), Item(text: "Three"), Item(text: "Four"), Item(text: "Five"), Item(text: "Six"), Item(text: "Seven"),Item(text: "Eight"),Item(text: "Nine")]
#State private var isShowingDetail = false
#State private var selectedItemID: UUID? = nil
#Namespace var animation: Namespace.ID
var body: some View {
ZStack {
ScrollView(.horizontal) {
LazyHGrid(rows: [GridItem(.flexible())], spacing: 10, content: {
ForEach(items,id: \.id, content: { item in
VStack {
Image("Image2")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 200)
.matchedGeometryEffect(id: item.id, in: animation)
.onTapGesture {
withAnimation(.spring()){
self.selectedItemID = item.id
isShowingDetail.toggle()
}
}
Text(item.text)
}
})
})
}
if isShowingDetail {
VStack {
Image("Image1")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 300)
.matchedGeometryEffect(id: selectedItemID, in: animation)
.onTapGesture {
withAnimation(.spring()){
selectedItemID = nil
isShowingDetail.toggle()
}
}
Spacer()
}
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}

It looks like your selectedItemID and isShowingDetail are fighting each other (and are redundant, since selectedItemID is an optional. I've commented out a couple lines and it seems to be working:
struct ContentView: View {
#State var items = [Item(text: "One"), Item(text: "Two"), Item(text: "Three"), Item(text: "Four"), Item(text: "Five"), Item(text: "Six"), Item(text: "Seven"),Item(text: "Eight"),Item(text: "Nine")]
//#State private var isShowingDetail = false // <----- here
#State private var selectedItemID: UUID? = nil
#Namespace var animation: Namespace.ID
var body: some View {
ZStack {
ScrollView(.horizontal) {
LazyHGrid(rows: [GridItem(.flexible())], spacing: 10, content: {
ForEach(items,id: \.id, content: { item in
VStack {
Image("0")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 200)
.matchedGeometryEffect(id: item.id, in: animation)
.onTapGesture {
withAnimation(.spring()){
self.selectedItemID = item.id
//isShowingDetail.toggle() // <----- here
}
}
Text(item.text)
}
})
})
}
if let selectedItemID = selectedItemID { // <----- here
VStack {
Image("1")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 300)
.matchedGeometryEffect(id: selectedItemID, in: animation)
.onTapGesture {
withAnimation(.spring()){
self.selectedItemID = nil
//isShowingDetail.toggle() // <----- here
}
}
Spacer()
}
}
}
}
}
The one caveat is it seems possible to get in a funny state if you tap the original image again while the detail image is shrinking back down. Not sure if it's a bug with the code or a bug with matchGeometryEffect itself.

Related

How to have a Navigation link open a different view?

I keep trying to have the navigation links in the surveys list in contentView to open detailView when selected. I keep getting errors Missing argument for parameter 'newsurvey' in call Insert 'newsurvey: <#Survey#>'
Here's the home view
struct ContentView: View {
#State var surveys: [Survey] = []
#State var isPresented = false
#State var selectedTab: Int = 0
#State private var path = [String]()
let dateFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.dateFormat = "MM/dd/yyyy"
return formatter
}()
var body: some View {
ZStack {
VStack{
NavigationView {
VStack {
Text("")
.frame(height: 0)
.navigationBarTitleDisplayMode(.inline)
//reduces navbartitle height
.toolbar {
ToolbarItem(placement: .principal) {
Image("RR_Logo_Primary_Full_Color")
.resizable()
.frame(width: 275.0, height: 55.0)
}
}
List(surveys) { survey in
NavigationLink(destination: DetailView(newsurvey: survey)) //must be within navigationview
{
HStack{
ZStack {
Rectangle()
.frame(width: 400, height: 100.0)
.foregroundColor(.white)
.cornerRadius(4)
HStack (alignment: .top) {
VStack (alignment: .leading) {
Text(survey.customerName)
.offset(x: -60, y: -2)
.font(.custom("Roboto-Light", size: 24))
Text(survey.lineName)
.offset(x: -60, y: -5)
.font(.custom("Roboto-Light", size: 18))
Text("# of conveyors")
.offset(x: -60)
}
VStack {
Text(dateFormatter.string(from: survey.date))
.font(.custom("Roboto-Light", size: 18))
.offset(x: 60)
}
}
}
}
}
}
.listRowInsets(EdgeInsets())
}
}
.environment(\.defaultMinListHeaderHeight, 1)
CustomTabBar(surveys: $surveys, isPresented: $isPresented)
.frame(height: 60, alignment: .bottom)
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
I tried the following code below but I keep getting not in scope error.
#State var newsurvey: Survey
#State var surveys: [Survey] = []
#State private var conveyorName = ""
#State private var masterTag = ""
#State private var showAddConveyorSheet = false
#State private var conveyors: [Conveyor] = []
var body: some View {
NavigationView {
VStack {
List {
ForEach(conveyors, id: \.name) { conveyor in
NavigationLink(destination: ConveyorTrack(conveyor: conveyor)) {
Text(conveyor.name)
}
}
}
Button(action: {
self.showAddConveyorSheet = true
}) {
Text("Add Conveyor")
}
}
.navigationBarTitle("Conveyors", displayMode: .inline)
.sheet(isPresented: $showAddConveyorSheet) {
VStack {
HStack {
TextField("Conveyor Name", text: $conveyorName)
TextField("Master Tag", text: $masterTag)
.onTapGesture {
// open camera and scan text here
}
}
Button(action: {
self.conveyors.append(Conveyor(id: self.conveyorName, name: self.conveyorName, masterTag: self.masterTag))
self.conveyorName = ""
self.masterTag = ""
self.showAddConveyorSheet = false
}) {
Text("Save")
}
}
}
}
}
}
struct DetailView_Previews: PreviewProvider {
static var previews: some View {
DetailView(newsurvey: survey)
}
}

List ForEach Problem SwiftUI - ListCells are not visible with List

I realized that when I use nested List - ForEach, I can not see anything inside these codes. But when I remove List I can see all elements inside the view. Unfortunately, without using List I can not use .onDelete and that is a problem.
struct ListsInfoView: View {
#Environment(\.managedObjectContext) private var viewContext
#FetchRequest(entity:CDListModel.entity(),
sortDescriptors: [
NSSortDescriptor(
keyPath:\CDListModel.title,
ascending: true )
]
)var lists: FetchedResults<CDListModel>
#State var isListSelected = false
#State var selectedList : CDListModel!
var body: some View {
List{
ForEach(lists) { list in
Button(action: {
self.selectedList = list
self.isListSelected.toggle()
}) {
ListCell(list: list)
}
}
.onDelete(perform: deleteList)
}
.listStyle(PlainListStyle())
.fullScreenCover(isPresented: $isListSelected) {
ListDetailView(selectedList: $selectedList)
.environment(\.managedObjectContext, viewContext)
}
}
func deleteList(at offsets: IndexSet) {
viewContext.delete(lists[offsets.first!])
PersistenceController.shared.saveContext()
}
}
Like above, I do not see any ListCell but when I remove List { } it is all perfect. Why is that?
My ListCell
struct ListCell: View {
#ObservedObject var list : CDListModel
var body: some View {
HStack{
Image(systemName: "folder")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 30, height: 30, alignment: .center)
.foregroundColor(.yellow)
Text(list.title ?? "")
.foregroundColor(.black)
.font(.system(size: 20, weight: .regular, design: .rounded))
.padding(.leading,10)
Spacer()
Text(String(list.notes?.count ?? 0))
.foregroundColor(.gray)
Image(systemName: "chevron.right")
.foregroundColor(.gray)
}
.padding(.horizontal)
}
}
This is my MainView that I call ListsView. Inside of the ListView I am calling ListInfoView if isShowTapped
struct MainView: View {
#Environment(\.managedObjectContext) private var viewContext
#State var searchText = ""
#State var newFolderName = ""
#State var isAddList : Bool = false
#State var isAddNote: Bool = false
var body: some View {
ZStack{
Color(.white)
.edgesIgnoringSafeArea(.all)
NavigationView{
VStack(alignment: .leading){
ScrollView{
SearchBar(text: $searchText)
.environment(\.managedObjectContext, viewContext)
ListsView()
.environment(\.managedObjectContext, viewContext)
ListView
struct ListsView: View {
#Environment(\.managedObjectContext) private var viewContext
#State var isShowTapped: Bool = false
#State var selectedIndex : Int = 0
var body: some View {
VStack {
Spacer()
HStack{
Text("On My iPhone")
.font(.system(size: 20, weight: .semibold, design: .rounded))
Spacer()
Button(action: {
withAnimation{
self.isShowTapped.toggle()
print("slider button tapped")
}
}, label: {
Image(systemName:isShowTapped ? "chevron.down" : "chevron.right")
.foregroundColor(.black)
})
}
.padding(.horizontal)
if isShowTapped {
ListsInfoView()
.environment(\.managedObjectContext, viewContext)
.transition(.scale)
} else {}
Spacer()
}
}
}

SwiftUI TabView not working, it just shows text off screen

I am trying to get a TabView in SwiftUI, but it just doesn't work... My code is here:
import SwiftUI
import SDWebImage
import HalfModal
struct ContentView: View {
#State var launches: [Launch] = []
// #State private var showingAlert = false
#State private var show_modal: Bool = false
#State private var mName: String = ""
#State private var mDate: String = ""
#State private var rID: String = ""
#State private var mImg: String = ""
#State private var mDesc: String = ""
#State private var showingHalfModal: Bool = false
#State private var choices = ["Launches", "Rockets"]
#State private var choice = 0
var rocketNames = ["5e9d0d95eda69955f709d1eb": "Falcon 1", "5e9d0d95eda69973a809d1ec": "Falcon 9", "5e9d0d95eda69974db09d1ed": "Falcon Heavy", "5e9d0d96eda699382d09d1ee": "Starship"]
init() {
UITableView.appearance().separatorStyle = .none
UITableViewCell.appearance().backgroundColor = .clear
UITableView.appearance().backgroundColor = .clear
}
var body: some View {
// Spacer()
// .frame(height: 100)
TabView {
Group {
NavigationView {
ZStack {
VStack {
// Spacer()
// .frame(height: 10)
// Text("SpaceX launch list")
// .font(.largeTitle)
Spacer()
.frame(height: 1)
.navigationBarTitle("Launches")
List {
ForEach(launches, id: \.id) { launch in
// Text("image")
// Image("imagenotfound")
Button(action: {
self.mName = launch.name
self.mDate = Date(timeIntervalSince1970: launch.date_unix).getFormattedDate(format: "dd/MM/yyyy HH:mm:ss")
self.rID = launch.rocket
self.mImg = launch.links.patch.missionPatch ?? "null"
self.mDesc = launch.details ?? "No description"
// sleep(1)
self.show_modal.toggle()
withAnimation {
self.showingHalfModal = true
}
}) {
HStack {
// Image("imagenotfound")
// .resizable()
// .frame(width: 50, height: 50)
URLimageView(urlString: launch.links.patch.missionPatch)
// .frame(width: 50, height: 50)
Group {
Text(launch.name)
.font(.system(size: 23))
.frame(maxWidth: .infinity, alignment: .leading)
.fixedSize(horizontal: false, vertical: true)
Text(Date(timeIntervalSince1970: launch.date_unix).getFormattedDate(format: "dd/MM/yyyy HH:mm:ss"))
.font(.system(size: 11.5))
.foregroundColor(Color.gray)
.frame(maxWidth: .infinity, alignment: .leading)
.fixedSize(horizontal: false, vertical: true)
Spacer()
}
}
}
.buttonStyle(PlainButtonStyle())
// .sheet(isPresented: self.$show_modal) {
// // ModalView(mission: launch.name, date: Date(timeIntervalSince1970: launch.date_unix).getFormattedDate(format: "dd/MM/yyyy HH:mm:ss"), rocket: launch.rocket)
// ModalView(mission: mName, date: mDate, rocket: rID)
// }
}
}.onAppear {
apiCall().getUsers{ (launches) in self.launches = launches}
}.listStyle(SidebarListStyle())
.frame(alignment: .center)
}
if showingHalfModal {
HalfModalView(content: AnyView(VStack(alignment: .leading) {
Text(mDesc)
.padding()
}), header: AnyView(HStack {
URLimageView(urlString: self.mImg)
VStack(alignment: .leading) {
Text(self.mName)
Text(self.mDate)
.font(.system(size: 10))
.foregroundColor(Color.gray)
}}), isPresented: $showingHalfModal)
}
}
}
}
}
.tabItem {
Image(systemName: "flame")
Text("Launches")
}
Text("rockets")
.tabItem {
Image(systemName: "paperplane")
Text("Rockets")
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
extension Date {
func getFormattedDate(format: String) -> String {
let dateformat = DateFormatter()
dateformat.dateFormat = format
return dateformat.string(from: self)
}
}
I have tried following numerous tutorials that show that they get successful results, but mine still doesn't work...
Screenshot of issue:
It should show 2 tabs: Launches and Rockets... Any ideas on how to get it working?
Your view is too complex and you misplaced some subviews. If you clear the body a little bit, you can see that you attached tabItem modifiers outside the TabView:
var body: some View {
TabView {
Group {
NavigationView {
// ...
}
}
}
.tabItem {
Image(systemName: "flame")
Text("Launches")
}
Text("rockets")
.tabItem {
Image(systemName: "paperplane")
Text("Rockets")
}
}
Instead, try the following structure:
var body: some View {
TabView {
NavigationView {
// ...
}
.tabItem {
Image(systemName: "flame")
Text("Launches")
}
Text("rockets")
.tabItem {
Image(systemName: "paperplane")
Text("Rockets")
}
}
}
Note: I recommend you extract some views as subviews. Some examples can be found here:
SwiftUI - Can I share functions with an extracted subview?

How to show another view when user selects image from the photo library

My problem is that I can't show selected image in another view because I can't set my variable selected to true.
My variables:
#State private var selectedImage: UIImage?
#State private var isImagePickerDisplay = false
#State private var selected: Bool = false
Step 1 - I present ImagePicker for the user (works fine):
.sheet(isPresented: self.$isImagePickerDisplay) {
ImagePickerView(selectedImage: self.$selectedImage, sourceType: .photoLibrary)
}
Step 2 - I can't find the right way to know when the user actually selected the image. I tried to do like this:
if selectedImage != nil {
selected = true
}
But I get error:
Type '()' cannot conform to 'View'; only struct/enum/class types can conform to protocols
Step 3 - Then my plan was to show another view if user actually selected image from photo library:
.fullScreenCover(isPresented: $selected) {
EditView(selectedImage: $selectedImage)
}
Please, keep in mind that I'm new in SwiftUI. I just finished tutorials and now I'm trying to develop a project on my own.
EDIT (added my entire ContentView()):
struct ContentView: View {
#State private var selectedImage: UIImage?
#State private var isImagePickerDisplay = false
#State private var selected: Bool = false
var body: some View {
Button(action: {
self.isImagePickerDisplay.toggle()
}) {
Image(systemName: "plus")
.renderingMode(.original)
.font(.system(size: 16, weight: .medium))
.aspectRatio(contentMode: .fit)
.frame(width: 36, height: 36)
.background(Color(.white))
.clipShape(Circle())
.shadow(radius: 10)
}.sheet(isPresented: self.$isImagePickerDisplay) {
ImagePickerView(selectedImage: self.$selectedImage, sourceType: .photoLibrary)
}
if selectedImage != nil {
selected = true
}
.fullScreenCover(isPresented: $selected) {
EditView(selectedImage: $selectedImage)
}
}
}
Probably you wanted this
struct DFContentView: View {
#State private var selectedImage: UIImage?
#State private var isImagePickerDisplay = false
#State private var selected: Bool = false
var body: some View {
VStack {
Button(action: {
self.isImagePickerDisplay.toggle()
}) {
Image(systemName: "plus")
.renderingMode(.original)
.font(.system(size: 16, weight: .medium))
.aspectRatio(contentMode: .fit)
.frame(width: 36, height: 36)
.background(Color(.white))
.clipShape(Circle())
.shadow(radius: 10)
}
.sheet(isPresented: self.$isImagePickerDisplay, onDismiss: { // << here !!
self.selected = selectedImage != nil
}) {
ImagePickerView(selectedImage: self.$selectedImage, sourceType: .photoLibrary)
}
EmptyView()
.fullScreenCover(isPresented: $selected) {
EditView(selectedImage: $selectedImage)
}
}
}
}
Updated: Thanks to #RajaKishan, about iOS 14.2 - updated with separated .sheet and .fullScreenCover into different views. Tested and worked.
Possible solution
struct ContentView: View {
private enum SheetType {
case pickImage, showImage
}
#State private var selectedImage: UIImage?
#State private var selected: Bool = false
#State private var currentSheetType: SheetType = .pickImage
var body: some View {
Button(action: {
self.selected.toggle()
self.currentSheetType = .pickImage
}) {
Image(systemName: "plus")
.renderingMode(.original)
.font(.system(size: 16, weight: .medium))
.aspectRatio(contentMode: .fit)
.frame(width: 36, height: 36)
.background(Color(.white))
.clipShape(Circle())
.shadow(radius: 10)
}
.sheet(isPresented: self.$selected) {
if currentSheetType == .pickImage {
if selectedImage != nil {
self.selected.toggle()
self.currentSheetType = .showImage
}
}
} content: {
if currentSheetType == .pickImage {
ImagePickerView(selectedImage: self.$selectedImage, sourceType: .photoLibrary)
} else if currentSheetType == .showImage {
EditView(selectedImage: $selectedImage)
}
}
}
}

How do I change my view's background color using List (SwiftUI)

I want to let my cell looks not fill in list's column. I have already clear the list background color and
separatorStyle set .none. I also set my cellView's listRowBackground been gray, but it doesn't work well.The background color is still white in my cell. How do I clear my list's column background color? Please help. Thank you.
struct TeamListView: View {
#EnvironmentObject var userToken : UserToken
#State var teamResults : [TeamResult] = []
var body: some View {
NavigationView {
ZStack{
Color.gray.edgesIgnoringSafeArea(.all)
VStack {
List(teamResults) { team in
TeamListCellView(teamResult: team)
}.navigationBarTitle(Text("My team"),displayMode: .inline)
}
}
.onAppear(perform: {
self.getTeamData()
UITableView.appearance().backgroundColor = .gray
UITableView.appearance().separatorStyle = .none
})
.onDisappear(perform: {
UITableView.appearance().backgroundColor = .white
UITableView.appearance().separatorStyle = .singleLine
})
}
Below is my cellView, I set the .listRowBackground(Color.gray) in here.
struct TeamListCellView: View {
// #ObservedObject var teamResult: TeamResult
var teamResult: TeamResult
var body: some View {
NavigationLink(destination: TeamDetail(teamResult1: teamResult)) {
Image(uiImage: teamResult.teamImage)
.resizable()
.aspectRatio(contentMode: ContentMode.fill)
.frame(width:70, height: 70)
.cornerRadius(35)
VStack(alignment: .leading) {
Text(teamResult.groupName)
Text(teamResult.groupIntro)
.font(.subheadline)
.foregroundColor(Color.gray)
}
} .frame(width:200,height: 100)
.background(Color.green)
.cornerRadius(10)
.listRowBackground(Color.gray)
}
}
You can create a Background<Content: View> and use it to set the background colour of your view. To do it you can embed your views inside your Background View
For example:
struct ContentView: View {
#EnvironmentObject var userToken : UserToken
#State var teamResults : [TeamResult] = []
var body: some View {
Background{
NavigationView {
ZStack{
Color.gray.edgesIgnoringSafeArea(.all)
VStack {
List(teamResults) { team in
TeamListCellView(teamResult: team)
}
.navigationBarTitle(Text("My team"),displayMode: .inline)
}
}
.onAppear(perform: {
self.getTeamData()
UITableView.appearance().backgroundColor = .gray
UITableView.appearance().separatorStyle = .none
})
.onDisappear(perform: {
UITableView.appearance().backgroundColor = .white
UITableView.appearance().separatorStyle = .singleLine
})
}
}
}
}
struct Background<Content: View>: View {
private var content: Content
init(#ViewBuilder content: #escaping () -> Content) {
self.content = content()
}
var body: some View {
Color.gray
.frame(width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height)
.overlay(content)
}
}