How I can add a drawing function in SwiftUI? - swiftui

I can't draw. Unfortunately I don't know where the problem is, colour selection works and the canva is also created, only the drawing doesn't work. I have also checked that the size of the canva is correct and that the path is defined correctly. here is my code:
//
import SwiftUI
struct ContentView: View {
#State private var lines: [Line] = []
#State private var strokeColor: Color = Color.black
var body: some View {
ZStack {
Color.white
.edgesIgnoringSafeArea(.all)
VStack {
Canvas(lines: $lines, strokeColor: $strokeColor)
HStack {
Button(action: clearDrawing) {
Text("Clear")
}
ColorPicker("Stroke Color", selection: $strokeColor)
}
.padding()
}
}
}
func clearDrawing() {
lines.removeAll()
}
}
//
Here I have created the Canva and the button to delete the drawing
//
struct Canvas: View {
#Binding var lines: [Line]
#Binding var strokeColor: Color
var body: some View {
GeometryReader { geometry in
Path { path in
for line in self.lines {
path.move(to: line.points[0])
path.addLines(line.points)
}
}
.stroke(self.strokeColor, lineWidth: 3)
.frame(width: geometry.size.width, height: geometry.size.height)
.gesture(
DragGesture(minimumDistance: 0.1)
.onChanged({ value in
var lastLine = self.lines.last!
let currentPoint = value.location
lastLine.points.append(currentPoint)
})
.onEnded({ value in
self.lines.append(Line())
})
)
}
}
}
struct Line: Identifiable {
var id: UUID = UUID()
var points: [CGPoint] = []
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
//
And here I have made it so that you should actually draw by touch, and that there is a colour selection

Related

SwiftUI How to Make Page TabView Dynamic

So I am trying to make my TabView height dynamic. I have been looking for a way to do this but I can't seem to find a solution anywhere. This is how my code looks like.
struct ContentView: View {
#State var contentHeight: CGFloat = 0
var body: some View {
NavigationView {
ScrollView {
VStack {
TabView {
TestView1(contentHeight: $contentHeight)
TestView2(contentHeight: $contentHeight)
}
.tabViewStyle(.page)
.frame(height: contentHeight)
.indexViewStyle(.page(backgroundDisplayMode: .always))
.background(.yellow)
}
.navigationBarTitleDisplayMode(.inline)
.navigationTitle("Test Project")
}
}
}
}
This is how my test1view and test2view look like.
struct TestView1: View {
#State var height: CGFloat = 0
#Binding var contentHeight: CGFloat
var body: some View {
Color.red
.frame(maxWidth:.infinity, minHeight: 200, maxHeight: 200)
.background(
GeometryReader { geo in
Color.clear
.preference(
key: HeightPreferenceKey.self,
value: geo.size.height
)
.onAppear {
contentHeight = height
}
}
.onPreferenceChange(HeightPreferenceKey.self) { height in
self.height = height
}
)
}
}
struct TestView2: View {
#Binding var contentHeight: CGFloat
#State var height: CGFloat = 0
var body: some View {
Color.black
.frame(maxWidth:.infinity, minHeight: 350, maxHeight: 350)
.background(
GeometryReader { geo in
Color.clear
.preference(
key: HeightPreferenceKey.self,
value: geo.size.height
)
.onAppear {
contentHeight = height
}
}
.onPreferenceChange(HeightPreferenceKey.self) { height in
self.height = height
}
)
}
}
struct HeightPreferenceKey: PreferenceKey {
static let defaultValue: CGFloat = 0
static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
value = nextValue()
}
}
Now the problem is that when I drag it just a little the height changes. So when I drag it a little to the left the height changes to the height of TestView2 and it is still on TestView1.
I tried to add a drag gesture but it didn't let me swipe to the next page. So I don't know how I will be able to achieve this. Ive been looking for a solution but still no luck.
You can use the TabView($selection) initializer to do this. https://developer.apple.com/documentation/swiftui/tabview/init(selection:content:)
It tells you which tab you're currently viewing. Based off the middle point of the screen. And you don't have to deal with nasty GeometryReader and HeightPreferenceKey.
Here's your updated code. I even added a nice animation to fade between the two heights!
struct ContentView: View {
#State var selectedTab: Tab = .first
#State var animatedContentHeight: CGFloat = 300
enum Tab {
case first
case second
var contentHeight: CGFloat {
switch self {
case .first:
return 200
case .second:
return 350
}
}
}
var body: some View {
TabView(selection: $selectedTab) {
TestView1()
.tag(Tab.first)
TestView2()
.tag(Tab.second)
}
.tabViewStyle(.page)
// .frame(height: selectedTab.contentHeight) // Uncomment to see without animation
.frame(height: animatedContentHeight)
.indexViewStyle(.page(backgroundDisplayMode: .always))
.onChange(of: selectedTab) { newValue in
print("now selected:", newValue)
withAnimation { animatedContentHeight = selectedTab.contentHeight }
}
}
}
struct TestView1: View {
var body: some View {
Color.red
}
}
struct TestView2: View {
var body: some View {
Color.black
}
}

Custom Reusable Color Picker not updating colors in other views

The following code successfully shows a set of colors and displays the selected color when the colors are tapped, similar to the standard ColorPicker. What I would like to be able to do is use this custom color picker in other views, in a similar way as the standard ColorPicker. My issue is that I cannot expose the selected color to other views.
ContentView:
struct ContentView: View {
var body: some View {
VStack{
CustomColorPicker()
}
}
}
Custom Color Picker:
struct CustomColorPicker: View {
var colors: [UIColor] = [.red, .green, .blue, .purple, .orange]
#State var selectedColor = UIColor.systemGray6
var body: some View {
VStack {
Rectangle()
.fill(Color(selectedColor))
.frame(width: 45, height: 45)
HStack(spacing: 0) {
ForEach(colors, id: \.self) { color in
Button {
selectedColor = color
} label: {
Color(color)
.border(Color.gray, width: color == selectedColor ? 2 : 0)
}
}
}
.frame(height: 50.0)
}
}
}
I have tied using a model/ObservableObject to be able to capture the selected color in other views but it doesn't when you select the colors.
How can I make the Rectangle in ContentView update its fill color when a color in the color picker is tapped?
Or in general, what would be the best way to create a reusable custom color picker?
Using an ObservableObject
Content View
struct ContentView: View {
#ObservedObject var cModel = ColorPickerModel()
var body: some View {
VStack{
CustomColorPicker()
Rectangle()
.fill(cModel.selectedColor)
.frame(width: 100, height: 100)
}
}
}
Custom Color Picker
class ColorPickerModel:ObservableObject{
#Published var selectedColor:Color = Color.orange
}
struct CustomColorPicker: View {
var colors: [UIColor] = [.red, .green, .blue, .purple, .orange]
#StateObject var cModel = ColorPickerModel()
var body: some View {
VStack {
Rectangle()
.fill(cModel.selectedColor)
.frame(width: 45, height: 45)
HStack(spacing: 0) {
ForEach(colors, id: \.self) { color in
Button {
cModel.selectedColor = Color(color)
} label: {
Color(color)
//.border(Color.gray, width: color == selectedColor ? 2 : 0)
}
}
}
.frame(height: 50.0)
}
}
}
import SwiftUI
struct MyColorView: View {
//Source of truth
#StateObject var cModel = MyColorViewModel()
var body: some View {
VStack{
CustomColorPicker(selectedColor: $cModel.selectedColor)
Rectangle()
//Convert to the View Color
.fill(Color(cModel.selectedColor))
.frame(width: 100, height: 100)
}
}
}
//This will serve as the source of truth for this View and any View you share the ObservableObject with
//Share it using #ObservedObject and #EnvironmentObject
class MyColorViewModel:ObservableObject{
//Change to UIColor the types have to match
#Published var selectedColor: UIColor = .orange
}
struct CustomColorPicker: View {
var colors: [UIColor] = [.red, .green, .blue, .purple, .orange]
//You can now use this Picker with any Model
//Binding is a two-way connection it needs a source of truth
#Binding var selectedColor: UIColor
var body: some View {
VStack {
HStack(spacing: 0) {
ForEach(colors, id: \.self) { color in
Button {
selectedColor = color
} label: {
Color(color)
.border(Color.gray, width: color == selectedColor ? 2 : 0)
}
}
}
.frame(height: 50.0)
}
}
}
struct MyColorView_Previews: PreviewProvider {
static var previews: some View {
MyColorView()
}
}
Try the Apple SwiftUI Tutorials they are a good start.

Weird behavior matchedGeometryEffect with list

Why has only the orange Color a right animation? Green and Red is laying under the list while the animation, but why?
With VStavk there is no problem but with list. Want an animation when switching from list View to Grid View.
struct Colors: Identifiable{
var id = UUID()
var col: Color
}
struct ContentView: View {
#State var on = true
#Namespace var ani
var colors = [Colors(col: .green),Colors(col: .orange),Colors(col: .red)]
var body: some View {
VStack {
if on {
List{
ForEach(colors){col in
col.col
.matchedGeometryEffect(id: "\(col.id)", in: ani)
.animation(.easeIn)
}
.listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0))
}
.listStyle(InsetGroupedListStyle())
.frame(height: 400)
} else {
LazyVGrid(columns: [GridItem(.fixed(200)),GridItem(.fixed(200))], content: {
ForEach(colors){col in
col.col
.matchedGeometryEffect(id: "\(col.id)", in: ani)
.animation(.easeIn)
}
})
.frame(height: 400)
}
Button("toggle"){
withAnimation(.easeIn){
on.toggle()
}
}
}
}
Thanks to the comment of #Asperi and this post: Individually modifying child views passed to a container using #ViewBuilder in SwiftUI with the answer of #Tushar Sharma I tried something like this:
import SwiftUI
struct SomeContainerView<Content: View>:View {
var ani: Namespace.ID
var model:[Model] = []
init(namespace: Namespace.ID,model:[Model],#ViewBuilder content: #escaping (Model) -> Content) {
self.content = content
self.model = model
ani = namespace
}
let content: (Model) -> Content
var body: some View {
VStack{
ForEach(model,id:\.id){model in
content(model)
.background(Color.gray.matchedGeometryEffect(id: model.id, in: ani))
}
}
}
}
struct ContentView:View {
#ObservedObject var modelData = Objects()
#Namespace var ani
#State var show = true
var body: some View{
VStack{
Toggle("toggle", isOn: $show.animation())
if show{
SomeContainerView(namespace: ani,model: modelData.myObj){ data in
HStack{
Text("\(data.name)")
data.color.frame(width: 100,height : 100)
}
}
}else{
LazyVGrid(columns: [GridItem(.fixed(110)),GridItem(.fixed(110))],spacing: 10){
ForEach(modelData.myObj){model in
Text("\(model.name)")
.frame(width: 100,height: 100)
.background(Color.gray.matchedGeometryEffect(id: model.id, in: ani))
}
}
}
}
}
}
struct Model: Identifiable{
var id = UUID().uuidString
var name:String
var color:Color
init(name:String,color:Color) {
self.name = name
self.color = color
}
}
class Objects:ObservableObject{
#Published var myObj:[Model] = []
init() {
initModel()
}
func initModel(){
let model = Model(name: "Jack", color: .green)
let model1 = Model(name: "hey Jack", color: .red)
let model2 = Model(name: "hey billy", color: .red)
myObj.append(model)
myObj.append(model1)
myObj.append(model2)
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}

How to Add multi text into the list in SwiftUI?(Data Flow)

I'm trying to build an demo app by swiftUI that get multi text from user and add them to the list, below , there is an image of app every time user press plus button the AddListView show to the user and there user can add multi text to the List.I have a problem to add them to the list by new switUI data Flow I don't know how to pass data.(I comment more information)
Thanks 🙏
here is my code for AddListView:
import SwiftUI
struct AddListView: View {
#State var numberOfTextFiled = 1
#Binding var showAddListView : Bool
var body: some View {
ZStack {
Title(numberOfTextFiled: $numberOfTextFiled)
VStack {
ScrollView {
ForEach(0 ..< numberOfTextFiled, id: \.self) { item in
PreAddTextField()
}
}
}
.padding()
.offset(y: 40)
Buttons(showAddListView: $showAddListView)
}
.frame(width: 300, height: 200)
.background(Color.white)
.shadow(color: Color.black.opacity(0.3), radius: 10, x: 0, y: 10)
}
}
struct SwiftUIView_Previews: PreviewProvider {
static var previews: some View {
AddListView(showAddListView: .constant(false))
}
}
struct PreAddTextField: View {
// I made this standalone struct and use #State to every TextField text be independent
// if i use #Binding to pass data all Texfield have the same text value
#State var textInTextField = ""
var body: some View {
VStack {
TextField("Enter text", text: $textInTextField)
}
}
}
struct Buttons: View {
#Binding var showAddListView : Bool
var body: some View {
VStack {
HStack(spacing:100) {
Button(action: {
showAddListView = false}) {
Text("Cancel")
}
Button(action: {
showAddListView = false
// What should happen here to add Text to List???
}) {
Text("Add")
}
}
}
.offset(y: 70)
}
}
struct Title: View {
#Binding var numberOfTextFiled : Int
var body: some View {
VStack {
HStack {
Text("Add Text to list")
.font(.title2)
Spacer()
Button(action: {
numberOfTextFiled += 1
}) {
Image(systemName: "plus")
.font(.title2)
}
}
.padding()
Spacer()
}
}
}
and for DataModel:
import SwiftUI
struct Text1 : Identifiable , Hashable{
var id = UUID()
var text : String
}
var textData = [
Text1(text: "SwiftUI"),
Text1(text: "Data flow?"),
]
and finally:
import SwiftUI
struct ListView: View {
#State var showAddListView = false
var body: some View {
NavigationView {
VStack {
ZStack {
List(textData, id : \.self){ text in
Text(text.text)
}
if showAddListView {
AddListView(showAddListView: $showAddListView)
.offset(y:-100)
}
}
}
.navigationTitle("List")
.navigationBarItems(trailing:
Button(action: {showAddListView = true}) {
Image(systemName: "plus")
.font(.title2)
}
)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ListView()
}
}
Because of the multiple-items part of the question, this becomes a lot less trivial. However, using a combination of ObservableObjects and callback functions, definitely doable. Look at the inline comments in the code for explanations about what is going on:
struct Text1 : Identifiable , Hashable{
var id = UUID()
var text : String
}
//Store the items in an ObservableObject instead of just in #State
class AppState : ObservableObject {
#Published var textData : [Text1] = [.init(text: "Item 1"),.init(text: "Item 2")]
}
//This view model stores data about all of the new items that are going to be added
class AddListViewViewModel : ObservableObject {
#Published var textItemsToAdd : [Text1] = [.init(text: "")] //start with one empty item
//save all of the new items -- don't save anything that is empty
func saveToAppState(appState: AppState) {
appState.textData.append(contentsOf: textItemsToAdd.filter { !$0.text.isEmpty })
}
//these Bindings get used for the TextFields -- they're attached to the item IDs
func bindingForId(id: UUID) -> Binding<String> {
.init { () -> String in
self.textItemsToAdd.first(where: { $0.id == id })?.text ?? ""
} set: { (newValue) in
self.textItemsToAdd = self.textItemsToAdd.map {
guard $0.id == id else {
return $0
}
return .init(id: id, text: newValue)
}
}
}
}
struct AddListView: View {
#Binding var showAddListView : Bool
#ObservedObject var appState : AppState
#StateObject private var viewModel = AddListViewViewModel()
var body: some View {
ZStack {
Title(addItem: { viewModel.textItemsToAdd.append(.init(text: "")) })
VStack {
ScrollView {
ForEach(viewModel.textItemsToAdd, id: \.id) { item in //note this is id: \.id and not \.self
PreAddTextField(textInTextField: viewModel.bindingForId(id: item.id))
}
}
}
.padding()
.offset(y: 40)
Buttons(showAddListView: $showAddListView, save: {
viewModel.saveToAppState(appState: appState)
})
}
.frame(width: 300, height: 200)
.background(Color.white)
.shadow(color: Color.black.opacity(0.3), radius: 10, x: 0, y: 10)
}
}
struct PreAddTextField: View {
#Binding var textInTextField : String //this takes a binding to the view model now
var body: some View {
VStack {
TextField("Enter text", text: $textInTextField)
}
}
}
struct Buttons: View {
#Binding var showAddListView : Bool
var save : () -> Void //callback function for what happens when "Add" gets pressed
var body: some View {
VStack {
HStack(spacing:100) {
Button(action: {
showAddListView = false}) {
Text("Cancel")
}
Button(action: {
showAddListView = false
save()
}) {
Text("Add")
}
}
}
.offset(y: 70)
}
}
struct Title: View {
var addItem : () -> Void //callback function for what happens when the plus button is hit
var body: some View {
VStack {
HStack {
Text("Add Text to list")
.font(.title2)
Spacer()
Button(action: {
addItem()
}) {
Image(systemName: "plus")
.font(.title2)
}
}
.padding()
Spacer()
}
}
}
struct ListView: View {
#StateObject var appState = AppState() //store the AppState here
#State private var showAddListView = false
var body: some View {
NavigationView {
VStack {
ZStack {
List(appState.textData, id : \.self){ text in
Text(text.text)
}
if showAddListView {
AddListView(showAddListView: $showAddListView, appState: appState)
.offset(y:-100)
}
}
}
.navigationTitle("List")
.navigationBarItems(trailing:
Button(action: {showAddListView = true}) {
Image(systemName: "plus")
.font(.title2)
}
)
}
}
}

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)
}
}