Post

Replies

Boosts

Views

Activity

Delay between animation and view accepting touch input
Hi! I was trying to add an animation to my SwiftUI view with UIKit, but after the animation runs there's a delay before the view will accept touch interactions. I thought it was because of the frame size of the view controller, but even after fixing that I still get the delay. Could anyone point me to where I might be going wrong, or if maybe using a UIKit modifier for the animation just doesn't work? Any help would be greatly appreciated! UIView: class BounceView: UIView { required init() { super.init(frame: .zero) } func bounceAnimation() { guard let piece = self.subviews.first else { return } UIView.animate(withDuration: 0.7, delay: 0, usingSpringWithDamping: 0.7, initialSpringVelocity: 0) { piece.frame.origin.x += 10 } } func bounceBack() { guard let piece = self.subviews.first else { return } UIView.animate(withDuration: 0.7, delay: 0, usingSpringWithDamping: 0.7, initialSpringVelocity: 0) { piece.frame.origin.x -= 10 } } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } } UIView controller: class BounceViewController: UIViewController { init(controller: UIViewController) { super.init(nibName: nil, bundle: nil) view = BounceView() addChild(controller) controller.view.translatesAutoresizingMaskIntoConstraints = false controller.view.backgroundColor = .clear view.addSubview(controller.view) controller.didMove(toParent: self) } // adjusts view to match bounds of child override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() let subviewFrame = self.view.subviews.first?.bounds ?? .zero view.frame = subviewFrame print(subviewFrame) self.updateViewConstraints() } func update(animated: Bool) { let bounceView = view as? BounceView if animated { bounceView?.bounceAnimation() } else { bounceView?.bounceBack() } } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } } SwiftUI wrapper: struct BounceUIViewController: UIViewControllerRepresentable { private var controller: UIViewController @Binding var animated: Bool init(controller: UIViewController, animated: Binding<Bool>) { self.controller = controller self._animated = animated } func makeUIViewController(context: Context) -> BounceViewController { BounceViewController(controller: controller) } func updateUIViewController(_ uiViewController: BounceViewController, context: Context) { uiViewController.update(animated: animated) } } View extension: extension View { func bounce(animated: Binding<Bool>) -> some View { modifier(Bounce(animated: animated)) } } struct Bounce: ViewModifier { @Binding var animated: Bool init(animated: Binding<Bool>) { self._animated = animated } func body(content: Content) -> some View { BounceUIViewController(controller: content.uiViewController, animated: $animated) } }
1
0
185
1w
How to remove antialiasing in CGContext?
I'm trying to create a brush by drawing in CGContext using a UIImage for a brush. However, I when I try drawing, the stroke is antialiased when I don't want it to be. I tried context.interpolationQuality = .none context.setShouldAntialias(false) but it doesn't seem to work. Is it a problem with my brush or resizing the brush maybe?? (Also this problem doesn't occur when I draw a regular stroke without the brush.) Any help or advice would be greatly appreciated! Here is my draw function override func draw(_ rect: CGRect) { super.draw(rect) guard let context = UIGraphicsGetCurrentContext() else { return } context.interpolationQuality = .none context.setShouldAntialias(false) for stroke in strokes { let brush = UIColor.blue.circle(size: CGSize(width: CGFloat(stroke.width * 10), height: CGFloat(stroke.width * 10))).mask(color: UIColor(cgColor: stroke.color)) var first = true for point in stroke.points { if first { first = false context.move(to: point) continue } context.addLine(to: point, using: brush) } } } Circle brush extension UIColor { func circle(size: CGSize = CGSize(width: 1, height: 1)) -> UIImage { return UIGraphicsImageRenderer(size: size).image { rendererContext in self.setFill() UIBezierPath(ovalIn: CGRect(origin: .zero, size: size)).fill() } } } CGConext extension where I overloaded addLine extension CGContext { func addLine(to point: CGPoint, using brush: UIImage, density: CGFloat = 1.0) { var frame: CGRect = .zero frame.size = brush.size let lastPoint = self.currentPointOfPath let distanceX = point.x - lastPoint.x let distanceY = point.y - lastPoint.y let distanceR = sqrt(pow(distanceX, 2) + pow(distanceY, 2)) let deltaR = (1.0 / density) let numOfSteps = ceil(distanceR / deltaR) var renders : CGFloat = 0.0 let deltaX = distanceX / numOfSteps let deltaY = distanceY / numOfSteps var currentCenter = lastPoint repeat { frame.origin.x = currentCenter.x - frame.width / 2.0 frame.origin.y = currentCenter.y - frame.height / 2.0 brush.draw(in: frame) currentCenter.x += deltaX currentCenter.y += deltaY renders += 1.0 } while (renders <= numOfSteps) self.move(to: point) } }
4
0
328
Oct ’24
Attempting to add scrubbing to UISlider
I made a custom slider by subclassing UISlider, and I'm trying to add scrubbing functionality to it, but for some reason the scrubbing is barely even noticeable at 0.1? In my code, I tried multiplying change in x distance by the scrubbing value, but it doesn't seem to work. Also, when I manually set the scrubbing speed to a lower value such as 0.01, it does go slower but it looks really laggy and weird?? What am I doing wrong? Any help or advice would be greatly appreciated! Subclass of UISlider: class SizeSliderView: UISlider { private var previousLocation: CGPoint? private var currentLocation: CGPoint? private var translation: CGFloat = 0 private var scrubbingSpeed: CGFloat = 1 private var defaultDiameter: Float init(startValue: Float = 0, defaultDiameter: Float = 500) { self.defaultDiameter = defaultDiameter super.init(frame: .zero) value = clamp(value: startValue, min: minimumValue, max: maximumValue) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func draw(_ rect: CGRect) { super.draw(rect) clear() createThumbImageView() addTarget(self, action: #selector(valueChanged(_:)), for: .valueChanged) } // Clear elements private func clear() { tintColor = .clear maximumTrackTintColor = .clear backgroundColor = .clear thumbTintColor = .clear } // Call when value is changed @objc private func valueChanged(_ sender: SizeSliderView) { CATransaction.begin() CATransaction.setDisableActions(true) CATransaction.commit() createThumbImageView() } // Create thumb image with thumb diameter dependent on thumb value private func createThumbImageView() { let thumbDiameter = CGFloat(defaultDiameter * value) let thumbImage = UIColor.red.circle(CGSize(width: thumbDiameter, height: thumbDiameter)) setThumbImage(thumbImage, for: .normal) setThumbImage(thumbImage, for: .highlighted) setThumbImage(thumbImage, for: .application) setThumbImage(thumbImage, for: .disabled) setThumbImage(thumbImage, for: .focused) setThumbImage(thumbImage, for: .reserved) setThumbImage(thumbImage, for: .selected) } // Return true so touches are tracked override func beginTracking(_ touch: UITouch, with event: UIEvent?) -> Bool { let location = touch.location(in: self) // Ensure that start location is on thumb let thumbDiameter = CGFloat(defaultDiameter * value) if location.x < bounds.width / 2 - thumbDiameter / 2 || location.x > bounds.width / 2 + thumbDiameter / 2 || location.y < 0 || location.y > thumbDiameter { return false } previousLocation = location super.beginTracking(touch, with: event) return true } // Track based on moving slider override func continueTracking(_ touch: UITouch, with event: UIEvent?) -> Bool { guard isTracking else { return false } guard let previousLocation = previousLocation else { return false } // Reference // location: location of touch relative to device // delta location: change in touch location WITH scrubbing // adjusted location: location of touch to slider bounds (WITH scrubbing) // translation: location of slider relative to device let location = touch.location(in: self) currentLocation = location scrubbingSpeed = getScrubbingSpeed(for: location.y - 50) let deltaLocation = (location.x - previousLocation.x) * scrubbingSpeed var adjustedLocation = deltaLocation + previousLocation.x - translation if adjustedLocation < 0 { translation += adjustedLocation adjustedLocation = deltaLocation + previousLocation.x - translation } else if adjustedLocation > bounds.width { translation += adjustedLocation - bounds.width adjustedLocation = deltaLocation + previousLocation.x - translation } self.previousLocation = CGPoint(x: deltaLocation + previousLocation.x, y: location.y) let newValue = Float(adjustedLocation / bounds.width) * (maximumValue - minimumValue) + minimumValue setValue(newValue, animated: false) sendActions(for: .valueChanged) return true } // Reset start and current location override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) { self.currentLocation = nil self.translation = 0 super.touchesEnded(touches, with: event) } // Thumb location follows current location and resets in middle override func thumbRect(forBounds bounds: CGRect, trackRect rect: CGRect, value: Float) -> CGRect { let thumbDiameter = CGFloat(defaultDiameter * value) let origin = CGPoint(x: (currentLocation?.x ?? bounds.width / 2) - thumbDiameter / 2, y: (currentLocation?.y ?? thumbDiameter / 2) - thumbDiameter / 2) return CGRect(origin: origin, size: CGSize(width: thumbDiameter, height: thumbDiameter)) } private func getScrubbingSpeed(for value: CGFloat) -> CGFloat { switch value { case 0: return 1 case 0...50: return 0.5 case 50...100: return 0.25 case 100...: return 0.1 default: return 1 } } private func clamp(value: Float, min: Float, max: Float) -> Float { if value < min { return min } else if value > max { return max } else { return value } } } UIView representative: struct SizeSlider: UIViewRepresentable { private var startValue: Float private var defaultDiameter: Float init(startValue: Float, defaultDiameter: Float) { self.startValue = startValue self.defaultDiameter = defaultDiameter } func makeUIView(context: Context) -> SizeSliderView { let view = SizeSliderView(startValue: startValue, defaultDiameter: defaultDiameter) view.minimumValue = 0.1 view.maximumValue = 1 return view } func updateUIView(_ uiView: SizeSliderView, context: Context) {} } Content view: struct ContentView: View { var body: some View { SizeSlider(startValue: 0.20, defaultDiameter: 100) .frame(width: 400) } }
7
0
504
Aug ’24
Passing touches from UIView to a child view?
I made an extension with a UIView that takes a SwiftUI view, gets its UIView, and then adds it as a subview. But now the subview isn't receiving any touches and idk how to fix that. I've already tried point(inside:with:) but it doesn't seem to work. I've also tried forwarding the touches from touchesBegan, touchesMoved, etc., but that didn't work either. Any help or advice would be greatly appreciated! Extension with the UIView: // Add view modifier to View extension View { func transformable() -> some View { modifier(Transformable()) } } // View modifier struct Transformable: ViewModifier { func body(content: Content) -> some View { TransformableUIView(content: content) } } // Wrap UIView struct TransformableUIView<V>: UIViewRepresentable where V: View { private var content: V init(content: V) { self.content = content } func makeUIView(context: Context) -> TransformableView<V> { TransformableView(content: content) } func updateUIView(_ uiView: TransformableView<V>, context: Context) {} } // View that handles zoom, pan, and rotate gestures class TransformableView<V>: UIView, UIGestureRecognizerDelegate where V: View { private var content: V private var initialCenter: CGPoint = .zero private var totalScale: CGFloat = 1.0 private var boundsDidSet = false required init(content: V) { self.content = content super.init(frame: .zero) self.addSubview(content.uiView) let panGesture = UIPanGestureRecognizer(target: self, action: #selector(panPiece(_:))) panGesture.minimumNumberOfTouches = 2 panGesture.maximumNumberOfTouches = 2 panGesture.delegate = self self.addGestureRecognizer(panGesture) let scaleGesture = UIPinchGestureRecognizer(target: self, action: #selector(scalePiece(_:))) scaleGesture.delegate = self self.addGestureRecognizer(scaleGesture) let rotateGesture = UIRotationGestureRecognizer(target: self, action: #selector(rotatePiece(_:))) rotateGesture.delegate = self self.addGestureRecognizer(rotateGesture) } // Position content in center of view override func layoutSubviews() { super.layoutSubviews() // Return if bounds are already set if boundsDidSet { return } guard let piece = self.subviews.first else { return } piece.center = CGPoint(x: self.bounds.size.width / 2, y: self.bounds.size.height / 2) boundsDidSet = true } // Function called when pan gesture is recognized @objc private func panPiece(_ gestureRecognizer: UIPanGestureRecognizer) { guard let piece = gestureRecognizer.view?.subviews.first else { return } // Get the changes in the X and Y directions relative to // the superview's coordinate space. let translation = gestureRecognizer.translation(in: piece.superview) if gestureRecognizer.state == .began { // Save the view's original position. self.initialCenter = piece.center } // Update the position for the .began, .changed, and .ended states if gestureRecognizer.state != .cancelled { // Add the X and Y translation to the view's original position. var newCenter = CGPoint(x: initialCenter.x + translation.x, y: initialCenter.y + translation.y) // Prevent content from leaving view newCenter.x = clamp(value: newCenter.x, min: 0, max: self.bounds.width) newCenter.y = clamp(value: newCenter.y, min: 0, max: self.bounds.height) piece.center = newCenter } else { // On cancellation, return the piece to its original location. piece.center = initialCenter } } // Function called when scale gesture is recognized @objc private func scalePiece(_ gestureRecognizer : UIPinchGestureRecognizer) { guard let piece = gestureRecognizer.view?.subviews.first else { return } if gestureRecognizer.state == .began || gestureRecognizer.state == .changed { // Set min/max zoom let newScale = clamp(value: totalScale * gestureRecognizer.scale, min: 0.2, max: 20) / totalScale piece.transform = (piece.transform.scaledBy(x: newScale, y: newScale)) gestureRecognizer.scale = 1.0 totalScale *= newScale } } // Function called when rotate gesture is recognized @objc private func rotatePiece(_ gestureRecognizer : UIRotationGestureRecognizer) { guard let piece = gestureRecognizer.view?.subviews.first else { return } if gestureRecognizer.state == .began || gestureRecognizer.state == .changed { piece.transform = piece.transform.rotated(by: gestureRecognizer.rotation) gestureRecognizer.rotation = 0 } } func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { return true } func clamp(value: CGFloat, min: CGFloat, max: CGFloat) -> CGFloat { if value < min { return min } else if value > max { return max } else { return value } } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } } Get UIView from SwiftUI View: // Get UIView from SwiftUI View extension View { var uiView: UIView { UIHostingController(rootView: self).view } } ContentView: struct ContentView: View { var body: some View { CanvasView() .frame(width: 880, height: 608) .transformable() .background(Color.blue) } }
1
0
628
Jul ’24
iPad disappearing under Xcode devices and simulators
When I plug in my iPad, it appears for a few seconds and prompts me to trust the device, which I do, but a few seconds later it just disappears. When I disconnect and reconnect again, the same thing happens again. I tried restarting my devices, the terminal command, and trying different USB ports, but to no avail. My iPad does show up in Finder however. How can I resolve this issue? I'm on macOS 13.4, with Xcode 15 beta 2, and iPadOS 17. Any help would be greatly appreciated! ^ This screen appears when I disconnect and reconnect and it prompts me to click trust and enter my password, but then it just suddenly disappears ^This is what shows up when my iPad is disconnected
0
1
603
Jun ’23
Playground app won't run on Swift Playgrounds?
My playground app runs perfectly fine when I compile it on Xcode on my Mac to run it on my iPad, but for some reason it won't run at all when I try running it directly on Swift Playgrounds (the file opens and it doesn't give me any errors, but it won't compile). How might I be able to fix this? My app is a .swiftpm file and I'm using Swift Playgrounds 4.0.2 on iPadOS 15.4.1. Any help would be greatly appreciated!
2
0
2.2k
Apr ’22