I am working on an image editing app for macOS in SwiftUI, but I feel like I have a lot of code duplication, where things should probably more elegant.
I have some sliders, and some bindings to make sure the values update and a processing method is called when the slider value has changed. Currently I have a binding for each slider:
let vStretch = Binding<Double>(
get: {
self.verticalStretchLevel
},
set: {
self.verticalStretchLevel = $0
applyProcessing("vertical stretch")
}
)
let straighten = Binding<Double>(
get: {
self.straightenLevel
},
set: {
self.straightenLevel = $0
applyProcessing("straighten")
}
)
let vignette = Binding<Double>(
get: {
self.vignetteLevel
},
set: {
self.vignetteLevel = $0
applyProcessing("vignette")
}
)
This is ugly right? Can anyone point me to some article, site or give me some advice on how to make this right?
Thanks in advance!
I ended up making a view for a slider, which also has the binding:
//
// SliderView.swift
//
// Created by Michel Storms on 07/12/2020.
//
import SwiftUI
struct SliderView: View {
var runFilters: () -> Void // links to function from parent view
let label: String
let level: Binding<Double>
var body: some View {
if label.count == 1 {
HStack {
Text(label).frame(width: sliderValueWidth)
Slider(value: intensity(for: level) )
TextField("", value: level, formatter: sliderFormatter(), onCommit: { self.runFilters() } ).frame(width: sliderValueWidth)
}
.onLongPressGesture{ level.wrappedValue = 0.5 ; self.runFilters() }
.onTapGesture(count: 2, perform: { level.wrappedValue = 0.5 ; self.runFilters() })
.frame(height: sliderTextSize)
.font(.system(size: sliderTextSize))
} else {
VStack {
HStack{
Text(label)
Spacer()
TextField("", value: level, formatter: sliderFormatter(), onCommit: { self.runFilters() } ).frame(width: sliderValueWidth)
}
.frame(height: sliderTextSize)
.font(.system(size: sliderTextSize))
Slider(value: intensity(for: level) ).frame(height: sliderTextSize)
}
.onLongPressGesture{ level.wrappedValue = 0.5 ; self.runFilters() }
.onTapGesture(count: 2, perform: { level.wrappedValue = 0.5 ; self.runFilters() })
.frame(height: sliderHeight)
.font(.system(size: sliderTextSize))
}
}
func intensity(for sliderLevel: Binding<Double>) -> Binding<Double> {
Binding<Double>(
get: { sliderLevel.wrappedValue },
set: { sliderLevel.wrappedValue = $0; self.runFilters() }
)
}
func sliderFormatter() -> NumberFormatter {
let formatter = NumberFormatter()
formatter.allowsFloats = true
formatter.numberStyle = .decimal
formatter.alwaysShowsDecimalSeparator = true
formatter.maximumFractionDigits = 2
formatter.minimumFractionDigits = 2
formatter.decimalSeparator = "."
return formatter
}
}
... and then display the sliders like this:
var body: some View {
return List {
VStack {
SliderView(runFilters: self.runFilters, label: "Exposure", level: $appState.exposureLevel)
SliderView(runFilters: self.runFilters, label: "Contrast", level: $appState.contrastLevel)
SliderView(runFilters: self.runFilters, label: "Brightness", level: $appState.brightnessLevel)
SliderView(runFilters: self.runFilters, label: "Shadows", level: $appState.shadowsLevel)
SliderView(runFilters: self.runFilters, label: "Highlights", level: $appState.highlightsLevel)
SliderView(runFilters: self.runFilters, label: "Vibrance", level: $appState.vibranceLevel)
SliderView(runFilters: self.runFilters, label: "Saturation", level: $appState.saturationLevel)
SliderView(runFilters: self.runFilters, label: "Clarity", level: $appState.clarityLevel)
SliderView(runFilters: self.runFilters, label: "Black Point", level: $appState.blackpointLevel)
if debug {
SliderView(runFilters: self.runFilters, label: "DEBUG / TEST", level: $appState.debugAndTestSliderLevel)
}
}
.font(.system(size: sliderTextSize))
Related
I have a TextField on a .sheet that is acting up and I don't know why or how to solve it. When you type a long text, the cursor reaches the end of the TextField (the right end), and it stays there. You can continue to write (and the text will get entered) but you can't see it, nor scroll to it, nor move the cursor.
Is this a default behavior? If not, how do I fix it? What am I not doing right? I haven't found anything either here at SO, or on Apple's forums or various SwiftUI sites.
Here's how it looks
And here's the code for the view:
struct SheetViewNew: View {
#EnvironmentObject private var taskdata: DataModel
#Binding var isVisible: Bool
#Binding var enteredText: String
#Binding var priority: Int
var priorities = [0, 1, 2]
var body: some View {
VStack {
Text("Enter a new task")
.font(.headline)
.multilineTextAlignment(.center)
TextField("New task", text: $enteredText)
.textFieldStyle(RoundedBorderTextFieldStyle())
.font(.system(size: 14.0))
.onSubmit{
if enteredText == "" {
self.isVisible = false
return
}
taskdata.tasks.append(Task(id: UUID(), title: enteredText, priority: priority))
}
Picker("Priority", selection: $priority) {
Text("Today").tag(0)
Text("Important").tag(1)
Text("Normal").tag(2)
}.pickerStyle(RadioGroupPickerStyle())
Spacer()
HStack {
Button("Cancel") {
self.isVisible = false
self.enteredText = ""
}
.keyboardShortcut(.cancelAction)
.buttonStyle(DefaultButtonStyle())
Spacer()
Button("Add Task") {
if enteredText == "" {
self.isVisible = false
return
}
taskdata.tasks.append(Task(id: UUID(), title: enteredText, priority: priority))
taskdata.sortList()
self.isVisible = false
}
.keyboardShortcut(.defaultAction)
.buttonStyle(DefaultButtonStyle())
}
}
.frame(width: 300, height: 200)
.padding()
}
}
In the first section there is no problem, but in the second section there is a problem in my animation. using the same view in the first section and the second section.
In the second section, the text shifts to the right and left. But I don't want that. I want the text to remain stable just like in the first section.
SectionView:
(option == 1) = image on the left, text on the right.
(option == 2) = image on the right, text on the left.
struct SectionView: View {
var title: String
var imageView: AnyView
var description: String
var option: Int
var body: some View {
Group {
if option == 1 {
HStack {
ZStack {
Rectangle()
.frame(width: 125, height: 120)
.foregroundColor(Color(UIColor.secondarySystemBackground))
.cornerRadius(40, corners: [.topRight, .bottomRight])
imageView
}
Spacer()
VStack {
Text(title)
.font(.system(size: 14, weight: .bold, design: .rounded))
Text(description)
.multilineTextAlignment(.center)
.font(.system(size: 12, weight: .medium, design: .rounded))
.padding(.trailing, 5)
}
.frame(height: 120)
.foregroundColor(Color(UIColor.label))
}
} else if option == 2 {
HStack {
VStack {
Text(title)
.font(.system(size: 14, weight: .bold, design: .rounded))
Text(description)
.multilineTextAlignment(.center)
.font(.system(size: 12, weight: .medium, design: .rounded))
.padding(.leading, 5)
}
.frame(height: 120)
.foregroundColor(Color(UIColor.label))
Spacer()
ZStack {
Rectangle()
.frame(width: 125, height: 120)
.foregroundColor(Color(UIColor.secondarySystemBackground))
.cornerRadius(40, corners: [.topLeft, .bottomLeft])
imageView
}
}
}
}
}
}
MainViewModel:
I am adding sections here.
As you can see I'm using the same view in the two CategorySections I've added. In the first section, the animation is displayed properly, but while the animation is displayed in the second section, the text shifts to the right and left. What is the reason for this?
class MainViewModel: ObservableObject {
#Published var section: [CategorySection] = []
init() {
getSections()
}
func getSections() {
section = [
CategorySection(title: "Trafik Levhaları", imageView: AnyView(PoliceSignsSectionIconView()), description: "Trafik levhaları trafiğe çıkan her sürücünün mutlaka dikkat etmesi gereken uyarı işaretleridir. Trafik kurallarını ve gerekliliklerini uygulama açısından önemli bir yeri olan trafik işaretleri mutlaka izlenmeli. Bu bölümde trafik levha işaretlerinin anlamlarını öğrenebilirsiniz.", option: 1, page: TrafficSignsView()),
CategorySection(title: "Polis İşaretleri", imageView: AnyView(PoliceSignsSectionIconView()), description: "Trafik polisleri gerektiği durumlarda yolda trafiğin kontrolünü sağlar. Çoğu yerde karşımıza çıkabilecek bu durumda trafik polisi işaretlerinin anlamını bilmemiz gerekmektedir. Bu bölümde polis işaretlerinin anlamlarını öğrenebilirsiniz.", option: 2, page: PoliceSignsView()),
....
]
}
}
CategorySection Model:
struct CategorySection {
let id = UUID()
let title: String
let imageView: AnyView
let description: String
let option: Int
let page: Any
}
Using SectionView:
I am using the sections I added in MainViewModel here.
struct MainView: View {
#ObservedObject var mainViewModel: MainViewModel = MainViewModel()
#EnvironmentObject var publishObjects: PublishObjects
var body: some View {
NavigationView {
ScrollView(showsIndicators: false) {
VStack(spacing: 30) {
ForEach(mainViewModel.section, id: \.id) { section in
NavigationLink(
destination: AnyView(_fromValue: section.page)?.navigationBarTitleDisplayMode(.inline),
label: {
SectionView(title: section.title, imageView: section.imageView, description: section.description, option: section.option)
})
}
}
}
.navigationBarTitle("Ehliyet Sınavım")
.onAppear {
}
}
.accentColor(Color(UIColor.label))
}
}
PoliceSignsSectionIconView(Animation here):
I applied my animations on this page.
struct PoliceSignsSectionIconView: View {
#State var isDrawing: Bool = true
let pathBounds = UIBezierPath.calculateBounds(paths: [.policePath1, .policePath2, .policePath3, .policePath4, .policePath5, .policePath6, .policePath7, .policePath8, .policePath9, .policePath10])
var body: some View {
Text("Test Icon")
.frame(width: 75, height: 70)
.opacity(isDrawing ? 1 : 0)
.onAppear {
self.isDrawing.toggle()
}
.animation(.easeInOut(duration: 2).repeatForever(autoreverses: true))
}
}
I solved my problem. I don't know why my problem was solved when I added List object.
MainView:
struct MainView: View {
#ObservedObject var mainViewModel: MainViewModel = MainViewModel()
#EnvironmentObject var publishObjects: PublishObjects
var body: some View {
NavigationView {
List(mainViewModel.section, id: \.id) { section in
NavigationLink(
destination: AnyView(_fromValue: section.page)?.navigationBarTitleDisplayMode(.inline),
label: {
SectionView(title: section.title, imageView: section.imageView, description: section.description, sectionStatus: section.sectionStatus)
})
}
.navigationBarTitle("Ehliyet Sınavım")
}
.accentColor(Color(UIColor.label))
}
}
I trying to learn the new SwiftUI coding technique. I would like to click a button that will add elements to an array that is a #State variable. The button is the buttonclick function. The array is the push_group_row / push_group_array. I get an error in the append statement.
Eventually the buttonclick will access a database to build an array with more row, but for now I just trying to add one row.
Code:
import SwiftUI
import Combine
var gordon: String = "xxxxxx"
struct Result: Codable {
let trackId: Int
let trackName: String
let collectionName: String
}
struct Response: Codable {
var results: [Result]
}
struct Pokemon: Identifiable {
let id: Int
let name: String
let type: String
let color: Color
}
struct push_group_row {
let id: Int
let code: String
let title: String
}
struct ContentView: View
{
#State private var results = [Result]()
#State var pokemonList = [
Pokemon(id: 0, name: "Charmander", type: "Fire", color: .red),
Pokemon(id: 1, name: "Squirtle", type: "Water", color: .blue),
Pokemon(id: 2, name: "Bulbasaur", type: "Grass", color: .green),
Pokemon(id: 3, name: "Pikachu", type: "Electric", color: .yellow),]
#State var push_group_array = [push_group_row(id: 0, code: "code12", title: "POAFire")]
var body: some View
{
NavigationView
{
VStack(alignment: . leading){
Button(action: {
// What to perform
self.buttonclick()
}) {
// How the button looks like
Text("clickme")
.background(Color.purple)
.foregroundColor(.white)
}
List(results, id: \.trackId)
{item in
NavigationLink(destination: DetailView(lm: String(item.trackId)))
{
VStack(alignment: .leading)
{
Text(String(item.trackId))
Text(item.trackName)
.font(.headline)
Text(item.collectionName)
Text(gordon)
}
}
}
List(self.pokemonList)
{ pokemon in
HStack
{
Text(pokemon.name)
Text(pokemon.type).foregroundColor(pokemon.color)
}
}
List(push_group_array, id: \.id)
{ pg_item in
HStack
{
Text(String(pg_item.id))
Text(pg_item.code)
}
}
.onAppear(perform: self.loaddata)
}
.navigationBarTitle("x")
.navigationBarItems(
trailing: Button(action: addPokemon, label: { Text("Add") }))
Spacer()
}
}
func addPokemon() {
if let randomPokemon = pokemonList.randomElement() {
pokemonList.append(randomPokemon)
}
}
// *************************** below is the add arrat code
func buttonclick() {
let newCode = "First"
let newTitle = "Second"
push_group_array.append(id: 1, code: newCode, title: newTitle)
}
func loaddata()
{
print("loaddata")
guard let url = URL(string: "https://itunes.apple.com/search?term=taylor+swift&entity=song")
else
{
print("Invalid URL")
return
}
var urlData: NSData?
let request = URLRequest(url: url)
URLSession.shared.dataTask(with: request) { data, response, error in
if let data = data
{
if let decodedResponse = try? JSONDecoder().decode(Response.self, from: data)
{
DispatchQueue.main.async
{
urlData = data as NSData?
self.results = decodedResponse.results
print(self.results)
print(urlData ?? "urlData_Defaultvalue")
}
return
}
}
print("Fetch failed: \(error?.localizedDescription ?? "Unknown error")")
}.resume()
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
You need to push the object rather than 3 values
push_group_array.append(push_group_row(id: 1, code: newCode, title: newTitle))
Ive make a mini app with just a button and a picker and the idea is to have a done button above the picker so once ive chosen a value i can press done and the picker will close.
I am aware if you click the "click me" button it will open and if you click it again close the picker but im looking for a button that appears with the picker and disapears with the clicker when clicked.
Almost like a toolbar above the picker with a done button
#State var expand = false
#State var list = ["value1", "value2", "value3"]
#State var index = 0
var body: some View {
VStack {
Button(action: {
self.expand.toggle()
}) {
Text("Click me \(list[index])")
}
if expand {
Picker(selection: $list, label: EmptyView()) {
ForEach(0 ..< list.count) {
Text(self.list[$0]).tag($0)
}
}.labelsHidden()
}
}
The third image is what im trying to accomplish and the first 2 are what ive currently got
Thank you for your help
Add a Button to the if-clause:
if expand {
VStack{
Button(action:{self.expand = false}){
Text("Done")
}
Picker(selection: $list, label: EmptyView()) {
ForEach(0 ..< list.count) {
Text(self.list[$0]).tag($0)
}
}.labelsHidden()
}
}
Here is an approach how I would do this... of course tuning is still possible (animations, rects, etc.), but the direction of idea should be clear
Demo of result:
Code:
struct ContentView: View {
#State var expand = false
#State var list = ["value1", "value2", "value3"]
#State var index = 0
var body: some View {
VStack {
Button(action: {
self.expand.toggle()
}) {
Text("Click me \(list[index])")
}
if expand {
Picker(selection: $index, label: EmptyView()) {
ForEach(0 ..< list.count) {
Text(self.list[$0]).tag($0)
}
}.labelsHidden()
.overlay(
GeometryReader { gp in
VStack {
Button(action: {
self.expand.toggle()
}) {
Text("Done")
.font(.system(size: 42))
.foregroundColor(.red)
.padding(.vertical)
.frame(width: gp.size.width)
}.background(Color.white)
Spacer()
}
.frame(width: gp.size.width, height: gp.size.height - 12)
.border(Color.black, width: 8)
}
)
}
}
}
}
Also this would work (especially if you're doing it outside of a Vstack)...
// Toolbar for "Done"
func createToolbar() {
let toolBar = UIToolbar()
toolBar.sizeToFit()
// "Done" Button for Toolbar on Picker View
let doneButton = UIBarButtonItem(title: "Done", style: .plain, target:
self, action: #selector(PageOneViewController.dismissKeyboard))
toolBar.setItems([doneButton], animated: false)
toolBar.isUserInteractionEnabled = true
// Makes Toolbar Work for Text Fields
familyPlansText.inputAccessoryView = toolBar
kidsOptionText.inputAccessoryView = toolBar
ethnicityText.inputAccessoryView = toolBar
}
And be sure to call createToolbar() and you're done!
struct ContentView: View {
var colors = ["Red", "Green", "Blue"]
#State private var selectedColor = 0
var body: some View {
NavigationView {
Form {
Section {
Picker(selection: $selectedColor, label: Text("Color")) {
ForEach(0 ..< colors.count) {
Text(self.colors[$0])
}
}
}
}
}
}
}
This has some form specific picker behavior where it opens up inline with no button hiding needed.
This is a simple example of infinity scroll. How add infinity to Up scroll
and insert rows at beginning:
rows.insert(contentsOf: Array(repeating: "Item 0", count: 20), at: 0)
Like apple do this trick in calendar.
struct Screen: View {
#State var rows: [String] = Array(repeating: "Item", count: 20)
private func getNextPageIfNecessary(encounteredIndex: Int) {
guard encounteredIndex == rows.count - 1 else { return }
rows.append(contentsOf: Array(repeating: "Item", count: 20))
}
var body: some View {
...
List(0..<rows.count, id: \.self) { index in
Text(verbatim: self.rows[index])
.onAppear {
self.getNextPageIfNecessary(encounteredIndex: index)
}
}
Here is the simplest idea. It is scratchy and of course it can be tuned and improved (like cancelling, better timing, etc.), but the idea remains... Hope it will be helpful somehow.
struct TestInfinityList: View {
#State var items: [Int] = Array(100...120)
#State var isUp = false
var body: some View {
VStack {
HStack {
Button(action: { self.isUp = true }) { Text("Up") }
Button(action: { self.isUp = false }) { Text("Down") }
}
Divider()
List(items, id: \.self) { item in
Text("Item \(item)")
}
.onAppear {
DispatchQueue.main.async {
self.goNext()
}
}
}
}
func goNext() {
self.isUp ? self.moveUp() : self.moveDown()
}
func moveUp() {
self.items.insert(self.items.first! - 1, at: 0)
DispatchQueue.main.asyncAfter(deadline: .now() + 0.4) {
self.goNext()
}
}
func moveDown() {
_ = self.items.removeFirst()
DispatchQueue.main.asyncAfter(deadline: .now() + 0.4) {
self.items.append(self.items.last! + 1)
self.goNext()
}
}
}