์ƒ์„ธ ์ปจํ…์ธ 

๋ณธ๋ฌธ ์ œ๋ชฉ

[Swift] UITextView - PlaceHolder ์ ์šฉํ•˜๊ธฐ

๋ณธ๋ฌธ

UITextView๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์œ„์™€ ๊ฐ™์ด ์‚ฌ์šฉ์ž์˜ ์ •๋ณด๋ฅผ ์ž…๋ ฅ๋ฐ›์„ ์ˆ˜ ์žˆ๋‹ค. 

๊ทธ๋Ÿฌ๋‚˜ ๋ฌด์—‡์„ ์ž…๋ ฅํ•ด์•ผํ•˜๋Š”์ง€ ์‚ฌ์šฉ์ž ์ž…์žฅ์—์„œ๋Š” ์ œ๋Œ€๋กœ ์•Œ ์ˆ˜๊ฐ€ ์—†๋‹ค. 

 

๊ทธ๋ž˜์„œ PlaceHoler๋ผ๋Š” ๊ธฐ๋Šฅ์„ ์ถ”๊ฐ€ํ•ด๋ณด๊ธฐ๋กœ ํ–ˆ๋‹ค. 

TextFiled๋Š” PlaceHoler ์†์„ฑ์ด ์žˆ๋Š”๊ฒƒ์œผ๋กœ ์•„๋Š”๋ฐ, 

TextView๋Š” ๋ธ๋ฆฌ๊ฒŒ์ดํŠธ๋ฅผ ํ™œ์šฉํ•ด ๋”ฐ๋กœ ์ปค์Šคํ…€ํ•ด์ค˜์•ผ ๊ฐ€๋Šฅํ•˜๋‹ค๊ณ  ํ•œ๋‹ค. 

 


 

 

 

 

extension UpdatePhoneBookView: UITextViewDelegate {
    
    // ์•„๋ž˜ ํ•จ์ˆ˜๋“ค์€ UITextViewDelegate ํ”„๋กœํ† ์ฝœ์— ํฌํ•จ๋œ ๋ฉ”์„œ๋“œ๋“ค
    // 1. ์‚ฌ์šฉ์ž๊ฐ€ ํ…์ŠคํŠธ ๋ทฐ์— ์ž…๋ ฅ์„ ์‹œ์ž‘ํ•  ๋•Œ ํ˜ธ์ถœ๋˜๋Š” ๋ฉ”์„œ๋“œ
    func textViewDidBeginEditing(_ textView: UITextView) {
        //ํ…์ŠคํŠธ ๋ทฐ์˜ ํ˜„์žฌ ํ…์ŠคํŠธ ์ƒ‰์ƒ์ด ํ”Œ๋ ˆ์ด์Šคํ™€๋” ํ…์ŠคํŠธ ์ƒ‰์ƒ์ธ์ง€ ํ™•์ธ
        guard textView.textColor == .systemGray4 else { return }
        //์ƒ‰์ƒ์ด ํšŒ์ƒ‰์ด๋ผ๋ฉด ํ…์ŠคํŠธ ๋ทฐ์˜ ๋‚ด์šฉ์„ ๋น„์šฐ๊ณ , ํ…์ŠคํŠธ ์ƒ‰์ƒ์„ ์–ด๋‘์šด ํšŒ์ƒ‰์œผ๋กœ ๋ณ€๊ฒฝํ•˜์—ฌ ์‚ฌ์šฉ์ž๊ฐ€ ์ž…๋ ฅํ•  ์ค€๋น„
        textView.text = nil
        textView.textColor = .darkGray
    }
    
    // 2. ์‚ฌ์šฉ์ž๊ฐ€ ํ…์ŠคํŠธ ๋ทฐ์— ์ž…๋ ฅ์„ ๋งˆ์น  ๋•Œ ํ˜ธ์ถœ๋˜๋Š” ๋ฉ”์„œ๋“œ
    func textViewDidEndEditing(_ textView: UITextView) {
        if textView.text.isEmpty{
        	// ํƒœ๊ทธ์— ๋”ฐ๋ผ ์ ์ ˆํ•œ ํ”Œ๋ ˆ์ด์Šคํ™€๋” ํ…์ŠคํŠธ๋ฅผ ์„ค์ •
            if textView.tag == 1 {
                textView.text = "์ด๋ฆ„์„ ์ž…๋ ฅํ•˜์„ธ์š”."
            } else if textView.tag == 2 {
                textView.text = "์ „ํ™”๋ฒˆํ˜ธ๋ฅผ ์ž…๋ ฅํ•˜์„ธ์š”."
            }
            //ํ”Œ๋ ˆ์ด์Šคํ™€๋” ํ…์ŠคํŠธ์˜ ์ƒ‰์ƒ์„ ํšŒ์ƒ‰์œผ๋กœ ๋ณ€๊ฒฝ
            textView.textColor = .systemGray4
        }
    }
}

๊ฐ€๋…์„ฑ์„ ์œ„ํ•ด extension์œผ๋กœ ๋ธ๋ฆฌ๊ฒŒ์ดํŠธ๋ฅผ ์ฑ„ํƒํ•ด ํ•˜๋‹จ์— ๋”ฐ๋กœ ๋นผ์ฃผ์—ˆ๋‹ค. 

 

 

 

 

 


ํ•˜์ง€๋งŒ ์—ฌ๊ธฐ์„œ ์ƒˆ๋กœ ๋ฐœ๊ฒฌํ•œ ์˜ค๋ฅ˜...

 

์ˆ˜์ • ์‹œ ์ด๋ฆ„์„ ์ž…๋ ฅํ•  ๋•Œ๋„ .systemGray4๋กœ ๋ณด์ธ๋‹ค๋Š” ์ ์ด๋‹ค.

 

 

 

import UIKit
import CoreData

class UpdatePhoneBookViewCotroller: UIViewController {
    var updatePhoneBookView: UpdatePhoneBookView!
    var phoneBookViewController: PhoneBookViewController? //๋ณ€์ˆ˜๊ฐ€ nil์ผ ์ˆ˜ ์žˆ์Œ
    var phoneBook: PhoneBook? // ์ˆ˜์ •ํ•  ๋ฐ์ดํ„ฐ
    let coreDataManager = CoreDataManager()
    
    //๋„ค๋น„๊ฒŒ์ด์…˜๋ฐ” - "์ ์šฉ" ๋ฒ„ํŠผ
    lazy var saveButton: UIBarButtonItem = {
        let button = UIBarButtonItem(title: "์ ์šฉ", style: .plain, target: self, action: #selector(saveButtonTapped))
        return button
    }()
    
    
    override func loadView() {
        // UpdatePhoneBookView๋ฅผ ๊ธฐ๋ณธ ๋ทฐ๋กœ ์„ค์ •
        updatePhoneBookView = UpdatePhoneBookView(frame: UIScreen.main.bounds)
        self.view = updatePhoneBookView
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        //๋„ค๋น„๊ฒŒ์ด์…˜๋ฐ” ์„ค์ •
        navigationItem.rightBarButtonItem = saveButton
        
        updatePhoneBookView.randomImageButton.addTarget(self, action: #selector(randomImageButtonTapped), for: .touchDown)
      
        if let phoneBook = phoneBook {
            // ์ „๋‹ฌ๋ฐ›์€ ๋ฐ์ดํ„ฐ๋ฅผ ๋ทฐ์— ํ‘œ์‹œ
            title = "\(phoneBook.name!)"
            updatePhoneBookView.inputName.text = phoneBook.name
            updatePhoneBookView.inputPhoneNumber.text = phoneBook.phoneNumber
            updatePhoneBookView.inputName.textColor = .darkGray //๐ŸŒŸ๐ŸŒŸ๐ŸŒŸ๐ŸŒŸ์ถ”๊ฐ€์‚ฌํ•ญ๐ŸŒŸ๐ŸŒŸ๐ŸŒŸ๐ŸŒŸ
            updatePhoneBookView.inputPhoneNumber.textColor = .darkGray  //๐ŸŒŸ๐ŸŒŸ๐ŸŒŸ๐ŸŒŸ์ถ”๊ฐ€์‚ฌํ•ญ๐ŸŒŸ๐ŸŒŸ๐ŸŒŸ๐ŸŒŸ
            if let imageData = phoneBook.image {
                updatePhoneBookView.profileImage.image = UIImage(data: imageData)
            }
        }
    }
    
    
    // MARK: - ์„œ๋ฒ„ ๋ฐ์ดํ„ฐ ๋ถˆ๋Ÿฌ์˜ค๋Š” ๋ฉ”์„œ๋“œ
    // ์„œ๋ฒ„ ๋ฐ์ดํ„ฐ ๊ฐ€์ ธ์˜ค๊ธฐ - ๋น„๋™๊ธฐ์ ์œผ๋กœ ๋™์ž‘ํ•˜๋Š” ํ•จ์ˆ˜๋กœ ๋ฆฌํ„ดํ˜•์ด ์•„๋‹Œ, ์ฝœ๋ฐฑ ํ•จ์ˆ˜๋กœ ์„ค๊ณ„
    private func fetchData<T: Decodable>(url: URL, completion: @escaping (T?) -> Void) {
        print(#function)
        let session = URLSession(configuration: .default)
        
        session.dataTask(with: URLRequest(url: url)) { data, response, error in
            guard let data = data, error == nil else {
                print("๋ฐ์ดํ„ฐ ๋กœ๋“œ ์‹คํŒจ")
                completion(nil)
                return
            }
            let successRange = 200..<300
            if let response = response as? HTTPURLResponse, successRange.contains(response.statusCode) {
                guard let decodeData = try? JSONDecoder().decode(T.self, from: data) else {
                    print("๋ฐ์ดํ„ฐ๊ฐ€ ์—†์Œ")
                    completion(nil)
                    return
                }
                completion(decodeData)
            } else {
                print("์‘๋‹ต ์˜ค๋ฅ˜")
                completion(nil)
            }
        }.resume()
    }
    
    // MARK: - ์„œ๋ฒ„์—์„œ ๋žœ๋ค ํฌ์ผ“๋ชฌ ๋ฐ์ดํ„ฐ๋ฅผ ๋ถˆ๋Ÿฌ์˜ค๋Š” ๋ฉ”์„œ๋“œ
    private func pokemonInfoData() {
        let urlComponents = URLComponents(string: "https://pokeapi.co/api/v2/pokemon/\(Int.random(in: 1...1000))")
        
        guard let url = urlComponents?.url else {
            print("์ž˜๋ชป๋œ URL")
            return
        }
        // ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ง€๊ณ  ์˜ค๋Š” ๋ฉ”์„œ๋“œ ์ถ”์ถœ
        fetchData(url: url) { [weak self] (pokemon: Pokemon?) in
            // guard let ์„ ํ†ตํ•ด self๋ฅผ ๋‹ค์‹œ ๊ฐ•ํ•œ์ฐธ์กฐ๋ฅผ ํ•˜์—ฌ ์•ˆ์ „ํ•˜๊ฒŒ ์‚ฌ์šฉ
            guard let self, let pokemon else { return }
            // ๋ฉ”์ธ ์Šค๋ ˆ๋“œ๋กœ ์„ค์ •์„ ํ•ด์ฃผ์ง€ ์•Š์œผ๋ฉด ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์Šค๋ ˆ๋“œ์—์„œ ๋Œ์•„๊ฐ€์„œ ์•ˆ์ข‹์Œ
            guard let imageUrl = URL(string: pokemon.sprites.frontDefault) else { return }
            // image ๋ฅผ ๋กœ๋“œํ•˜๋Š” ์ž‘์—…์€ ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์“ฐ๋ ˆ๋“œ ์ž‘์—… : Data(contensOf:)
            if let data = try? Data(contentsOf: imageUrl) {
                if let image = UIImage(data: data) {
                    // ์ด๋ฏธ์ง€๋ทฐ์— ์ด๋ฏธ์ง€๋ฅผ ๊ทธ๋ฆฌ๋Š” ์ž‘์—…์€ UI ์ž‘์—…์ด๊ธฐ ๋•Œ๋ฌธ์— ๋‹ค์‹œ ๋ฉ”์ธ ์“ฐ๋ ˆ๋“œ์—์„œ ์ž‘์—….
                    DispatchQueue.main.async {
                        self.updatePhoneBookView.profileImage.image = image
                    }
                }
            }
        }
    }
    
    
    //MARK: - @objc ๋ฉ”์„œ๋“œ
    //"๋žœ๋ค ์ด๋ฏธ์ง€ ์ƒ์„ฑ" ๋ฒ„ํŠผ ์•ก์…˜
    @objc
    func randomImageButtonTapped(){
        pokemonInfoData()
    }
    
    //"์ ์šฉ" ๋ฒ„ํŠผ ์•ก์…˜
    @objc
    func saveButtonTapped(){
        if let name = updatePhoneBookView.inputName.text,
           let phoneNumber = updatePhoneBookView.inputPhoneNumber.text,
           let image = updatePhoneBookView.profileImage.image {
            
            if let phoneBook = phoneBook {
                // ๊ธฐ์กด ๋ฐ์ดํ„ฐ๋ฅผ ์—…๋ฐ์ดํŠธ
                coreDataManager.updateData(currentPhoneNumer: phoneBook.phoneNumber!, newName: name, newPhoneNumber: phoneNumber, newImage: image)
            } else {
                // ์ƒˆ๋กœ์šด ๋ฐ์ดํ„ฐ๋ฅผ ์ƒ์„ฑ
                coreDataManager.createData(name: name, phoneNumber: phoneNumber, image: image)
            }   
            self.navigationController?.popViewController(animated: true)
        }
    }
}
import UIKit
import SnapKit

class UpdatePhoneBookView: UIView {
    
    let updatePhoneBookViewCotroller = UpdatePhoneBookViewCotroller()
    var phoneBookViewController: PhoneBookViewController?
    
    var profileImage: UIImageView = {
        let image = UIImageView()
        image.layer.borderColor = UIColor.systemGray4.cgColor
        image.contentMode = .scaleAspectFit
        image.clipsToBounds = true
        image.layer.borderWidth = 1
        image.layer.cornerRadius = 100
        return image
    }()
    lazy var randomImageButton: UIButton = {
        let button = UIButton()
        button.setTitle("๋žœ๋ค ์ด๋ฏธ์ง€ ์ƒ์„ฑ", for: .normal)
        button.backgroundColor = .systemGray5
        button.setTitleColor(.black, for: .normal)
        button.layer.cornerRadius = 20
        return button
    }()
    var inputName: UITextView = {
        let textView = UITextView()
        textView.textColor = .systemGray4
        textView.font = .systemFont(ofSize: 16)
        textView.backgroundColor = .white
        textView.text = "์ด๋ฆ„์„ ์ž…๋ ฅํ•˜์„ธ์š”."
        textView.tag = 1
        
        // ํ…Œ๋‘๋ฆฌ ์„ค์ •
        textView.layer.borderColor = UIColor.systemGray4.cgColor
        textView.layer.borderWidth = 1.0
        textView.layer.cornerRadius = 8.0
        return textView
    }()
    var inputPhoneNumber: UITextView = {
        let textView = UITextView()
        textView.textColor = .systemGray4
        textView.font = .systemFont(ofSize: 16)
        textView.backgroundColor = .white
        textView.text = "์ „ํ™”๋ฒˆํ˜ธ๋ฅผ ์ž…๋ ฅํ•˜์„ธ์š”."
        textView.tag = 2
        
        // ํ…Œ๋‘๋ฆฌ ์„ค์ •
        textView.layer.borderColor = UIColor.systemGray4.cgColor
        textView.layer.borderWidth = 1.0
        textView.layer.cornerRadius = 8.0
        return textView
    }()
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        configureUI()
        
        inputName.delegate = self
        inputPhoneNumber.delegate = self
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    
    private func configureUI(){
        self.backgroundColor = .white
        
        [profileImage, randomImageButton, inputName, inputPhoneNumber].forEach{ self.addSubview($0) }
        
       //์˜คํ† ๋ ˆ์ด์•„์›ƒ...
    }
}


//MARK: -PlaceHolder ์„ค์ •
extension UpdatePhoneBookView: UITextViewDelegate {
    
    // ์•„๋ž˜ ํ•จ์ˆ˜๋“ค์€ UITextViewDelegate ํ”„๋กœํ† ์ฝœ์— ํฌํ•จ๋œ ๋ฉ”์„œ๋“œ๋“ค
    // 1. ์‚ฌ์šฉ์ž๊ฐ€ ํ…์ŠคํŠธ ๋ทฐ์— ์ž…๋ ฅ์„ ์‹œ์ž‘ํ•  ๋•Œ ํ˜ธ์ถœ๋˜๋Š” ๋ฉ”์„œ๋“œ
    func textViewDidBeginEditing(_ textView: UITextView) {
        
        //ํ…์ŠคํŠธ ๋ทฐ์˜ ํ˜„์žฌ ํ…์ŠคํŠธ ์ƒ‰์ƒ์ด ํ”Œ๋ ˆ์ด์Šคํ™€๋” ํ…์ŠคํŠธ ์ƒ‰์ƒ์ธ์ง€ ํ™•์ธ
        guard textView.textColor == .systemGray4 else { return }
        //์ƒ‰์ƒ์ด ํšŒ์ƒ‰์ด๋ผ๋ฉด ํ…์ŠคํŠธ ๋ทฐ์˜ ๋‚ด์šฉ์„ ๋น„์šฐ๊ณ , ํ…์ŠคํŠธ ์ƒ‰์ƒ์„ ์–ด๋‘์šด ํšŒ์ƒ‰์œผ๋กœ ๋ณ€๊ฒฝํ•˜์—ฌ ์‚ฌ์šฉ์ž๊ฐ€ ์ž…๋ ฅํ•  ์ค€๋น„
        if textView.text == "์ด๋ฆ„์„ ์ž…๋ ฅํ•˜์„ธ์š”." || textView.text == "์ „ํ™”๋ฒˆํ˜ธ๋ฅผ ์ž…๋ ฅํ•˜์„ธ์š”." {
            textView.text = nil
            textView.textColor = .darkGray
        } else {
            textView.textColor = .darkGray //์‚ฌ์šฉ์ž๊ฐ€ ๊ฐ’์„ ์ž…๋ ฅ๋œ ์ƒํƒœ ์ผ ๋•Œ
        }
    }
    
    // 2. ์‚ฌ์šฉ์ž๊ฐ€ ํ…์ŠคํŠธ ๋ทฐ์— ์ž…๋ ฅ์„ ๋งˆ์น  ๋•Œ ํ˜ธ์ถœ๋˜๋Š” ๋ฉ”์„œ๋“œ
    func textViewDidEndEditing(_ textView: UITextView) {
        if textView.text.isEmpty{
            if textView.tag == 1 {
                textView.text = "์ด๋ฆ„์„ ์ž…๋ ฅํ•˜์„ธ์š”."
            } else if textView.tag == 2 {
                textView.text = "์ „ํ™”๋ฒˆํ˜ธ๋ฅผ ์ž…๋ ฅํ•˜์„ธ์š”."
            }
            //ํ”Œ๋ ˆ์ด์Šคํ™€๋” ํ…์ŠคํŠธ์˜ ์ƒ‰์ƒ์„ ํšŒ์ƒ‰์œผ๋กœ ๋ณ€๊ฒฝ
            textView.textColor = .systemGray4
        }
    }
}

 

๊ด€๋ จ๊ธ€ ๋”๋ณด๊ธฐ