NEW: My new book Pro SwiftUI is out now – level up your SwiftUI skills today! >>

SOLVED: My first SwiftUI App

Forums > SwiftUI

Im starting to learn SwiftUI a month ago and now i create an App. Would anyone be willing to watch my code and make a review? I posted code in GitHub (https://github.com/Spich3000/stonksProfitCalculator-1.01).

Thanks in advance!

   

Hi Dimitry

Welcome to this forum and good luck learning SwiftUI. I think Hacking With Swift is a really great resource. Keep on learning!

I looked at your source code and have the following suggestions for improvements:

  • Try splitting up your app into multiple views. Currently you only have ContentView defined. All your tabs (you have 4) should go into their separate Views along with the correspinding @State variables (perhaps even put them in their own file). You can try to the content of each tab as if it was a single screen.

  • You could write a ClearableTextField view and reuse it wherever you have

    TextField(..., text: ...)
      .modifier(TextFieldClearButton(text: ...))

    Basically, whenever you see code duplicated multiple time, you might ask "can I somehow make a component which can be reused?" You already did this by writing some ViewModifiers, but in this case a dedicated View would be much better.

  • Your approach to localization is somehow "unique" :-). Do you know by heart what localization(index: 6) or do you find yourself looking up the entry in the array? To improve on this, I recommend you: decide what's your development language and write all the localizable texts as plain String litterals in your code. TextField("Enter quantity of token", text: $quantityOfToken) instead of TextField(localization(index: 6), text: $quantityOfToken)

    Then add a Localizable.strings source to your project with the strings which are going to be shown in the UI:

    "Enter quantity of token" = "Enter quantity of token";

    See also How to localize your iOS app

1      

@pd95 Thank you so much! I will try to fix this as soon as possible. This is really helpfull cause i dont know how to write code right. Hope i can figure out how to handle it :3 Thank you again!

   

Hacking with Swift is sponsored by Play

SPONSORED Play is the first native iOS design tool created for designers and engineers. You can install Play for iOS and iPad today and sign up to check out the Beta of our macOS app with SwiftUI code export. We're also hiring engineers!

Click to learn more about Play!

Sponsor Hacking with Swift and reach the world's largest Swift community!

Hi @Spich3000

Sorry that I took awhile to reply, was just see what would be take. Here my finding.

ContentView

Firstly as @pd95 say that you should refactor out the "Tabs" into separate file and view so your ContentView should look like this.

struct ContentView: View {
    @SceneStorage("selectedView") var selectedView = 1

    @FocusState private var focus: Bool

    var body: some View {
        TabView(selection: $selectedView) {
            SellPriceView()
                .tabItem {
                    Label("Sell Price", systemImage: "dollarsign.circle")
                }
                .tag(1)

            DifferenceView()
                .tabItem {
                    Label("Difference", systemImage: "align.vertical.bottom")
                }
                .tag(2)

            AveragePriceView()
                .tabItem {
                    Label("Average Price", systemImage: "chart.xyaxis.line")
                }
                .tag(3)

            SettingsView()
                .tabItem {
                    Label("Settings", systemImage: "gear")
                }
                .tag(4)
        }
        .onAppear() {
            UITabBar.appearance().barTintColor = .gray
        }
        .toolbar {
            ToolbarItemGroup(placement: .keyboard) {
                Spacer()
                Button("Done") {
                    focus = false
                }
                .foregroundColor(.yellow)
            }
        }
        .accentColor(.black)
    }
}

A couple of thing used SceneStorage so that when a user closes the app when reopen at the same tab! Used Label instead of Text and Image and used the String name (explain later about Localizable).

View Modifier

If the ViewModifier do not take any parmeter then if you put an extension with a computed property like

fileprivate struct Title: ViewModifier {
    func body(content: Content) -> some View {
        content
            .textFieldStyle(RoundedBorderTextFieldStyle())
            .multilineTextAlignment(.center)
            .colorInvert()
            .accentColor(.gray)
            .padding(.horizontal, 35.0)
            .padding(.bottom, 20.0)
            .shadow(radius: 2)
            .keyboardType(.decimalPad)
    }
}

extension View {
    var title: some View {
        modifier(Title())
    }
}

or if take parmeter use a method like

fileprivate struct TextFieldClearButton: ViewModifier {
    @Binding var number: Double // note this has change will explain later

    func body(content: Content) -> some View {
        ZStack(alignment: .trailing) {

            content

            if number != 0 {
                Button(
                    action: { number = 0.0 },
                    label: {
                        Image(systemName: "multiply.circle")
                            .padding(.trailing)
                    }
                )
            }
        }
    }
}

extension View {
    func textFieldClearButton(for number: Binding<Double>) -> some View {
        modifier(TextFieldClearButton(number: number))
    }
}

Then you can use like

TextField("Enter quantity of token", value: $quantityOfToken, format: .number) // note this has change will explain later
      .textFieldClearButton(for: $quantityOfToken)
      .title
      .focused($focus)

TextFields

I was wondering what the convert a string replacing "," with ".". I thought it was maybe to do with how your enter decimal place in Russia, however if you turn the TextFields to accept Double and use the TextField as above. In UK/USA we used comma to separate thousands so if a user enters 1,000.20 your method would crash the app.

Display Text

You can use a format to display Number (percent and currency correctly)

Text("\(boughtValue, format: .currency(code: "USD"))")
      .foregroundColor(.black)
      .padding()
Text("\(percentageDifference, format: .percent)")
    .foregroundColor(.black)

Localizable

As @pd95 says you can set Localizable file so you end up with file like

"Sell Price" = "Цена продажи";
"Difference" = "Разница";
"Average Price" = "Средняя цена";
"Settings" = "Средняя цена";
"Clear" = "Удалить";
"Done" = "Готово";
"Enter quantity of token" = "Введи количество монет";
"Enter bought price" = "Введи стоимость";
"Enter profit you want to receive %" = "Введи желаемый профит %";
"Your bought value:" = "Сумма покупки:";
"Set limit order at:" = "Поставь ордер по цене:";
"Your profit:" = "Профит составит:";
"Sell price included maker/taker fee %@" = "Цена продажи учитывает комиссию %@";
"Enter sell value" = "Введи сумму продажи";
"Enter bought value" = "Введи сумму покупки";
"Difference is:" = "Разница составляет:";
"Enter amount of token: first buy" = "Введи количество монет: покупка №1";
"Enter price: first buy" = "Введи цену: покупка №1";
"Enter amount of token: second buy" = "Введи количество монет: покупка №2";
"Enter price: second buy" = "Введи цену: покупка №2";
"Your average price is:" = "Средняя цена:";
"This is app for my crypto-blog in Telegram. Stay tuned for new features!" = "Это приложение для моего крипто-канала в Telegram. Cледите за новостями!";
"Telegram:" = "Telegram:";
"Feel free for donate (BTC):" = "Донаты приветствуются (ВТС):";
"About:" = "Информация:";
"Copied!" = "Адрес скопирован!";
"Thank You!" = "Спасибо, Товарищ!";

/*The extra needed for Localizable*/
"%@" = "%@";

PS You can run the App the in Russain by Click in "Product" then hold Option and the click "Run" then change the App Language to Russain. This then will show Russain in the Previews.

I have stopped there but i would look at refactor the "Tabs" to make them smaller and also look at moving the logic out of the View to thier own file.

Sorry this is so long but the all the changes are on my GitHub

1      

@NigelGee Wow! Great thanks for you! Thats a lot of work, thank you so much :3

I finished today with localization: add Russian and Ukranian, but how can i select language manualy? When i build app it shows language based on a system region. But i want English.

  1. Can you explain about refactor code to a multiple Files in Xcode? It is for easy reading code? Or its one of the patern of programming?

  2. I tried with: format: .number it didn't work in my code. I would try with it again its really simplified formulas.

  3. You are right! This is for decimalPad in Russian location. But how can i handle coma in this case?

    // Replace "," with "." function

    func convert(text: String) -> String { let conversion = text.replacingOccurrences(of: ",", with: ".") return conversion }

  4. Yes i got it with localization, but for what is this ("%@" = "%@";)? I dont clearly understand what for is it? This is format or something?

I was searching for a week to find review and finaly found this beautiful site! Really hard learning alone, but its so cool to find out how all this things works. Thank you so much for your help <3

   

@Spich3000 I will try to give some answer for your points.

  1. Putting code into files make it easy to find the code you want. (think of it as a file cabinet, if everything was in one file then you would take along time to find the piece of paper that you needed). It also make each code sample smaller so easy to read.
  2. You need to use TextField that use value not text. If you just added format: .number to the end of your TextField then it will not work. Also the @State var need to be a number (Double or Int)
  3. You do not need to handle comma as if the App Language is set to Russian then Apple handle it.
  4. "%@" = "%@" This is for the Text like Text("\(percentageDifference, format: .percent)") etc. However if you had something like Text("The pencentage is \(percentageDifference, format: .percent)") then you would need this
    "The pencentage is %@" = "The pencentage is  %@"

Regarding the English version best is to add the English Localization (same as when you added Ukranian) then when you do Option Run you can select the English version and this will stay until you change it.

PS if you run a real device you can select the Language that the App run in to.

I would recomend that you do 100 Days of SwiftUI, It a great course and some of these question might be clearer.

PSS you can alway download my project from GitHub and look at the code I did if something does not work

Have fun and happy coding

1      

@NigelGee Oh i got it. I will try see into with your code. Already subcribe to your Git acc :-) Thank you so much!

   

@NigelGee How can i bind FocusState variable in ContentView with other views? I see in concole that when i press button in toolbar, focus changed to false, but its not going to other views where i need to close keyboard.

   

I handle it with UIKit:

import SwiftUI
import UIKit

struct HideKeyboard: ViewModifier {
    func body(content: Content) -> some View {
        content
        Button("Done") {
            UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
        }
    }
}

extension View {
    var hideKeyboard: some View {
        modifier(HideKeyboard())
    }
}

And then add .hideKeyboard to button action in toolbar. Now it all works. Im starting to try remove my convert func and testing app <3

   

I tried to change format to .number in TextField inputs and it's not working right:

  1. You are always starting write number with 0. Always needed to delete it at first.
  2. You cant see the keyTitle, cause of "0".
  3. ClearButton doesnt work properly, value always stays while editting.

I tried my convert function on a UK region (switch my phone to United Kindom region, and British English language). App works fine like i planed! Formulas are looking more chaotic with this function and convert to Double value, but i hope this is fine.

   

Hacking with Swift is sponsored by Play

SPONSORED Play is the first native iOS design tool created for designers and engineers. You can install Play for iOS and iPad today and sign up to check out the Beta of our macOS app with SwiftUI code export. We're also hiring engineers!

Click to learn more about Play!

Sponsor Hacking with Swift and reach the world's largest Swift community!

Reply to this topic…

You need to create an account or log in to reply.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.