Swift日记App开发:在ViewController中实现可拖拽缩放的文本图片添加功能
Hey there! Let’s break down how you can build this diary app feature that mimics Instagram Stories’ draggable, scalable elements. I’ll cover both a single ViewController approach and how to adapt it for Table/Collection View if that fits your flow better.
First, start by adding your two buttons (“Add Text” and “Add Image”) to your ViewController. You can do this programmatically or via Interface Builder—here’s a quick programmatic example in Swift:
class DiaryViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() setupButtons() } private func setupButtons() { // Add Text Button let addTextBtn = UIButton(type: .system) addTextBtn.setTitle("Add Text", for: .normal) addTextBtn.addTarget(self, action: #selector(addTextTapped), for: .touchUpInside) addTextBtn.frame = CGRect(x: 20, y: view.safeAreaInsets.top + 20, width: 120, height: 40) view.addSubview(addTextBtn) // Add Image Button let addImageBtn = UIButton(type: .system) addImageBtn.setTitle("Add Image", for: .normal) addImageBtn.addTarget(self, action: #selector(addImageTapped), for: .touchUpInside) addImageBtn.frame = CGRect(x: 150, y: view.safeAreaInsets.top + 20, width: 120, height: 40) view.addSubview(addImageBtn) } @objc private func addTextTapped() { // We'll implement this next } @objc private func addImageTapped() { // We'll implement this next } }
The key to mimicking Instagram Stories is having a reusable view that handles dragging and scaling. Create a subclass of UIView that includes gesture recognizers for pan and pinch:
class EditableContentItem: UIView { private var initialCenter: CGPoint! private var initialScale: CGFloat! override init(frame: CGRect) { super.init(frame: frame) setupGestures() backgroundColor = .clear } required init?(coder: NSCoder) { super.init(coder: coder) setupGestures() backgroundColor = .clear } private func setupGestures() { // Pan Gesture for dragging let panGesture = UIPanGestureRecognizer(target: self, action: #selector(handlePan(_:))) addGestureRecognizer(panGesture) // Pinch Gesture for scaling let pinchGesture = UIPinchGestureRecognizer(target: self, action: #selector(handlePinch(_:))) addGestureRecognizer(pinchGesture) } @objc private func handlePan(_ gesture: UIPanGestureRecognizer) { switch gesture.state { case .began: initialCenter = center case .changed: let translation = gesture.translation(in: superview) center = CGPoint(x: initialCenter.x + translation.x, y: initialCenter.y + translation.y) default: break } } @objc private func handlePinch(_ gesture: UIPinchGestureRecognizer) { switch gesture.state { case .began: initialScale = transform.a case .changed: let scale = initialScale * gesture.scale transform = CGAffineTransform(scaleX: scale, y: scale) default: break } } }
Now create specific subclasses for text and image:
// Text Item class EditableTextItem: EditableContentItem { let textLabel: UILabel = { let label = UILabel() label.textColor = .black label.font = .systemFont(ofSize: 24) label.textAlignment = .center label.numberOfLines = 0 return label }() init(text: String, frame: CGRect) { super.init(frame: frame) addSubview(textLabel) textLabel.text = text textLabel.frame = bounds textLabel.autoresizingMask = [.flexibleWidth, .flexibleHeight] } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } } // Image Item class EditableImageItem: EditableContentItem { let imageView: UIImageView = { let iv = UIImageView() iv.contentMode = .scaleAspectFit iv.clipsToBounds = true return iv }() init(image: UIImage, frame: CGRect) { super.init(frame: frame) addSubview(imageView) imageView.image = image imageView.frame = bounds imageView.autoresizingMask = [.flexibleWidth, .flexibleHeight] } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } }
Now hook up the button taps to create and add these items to your view:
extension DiaryViewController { @objc private func addTextTapped() { let alert = UIAlertController(title: "Add Text", message: "Enter your text:", preferredStyle: .alert) alert.addTextField { textField in textField.placeholder = "Type here..." } alert.addAction(UIAlertAction(title: "Add", style: .default) { [weak self] _ in guard let text = alert.textFields?.first?.text, !text.isEmpty else { return } // Create text item at a default position let textItem = EditableTextItem(text: text, frame: CGRect(x: 50, y: 200, width: 250, height: 100)) self?.view.addSubview(textItem) }) alert.addAction(UIAlertAction(title: "Cancel", style: .cancel)) present(alert, animated: true) } @objc private func addImageTapped() { let imagePicker = UIImagePickerController() imagePicker.delegate = self imagePicker.sourceType = .photoLibrary present(imagePicker, animated: true) } } // Conform to UIImagePickerController delegates extension DiaryViewController: UIImagePickerControllerDelegate, UINavigationControllerDelegate { func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) { guard let image = info[.originalImage] as? UIImage else { picker.dismiss(animated: true) return } // Create image item at a default position let imageItem = EditableImageItem(image: image, frame: CGRect(x: 50, y: 350, width: 200, height: 200)) view.addSubview(imageItem) picker.dismiss(animated: true) } func imagePickerControllerDidCancel(_ picker: UIImagePickerController) { picker.dismiss(animated: true) } }
If you prefer using a Table or Collection View (e.g., if your diary has multiple entries/pages):
- Collection View: Use a horizontal flow layout so each cell represents a single diary page. Each cell will act as the container view for your editable text/image items—just replicate the ViewController logic inside the Collection View Cell class.
- Table View: Each row could be a diary entry, but since you want draggable/scalable elements, a Collection View is a better fit (it’s more flexible for full-page content).
- Layer Management: If users add multiple items, make sure the selected item comes to the front. You can add a tap gesture to
EditableContentItemand callsuperview?.bringSubviewToFront(self)when tapped. - Saving Content: To persist the diary entries, save each item’s frame, transform, text, or image data (e.g., using Core Data or UserDefaults for simpler cases).
- Customization: Let users change text color/font or image filters to make it more Instagram-like!
内容的提问来源于stack exchange,提问作者ciklukylok

