inspired by Esat GÖZCÜ CustomTextField 🙇🏻♂️
Cassette is an Custom View library written in SwiftUI.
- Requirements
- iOS 13.0+
- Installation
- features
.package(url: "https://github.com/ios-carki/Cassette.git", from: "v1.1.10")
I will guide you on how to use BtnCassette(Button) in SwiftUI.
- Basic
struct TestView: View {
var body: some View {
VStack(spacing: 20) {
// Normal Button
BtnCassette(buttonMode: .normal(text: "Normal Button"))
// Image Button - Left Positioned Image
BtnCassette(buttonMode: .imageButton(text: "Leading Image Button", imageDirection: .leading, imageType: .system, imageName: "globe"))
// Image Button - Right Positioned Image
BtnCassette(buttonMode: .imageButton(text: "Trailing Image Button", imageDirection: .trailing, imageType: .system, imageName: "person"))
// Binding Text Button
BtnCassette(buttonMode: .bindingText(text: .constant("Binding Text Button")))
}.padding(.horizontal, 16)
}
}
- Customizing
struct TestView: View {
@StateObject private var viewModel = TestViewModel()
var body: some View {
VStack(spacing: 20) {
BtnCassette(buttonMode: .normal(text: "Normal Button"))
.setTitleTextColor(color: .blue)
.setTitleTextFont(font: .title2)
.setCornerRadius(20)
.setBorderWidth(width: 2)
.setBorderColor(color: .blue)
.setButtonHeight(height: 80)
.setBackgroundColor(color: .cyan)
.setDisableBackgroundColor(color: .gray)
.setDisable(disable: $viewModel.buttonDisable)
.click {
viewModel.buttonDisable.toggle()
}
}.padding(.horizontal, 16)
}
}
If you set default value, you don't need to set customizing function every views
- For Storyboard Interface Project
import UIKit
import Cassette
@main
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
// Here is important!
let shared = BtnCassetteConfig.shared
shared.defaultButtonTextColor = .blue
shared.defaultButtonTextFont = .title2
shared.defaultButtonCornerRadius = 20
shared.defaultBorderWidth = 2
shared.defaultBorderColor = .blue
shared.defaultButtonHeight = 80
shared.defaultButtonBackgroundColor = .cyan
shared.defaultbuttonDisableBackgroundColor = .gray
return true
}
.
.
.
}
- For SwiftUI Interface Project
import SwiftUI
import Cassette
@main
struct MyProjectApp: App {
// Here is important!
init() {
let shared = BtnCassetteConfig.shared
shared.defaultButtonTextColor = .blue
shared.defaultButtonTextFont = .title2
shared.defaultButtonCornerRadius = 20
shared.defaultBorderWidth = 2
shared.defaultBorderColor = .blue
shared.defaultButtonHeight = 80
shared.defaultButtonBackgroundColor = .cyan
shared.defaultbuttonDisableBackgroundColor = .gray
}
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
- With Using Default Variable
import SwiftUI
import Cassette
struct TestView: View {
@StateObject private var viewModel = TestViewModel()
var body: some View {
VStack(spacing: 20) {
BtnCassette(buttonMode: .normal(text: "Normal Button"))
.setDisable(disable: $viewModel.buttonDisable)
.click {
viewModel.buttonDisable.toggle()
}
}.padding(.horizontal, 16)
}
}
The result is the same as above, but the code is much shorter.
- Basic
![스크린샷 2023-09-12 오후 5 26 51](https://private-user-images.githubusercontent.com/44957712/267264311-a66f74e5-ef57-4360-b57b-75c54915340a.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MjQ4MTE2NjksIm5iZiI6MTcyNDgxMTM2OSwicGF0aCI6Ii80NDk1NzcxMi8yNjcyNjQzMTEtYTY2Zjc0ZTUtZWY1Ny00MzYwLWI1N2ItNzVjNTQ5MTUzNDBhLnBuZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNDA4MjglMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjQwODI4VDAyMTYwOVomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPWE4MGE0NDUwMTA0YzEyYWIxZTczNTk4ODMwOTFkZDE1YTIzZGU1NjMzMzQ3OWVlOGM4OGI0ZjBkZDQzZDg1ZDYmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0JmFjdG9yX2lkPTAma2V5X2lkPTAmcmVwb19pZD0wIn0.ZQfAeKJNHLOKeGblsjJEbZnCGT-brCr2_-3OmRKJ9F0)
//normal
SwitchCassette(switchType: .normal(text: Binding<String>?, isOn: Binding<Bool>, spacing: CGFloat?))
//rectangle
SwitchCassette(switchType: .rectangle(text: Binding<String>?, isOn: Binding<Bool>, spacing: CGFloat?))
- Customizing
import SwiftUI
import Cassette
struct ContentView: View {
@StateObject private var viewModel = ContentViewModeel()
var body: some View {
VStack(alignment: .leading, spacing: 8) {
HStack {
SwitchCassette(text: $viewModel.text, isOn: $viewModel.isOn, spacing: 12)
.setSwitchControllerColor(color: .blue)
.setTitleTextOffColor(color: .blue)
.setTitleTextOnColor(color: .red)
.setSwitchOnBackgroundColor(color: .indigo)
.insertSpacer(true)
}
}
.padding(.horizontal, 16)
}
}
![스크린샷 2023-09-08 오후 2 44 24](https://private-user-images.githubusercontent.com/44957712/266513191-701470e8-ee56-4f7e-917c-c5dc991dd832.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MjQ4MTE2NjksIm5iZiI6MTcyNDgxMTM2OSwicGF0aCI6Ii80NDk1NzcxMi8yNjY1MTMxOTEtNzAxNDcwZTgtZWU1Ni00ZjdlLTkxN2MtYzVkYzk5MWRkODMyLnBuZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNDA4MjglMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjQwODI4VDAyMTYwOVomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPTAzMDE4ZGVhYmI0MjIyYmY3MWY2YmVlMzNmZjZlMjY5ZWVlMjI4OWU1MzJiNmVlODUwZTdlMjhiNjFjMGQwYjcmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0JmFjdG9yX2lkPTAma2V5X2lkPTAmcmVwb19pZD0wIn0.1b0NL6bp8iS0O2LEMeuVdgyObPX7pSMXLgEJaD5HhLA)
import SwiftUI
import Cassette
struct ContentView: View {
@StateObject private var viewModel = ContentViewModeel()
var body: some View {
VStack(alignment: .leading, spacing: 8) {
HStack {
SwitchCassette(text: $viewModel.text, isOn: $viewModel.isOn, spacing: 12)
.setSwitchControllerColor(color: .blue)
.setTitleTextOffColor(color: .blue)
.setTitleTextOnColor(color: .red)
.setSwitchOnBackgroundColor(color: .indigo)
.insertSpacer(false) // here is changed!
}
}
.padding(.horizontal, 16)
}
}
![스크린샷 2023-09-08 오후 2 45 46](https://private-user-images.githubusercontent.com/44957712/266513396-8b458160-25f2-47a4-9db2-7cc4f434746d.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MjQ4MTE2NjksIm5iZiI6MTcyNDgxMTM2OSwicGF0aCI6Ii80NDk1NzcxMi8yNjY1MTMzOTYtOGI0NTgxNjAtMjVmMi00N2E0LTlkYjItN2NjNGY0MzQ3NDZkLnBuZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNDA4MjglMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjQwODI4VDAyMTYwOVomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPWVjZDM0MmExODBhNjQ1Mzk3OTdkNzk2YzQ1MDFiMTZlNmUwNzFkZjQwNjBkNzdkOTUzZTFjYmNhNmU1YTcwMTEmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0JmFjdG9yX2lkPTAma2V5X2lkPTAmcmVwb19pZD0wIn0.glxdizeoGsrzbRFIA12SKl4hokA4YLYR2DeW7v0CYEM)
import SwiftUI
import Cassette
struct ContentView: View {
@StateObject private var viewModel = ContentViewModeel()
var body: some View {
VStack(alignment: .leading, spacing: 8) {
HStack {
SwitchCassette(text: $viewModel.text, isOn: $viewModel.isOn, spacing: 100) // spacing value changed!
.setSwitchControllerColor(color: .blue)
.setTitleTextOffColor(color: .blue)
.setTitleTextOnColor(color: .red)
.setSwitchOnBackgroundColor(color: .indigo)
.insertSpacer(false)
}
}
.padding(.horizontal, 16)
}
}
![스크린샷 2023-09-08 오후 2 46 57](https://private-user-images.githubusercontent.com/44957712/266513601-b396f564-3a2e-41f6-967a-fb3f1dd1df9d.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MjQ4MTE2NjksIm5iZiI6MTcyNDgxMTM2OSwicGF0aCI6Ii80NDk1NzcxMi8yNjY1MTM2MDEtYjM5NmY1NjQtM2EyZS00MWY2LTk2N2EtZmIzZjFkZDFkZjlkLnBuZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNDA4MjglMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjQwODI4VDAyMTYwOVomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPWYzODYyNmQ4Y2VkZjc0OGM1OTk5M2EwY2ZmN2RhY2M4Nzk3NjhmMjM5NjE5ODUxNjZiZDk1ZjUwY2MxMTY4MzAmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0JmFjdG9yX2lkPTAma2V5X2lkPTAmcmVwb19pZD0wIn0.4_PYkI5veaFWBHhejoFaXMB-O_HFM8qG2Oh3DmwEUAA)
If you set default value, you don't need to set customizing function every views
- For Storyboard Interface Project
import UIKit
import Cassette
@main
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
// Here is important!
let shared = SwitchCassetteConfig.shared
shared.defaultSwitchOffTextColor = .blue
shared.defaultSwitchOnTextColor = .red
shared.defaultSwitchTextFont = .callout
shared.defaultSwitchOnBackgroundColor = .indigo
shared.defaultSwitchOffBackgroundColor = .gray
shared.defaultSwitchControllerColor = .blue
return true
}
.
.
.
}
- For SwiftUI Interface Project
import SwiftUI
import Cassette
@main
struct MyProjectApp: App {
// Here is important!
init() {
let shared = SwitchCassetteConfig.shared
shared.defaultSwitchOffTextColor = .blue
shared.defaultSwitchOnTextColor = .red
shared.defaultSwitchTextFont = .callout
shared.defaultSwitchOnBackgroundColor = .indigo
shared.defaultSwitchOffBackgroundColor = .gray
shared.defaultSwitchControllerColor = .blue
}
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
- With Using Default Variable
import SwiftUI
import Cassette
struct ContentView: View {
@StateObject private var viewModel = ContentViewModeel()
var body: some View {
VStack(alignment: .leading, spacing: 8) {
HStack {
SwitchCassette(text: $viewModel.text, isOn: $viewModel.isOn, spacing: 100)
.insertSpacer(false)
}
}
.padding(.horizontal, 16)
}
}
The result is the same as above, but the code is much shorter.
![스크린샷 2023-09-08 오후 2 54 53](https://private-user-images.githubusercontent.com/44957712/266514984-f85eec65-d4c8-4013-a173-a4d0187d20da.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MjQ4MTE2NjksIm5iZiI6MTcyNDgxMTM2OSwicGF0aCI6Ii80NDk1NzcxMi8yNjY1MTQ5ODQtZjg1ZWVjNjUtZDRjOC00MDEzLWExNzMtYTRkMDE4N2QyMGRhLnBuZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNDA4MjglMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjQwODI4VDAyMTYwOVomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPTA1MGE5NDg4YTIzY2Y3NDlkODZlZDc4YmE5MzY1YWZiMzczMzVlMDkyZjI1ZDIxN2ZhMGFmMmM0NWY5OTc0MjcmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0JmFjdG9yX2lkPTAma2V5X2lkPTAmcmVwb19pZD0wIn0.9hQ9qdEAqzcP39X5E5HvRD_ZzTXlVtmn8P-w1AsaOos)
- Basic
TextFieldCassette(mode: .underLine(placeHolder: "asdfok", text: $viewModel.text, title: "Title", alignment: .leading))
- Customizing
//Customizing
TextFieldCassette(mode: .underLine(placeHolder: "asdfok", text: $viewModel.text, title: "Title", alignment: .leading))
.setReactangleFieldBackgroundColor(.white) // SetBackgroundColor
.setPlaceHolderTextColor(.blue) // SetPlaceHolderTextColor
.setRectangleFieldBorderColor(.black) //SetBorderColor
.setImageButton(imageDirection: .trailing, imageType: .system(color: .black), imageName: "person") {
print("Hello") // SetImageButton
}
- Result
- SetError
struct ContentView: View {
@StateObject private var viewModel = ContentViewModeel()
var body: some View {
VStack(alignment: .leading, spacing: 8) {
VStack {
TextFieldCassette(mode: .underLine(placeHolder: "asdfok", text: $viewModel.text, title: "Title", alignment: .leading))
.setSecureField(isSecure: false)
.setError(isError: $viewModel.isError, errorColor: .red)
}
}
.padding(.horizontal, 16)
}
}
final class ContentViewModeel: ObservableObject {
@Published var isOn: Bool = false
@Published var text: String = "" { didSet { setError()}}
@Published var isError: Bool = false
func setError() {
if text == "abc" {
isError = true
} else {
isError = false
}
}
}
- Error Result
Simulator.Screen.Recording.-.iPhone.14.Pro.Max.-.2023-09-18.at.12.40.08.mp4
If you set default value, you don't need to set customizing function every views
//Title
public var defaultTitleTextFont: Font = .callout
public var defaultTitleTextColor: Color = .black
public var defaultTitleTextSpacing: CGFloat = 8
public var defaultTitleTextAlignment: Alignment = .leading
//Text
public var defaultTextFont: Font = .callout
public var defaultTextColor: Color = .black
public var defaultTextAlignment: Alignment = .leading
//PlaceHolder
public var defaultPlaceHolderTextColor: Color = .gray
public var defaultPlaceHolderTextFont: Font = .callout
public var defaultPlaceHolderTextAlignment: Alignment = .leading
//Design
public var defaultTextFieldHeight: CGFloat = 50
public var defaultRectangleFieldBackgroundColor: Color = .yellow
public var defaultRectangleFieldCornerRadius: CGFloat = 12
public var defaultRectangleFieldBorderColor: Color = .orange
public var defaultRectangleFieldBorderWidth: CGFloat = 2
//Error
public var defaultErrorColor: Color = .red
//SecureField
setSecureField(isSecure: Bool)
//Title
setTitleTextFont(_ font: Font)
setTitleTextColor(_ color: Color)
setTitleTextSpacing(spacing: CGFloat)
setTitleTextAlignment(alignment: Alignment)
//Text
setTextFont(_ font: Font)
setTextColor(_ color: Color)
setTextAlignment(alignment: Alignment)
//PlaceHolder
setPlaceHolderTextColor(_ color: Color)
setPlaceHolderTextFont(_ font: Font)
setPlaceHolderTextAlignment(alignment: Alignment)
//Design
setTextFieldHeight(_ height: CGFloat)
setReactangleFieldBackgroundColor(_ color: Color)
setRectangleFieldCornerRadius(_ radius: CGFloat)
setRectangleFieldBorderColor(_ color: Color)
setRectangleFieldBorderWidth(_ width: CGFloat)
//Error
setError(isError: Binding<Bool>, errorColor: Color)
//Image
setImageButton(imageDirection: ImageDirection, imageType: ImageType, imageName: String, action: (() -> ())?)