[Swift]Active,inActive button[MacOS]

โญ๏ธ ReactExample6 – server + swift

๐Ÿ‘‰๐Ÿป ์ด์ „์— ํฌ์ŠคํŠธํ•œ React ๋ถ€๋ถ„์˜ Active,inActiove๋ฒ„ํŠผ์„ Swift์•ฑ์œผ๋กœ ๊ตฌํ˜„ํ•œ ๋‚ด์šฉ์ž…๋‹ˆ๋‹ค.
This is the implementation of the Active, inActive buttons from the React section I posted previously into a Swift app.

๐Ÿ‘‰๐Ÿป ์„œ๋ฒ„ ๋ถ€๋ถ„์˜ ์ฝ”๋“œ๋Š” Vite+React์˜ Express์„œ๋ฒ„ ๋ถ€๋ถ„๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.
The code for the server part is the same as the Express server part of Vite+React.

๐Ÿ‘‰๐Ÿป Nodejs Express Server

โœ”๏ธserver.js

const express = require("express");
const { Pool } = require("pg");
const cors = require("cors");

const app = express();
app.use(cors());
app.use(express.json());

const pool = new Pool({
  user: "yourID", // ๋ณธ์ธ์˜ ๊ณ„์ •๋ช… / your account name
  host: "localhost",
  database: "postgres", // ๋ณธ์ธ์˜ DB๋ช… / Your DB name
  password: "", // ๋ณธ์ธ์˜ ๋น„๋ฐ€๋ฒˆํ˜ธ / your password
  port: 5432,
});

// ํ˜„์žฌ ์ƒํƒœ ๊ฐ€์ ธ์˜ค๊ธฐ
// Get current state
app.get("/status", async (req, res) => {
  try {
    const result = await pool.query(
      "SELECT is_active FROM button_status WHERE id = 1",
    );
    res.json(result.rows[0]);
  } catch (err) {
    res.status(500).send(err.message);
  }
});

// ์ƒํƒœ ์—…๋ฐ์ดํŠธํ•˜๊ธฐ
// update status
app.post("/toggle", async (req, res) => {
  const { isActive } = req.body;
  try {
    const result = await pool.query(
      "UPDATE button_status SET is_active = $1 WHERE id = 1 RETURNING *",
      [isActive],
    );
    res.json(result.rows[0]);
  } catch (err) {
    res.status(500).send(err.message);
  }
});

app.listen(3000, () => console.log("Server running on http://localhost:3000"));

๐Ÿ‘‰๐Ÿป swift

โœ”๏ธContentView

1) @MainActor :

–UI ๋ณ€๊ฒฝ์€ ๋ฐ˜๋“œ์‹œ ๋ฉ”์ธ ์Šค๋ ˆ๋“œ์—์„œ ํ•ด์•ผํ•ฉ๋‹ˆ๋‹ค.
UI changes must be made on the main thread.

–@MainActor๊ฐ€ ์žˆ์œผ๋ฉด ์ด ํ•จ์ˆ˜๋Š” ๋ฐ˜๋“œ์‹œ ๋ฉ”์ธ ์Šค๋ ˆ๋“œ์—์„œ ์‹คํ–‰๋˜์–ด์•ผ ํ•œ๋‹ค๋Š” ์˜๋ฏธ์ž…๋‹ˆ๋‹ค.
The presence of @MainActor means that this function must be executed on the main thread.

2)Thread

–์“ฐ๋ ˆ๋“œ๋Š” ํ•˜๋‚˜์˜ ํ”„๋กœ๊ทธ๋žจ ์•ˆ์—์„œ ์—ฌ๋Ÿฌ ์ž‘์—…์„ ๋™์‹œ์— ์‹คํ–‰ํ•˜๊ธฐ ์œ„ํ•œ ์‹คํ–‰ ํ๋ฆ„์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค.
A thread is a flow of execution for executing multiple tasks simultaneously within a single program.

–์ฆ‰ ์‹คํ–‰ ํ๋ฆ„์„ ๋ถ„๋ฆฌํ•ด์„œ ๋™์‹œ์— ์ง„ํ–‰ํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋งŒ๋“œ๋Š” ๊ฒƒ์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค.
That means separating the execution flow so that it can proceed concurrently.

import SwiftUI

struct ContentView: View {
    
    @State private var isActive: Bool = false
    private let baseURL = "http://192.168.0.100:3000" // your ip address
    
    var body: some View {
        VStack(spacing: 20) {
            
            Text("ํ˜„์žฌ ์ƒํƒœ / Current Status")
                .font(.headline)
            
            Text(isActive ? "๐Ÿ”ต ํ™œ์„ฑ / Active" : "๐Ÿ”ด ๋น„ํ™œ์„ฑ / Inactive")
                .font(.title)
            
            Button {
                Task {
                    await toggleStatus()
                }
            } label: {
                Text(isActive ? "๋น„ํ™œ์„ฑํ™”ํ•˜๊ธฐ / InActivate"
                              : "ํ™œ์„ฑํ™”ํ•˜๊ธฐ / Activate")
                    .padding()
                    .frame(width: 220)
                    .background(isActive ? Color.green : Color.red)
                    .foregroundColor(.white)
                    .cornerRadius(10)
            }
        }
        .frame(maxWidth: .infinity, maxHeight: .infinity)
        .task {   // ๋น„๋™๊ธฐ ์‹œ์ž‘ / asynchronous start
            await fetchStatus()
        }
    }
}

//
// MARK: - Networking
//

// extension : ๊ธฐ์กด ํƒ€์ž…์— ๊ธฐ๋Šฅ์„ ์ถ”๊ฐ€ํ•˜๊ฑฐ๋‚˜,์ฝ”๋“œ๋ฅผ ์—ญํ• ๋ณ„๋กœ ๋‚˜๋ˆ„๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉํ•˜๋Š” ๋ฌธ๋ฒ•( ์ฝ”๋“œ ๊ฐ€๋…์„ฑ์„ ์œ„ํ•ด์„œ ์‚ฌ์šฉํ•จ.)
// Extension: A grammar used to add functionality to existing types or to divide code by role (used for code readability)

extension ContentView {
    
    // GET ์š”์ฒญ(์กฐํšŒ) / GET Request(Search)
    @MainActor
    func fetchStatus() async {
        guard let url = URL(string: "\(baseURL)/status") else { return }
        
        do {
            let (data, _) = try await URLSession.shared.data(from: url)
            let decoded = try JSONDecoder().decode(StatusResponse.self, from: data)
            isActive = decoded.is_active
        } catch {
            print("์ƒํƒœ ๊ฐ€์ ธ์˜ค๊ธฐ ์‹คํŒจ/Failed to get status:", error)
        }
    }
    
    // POST ์š”์ฒญ(์ƒํƒœ๋ณ€๊ฒฝ) / POST Request(Status change)
    @MainActor
    func toggleStatus() async {
        guard let url = URL(string: "\(baseURL)/toggle") else { return }
        
        var request = URLRequest(url: url)
        request.httpMethod = "POST"
        request.setValue("application/json", forHTTPHeaderField: "Content-Type")
        
        let body = ["isActive": !isActive]
        request.httpBody = try? JSONSerialization.data(withJSONObject: body)
        
        do {
            let (data, _) = try await URLSession.shared.data(for: request)
            let decoded = try JSONDecoder().decode(StatusResponse.self, from: data)
            isActive = decoded.is_active
        } catch {
            print("ํ† ๊ธ€ ์‹คํŒจ/Toggle failed:", error)
        }
    }
}

// ์„œ๋ฒ„ ์‘๋‹ต ๋ชจ๋ธ / Server response model
struct StatusResponse: Codable {
    let is_active: Bool
}

#Preview {
    ContentView()
}

โญ๏ธ ๋งŒ์•ฝ ์‹ค์ œ๋กœ swift์•ฑ์„ ๋นŒ๋“œํ•˜๋Š” ๊ฒฝ์šฐ ์„œ๋ฒ„๋Š” httpsํ™˜๊ฒฝ์œผ๋กœ ๋ฐ”๊ฟ”์ค˜์•ผํ•ฉ๋‹ˆ๋‹ค.
If you are actually building a Swift app, you will need to change the server to https.

httpํ™˜๊ฒฝ์—์„œ ์‚ฌ์šฉํ•˜๋ ค๋ฉด xcode์—์„œ ๋ณ„๋„์˜ ์„ค์ •์„ ํ•ด์ค˜์•ผํ•ฉ๋‹ˆ๋‹ค.(์•„๋ž˜์˜ ์ด๋ฏธ์ง€๋ฅผ ์ฐธ์กฐํ•˜์„ธ์š”)
To use it in an http environment, you need to make separate settings in Xcode. (See the image below)


โœ”๏ธ Xcode -> ํ”„๋กœ์ ํŠธ์„ ํƒ->TARGETS์„ ํƒ -> info ์„ ํƒ
Xcode ->Select project -> Select TARGETS -> Select info

โœ”๏ธ ๋งˆ์šฐ์Šค ์šฐํด๋ฆญ -> Add Row ์„ ํƒ
Right click -> Select Add Row

โœ”๏ธ App Transport Security Settings ์„ ํƒ
Select App Transport Security Settings

โœ”๏ธ ์ขŒ์ธก ํ™”์‚ดํ‘œ ์•„๋ž˜๋กœ ํ™•์žฅ
Expand left arrow down

โœ”๏ธ ๋งˆ์šฐ์Šค ์šฐํด๋ฆญํ•ด์„œ Add Row์„ ํƒ
Right-click and select Add Row

โœ”๏ธ Allow Arbitray Loads ์ž…๋ ฅ
Enter Allow Arbitray Loads

๐Ÿ‘‰๐Ÿป ์Šคํฌ๋ฆฐ์ƒท/ScreenShot

โœ”๏ธ Xcode Simulator

โœ”๏ธ Postgresql

Leave a Reply