Post

Replies

Boosts

Views

Activity

How do you control the location of the menu in a UIContextMenu ?
class Coordinator: NSObject, UIContextMenuInteractionDelegate, ContextMenuManagerDelegate { var container: ContextMenuContainer var auxiliaryContent: AuxiliaryContent var menuItems: () -> [UIMenuElement] init(container: ContextMenuContainer) { self.container = container self.auxiliaryContent = container.auxiliaryContent self.menuItems = container.menuItems super.init() } func contextMenuInteraction( _ interaction: UIContextMenuInteraction, configurationForMenuAtLocation location: CGPoint ) -> UIContextMenuConfiguration? { container.viewModel.contextMenuManager?.notifyOnContextMenuInteraction( interaction, configurationForMenuAtLocation: location ) return UIContextMenuConfiguration(identifier: nil, previewProvider: nil) { [weak self] _ in UIMenu(title: "", children: self?.menuItems() ?? []) } } func contextMenuInteraction( _ interaction: UIContextMenuInteraction, willDisplayMenuFor configuration: UIContextMenuConfiguration, animator: UIContextMenuInteractionAnimating? ) { container.viewModel.contextMenuManager?.notifyOnContextMenuInteraction( interaction, willDisplayMenuFor: configuration, animator: animator ) } func contextMenuInteraction( _ interaction: UIContextMenuInteraction, willEndFor configuration: UIContextMenuConfiguration, animator: UIContextMenuInteractionAnimating? ) { container.viewModel.contextMenuManager?.notifyOnContextMenuInteraction( interaction, willEndFor: configuration, animator: animator ) } func contextMenuInteraction( _ interaction: UIContextMenuInteraction, previewForHighlightingMenuWithConfiguration configuration: UIContextMenuConfiguration ) -> UITargetedPreview? { guard let targetView = interaction.view else { return nil } let bubbleWithTail = BubbleWithTailPath() let customPath = bubbleWithTail.path(in: targetView.bounds) let parameters = UIPreviewParameters() parameters.visiblePath = customPath return UITargetedPreview( view: targetView, parameters: parameters ) } func onRequestMenuAuxiliaryPreview(sender: ContextMenuManager) -> UIView? { let hostingController = UIHostingController(rootView: auxiliaryContent) hostingController.view.backgroundColor = .clear return hostingController.view } } i am trying to recreate apple's iMessage tapback reaction context menu. i have figured out how to attach an auxiliary view to the view with the menu, however the menu itself varies in position. if i bring up the context menu on a view below half the screen height, the menu appears above the view. else, it appears below. i need it to always be below.
0
0
164
Nov ’24
How to transition & resize a view from one view controller to another using UIKit & Snapshots?
I am trying to seamlessly transition a view from one view controller and have it "jump" into a sheet. Apple does this in their widget picker for example when you add one of the top widgets. Based on all the documentation I've read, the most common approach to this is to snapshot the area/view that you are trying to seamlessly transition in the presented view controller (the place where the item is coming from. And then have that snapshot translate across into where it should be in the layout of the presenting view controller. func makeAnimatorIfNeeded( using transitionContext: UIViewControllerContextTransitioning ) -> UIViewPropertyAnimator { if let animator = animator { return animator } // Presentation animation let animator = UIViewPropertyAnimator( duration: 0.5, timingParameters: UISpringTimingParameters(dampingRatio: 1.0) ) guard let fromVC = transitionContext.viewController(forKey: .from), let toVC = transitionContext.viewController(forKey: .to) else { transitionContext.completeTransition(false) return animator } /// !IMPORTANT: For some weird reason, accessing the views of the view controller /// directly, like `fromVC.view` or `toVC.view` will break the sheet transition and gestures. /// Instead, use the `view(forKey:)` method of the `transitionContext` to get the views. let fromView = transitionContext.view(forKey: .from) let toView = transitionContext.view(forKey: .to) let containerView = transitionContext.containerView let fromFrame = transitionContext.viewController(forKey: .from).map { transitionContext.finalFrame(for: $0) } ?? containerView.bounds let toFrame = transitionContext.viewController(forKey: .to).map { transitionContext.finalFrame(for: $0) } ?? containerView.bounds // Calculate the frame of sourceView relative to fromVC.view to capture the correct snapshot area. let snapshotFrameInFromVC = sourceView?.convert(sourceView?.frame ?? .zero, to: fromVC.view) ?? containerView.frame // Calculate the frame of sourceView relative to containerView to position the snapshot correctly. let snapshotFrameInContainer = sourceView?.convert(sourceView?.frame ?? .zero, to: containerView) ?? containerView.frame let snapshot: UIView? if isPresenting { // Create a snapshot of fromVC.view from the defined area (snapshotFrameInFromVC). let originalColor = fromVC.view.backgroundColor fromVC.view.backgroundColor = .clear snapshot = fromVC.view.resizableSnapshotView(from: snapshotFrameInFromVC, afterScreenUpdates: true, withCapInsets: .zero) fromVC.view.backgroundColor = originalColor // Position the snapshot correctly within the snapshot container snapshot?.frame = snapshotFrameInContainer toView?.frame = toFrame toView?.transform = CGAffineTransform(translationX: 0, y: containerView.frame.size.height) toView?.layoutIfNeeded() if let fromView { containerView.addSubview(fromView) } if let toView { containerView.addSubview(toView) containerView.addSubview(snapshot ?? UIView()) } let toViewCenter = CGPoint( x: toVC.view.bounds.midX, y: toVC.view.bounds.midY + 55 ) let gestureVelocity = CGPoint(x: 0, y: -5000) animator.addAnimations { Wave.animate( withSpring: self.animatedSpring, mode: .animated, gestureVelocity: gestureVelocity ) { snapshot?.animator.frame.size = CGSize(width: 204, height: 204) // Important to animate first snapshot?.animator.center = toViewCenter } completion: { finished, retargeted in print("finished: \(finished), retargeted: \(retargeted)") } toView?.transform = CGAffineTransform.identity } animator.addCompletion { animatingPosition in switch animatingPosition { case .end: snapshot?.removeFromSuperview() transitionContext.completeTransition(true) default: transitionContext.completeTransition(false) } } } else { // Transitioning view is fromView if let toView { containerView.addSubview(toView) } if let fromView { containerView.addSubview(fromView) } animator.addAnimations { fromView?.transform = CGAffineTransform(translationX: 0, y: containerView.frame.size.height) } animator.addCompletion { animatingPosition in switch animatingPosition { case .end: transitionContext.completeTransition(true) default: transitionContext.completeTransition(false) } } } self.animator = animator return animator } I can pull this off seamlessly if the animation involves a simple movement from A to B. But if I try to resize the snapshot as well, the snapshot becomes pixelated and low quality (I assume this is because the snapshot is literally a small screenshot and I'm stretching it from 56x56 to 204x204). Is there another way that I'm overlooking to pull this off without a snapshot? Or is there a way I can resize the snapshot without losing quality? Here is the animator code, it works great without the sizing so this should help future people looking to replicate the same effect anyways.
0
0
200
Sep ’24
.matchedGeomtryEffect from View to Sheet.
I'm trying to accomplish something like this: https://x.com/mlaithv/status/1835041850236838265 But it seems like Apple uses a Private API to pull it off. Has anyone managed to create something similar? I know there is a package named Transmission that can do it but it seems hacky & I'm unfamiliar with UIKit. The source uses a "custom modal" but I'm not sure how.
0
0
240
Sep ’24
how do you pass a texture to a metal shader in swiftui?
specifically using the newer colorEffect, layerEffect, etc routes. the below does not seem to work. do i have to use the old MTK stuff? import SwiftUI import MetalKit struct HoloPreview: View { let startDate = Date() @State private var noiseTexture: MTLTexture? var body: some View { TimelineView(.animation) { context in RoundedRectangle(cornerRadius: 20) .fill(Color.red) .layerEffect(ShaderLibrary.iridescentEffect( .float(startDate.timeIntervalSinceNow), .texture(noiseTexture) ), maxSampleOffset: .zero) } .onAppear { noiseTexture = loadTexture(named: "perlinNoiseMap") } } func loadTexture(named imageName: String) -> MTLTexture? { guard let device = MTLCreateSystemDefaultDevice(), let url = Bundle.main.url(forResource: imageName, withExtension: "png") else { return nil } let textureLoader = MTKTextureLoader(device: device) let texture = try? textureLoader.newTexture(URL: url, options: nil) return texture } } #Preview { HoloPreview() }
1
0
420
Sep ’24
How to add a sort of engraving type of text to a mesh in SceneKit?
i'm trying to figure out how to basically engrave some text into this ellipsoid mesh. so far the only thing i've learned that can sort of come close to what im looking for is SCNText but it floats above the ellipsoid and doesnt conform to the angular shape. let allocator = MTKMeshBufferAllocator(device: MTLCreateSystemDefaultDevice()!) let disc = MDLMesh.newEllipsoid( withRadii: vector_float3(Float(discDiameter/2), Float(discDiameter/2), Float(discThickness/2)), radialSegments: 64, verticalSegments: 64, geometryType: .triangles, inwardNormals: false, hemisphere: false, allocator: allocator ) let discGeometry = SCNGeometry(mdlMesh: disc) let material = createIridescentMaterial() discGeometry.materials = [material]
2
0
719
Jul ’24
SwiftData nested models crash upon trying to access nested data.
I am new to swift. This is my Item.swift. import SwiftData @Model final class Item: Codable { var id: String var soundId: String var soundAppleId: String var soundType: String var type: String var authorId: String var text: String var createdAt: Date var actionsCount: Int var chainsCount: Int var rating: Int var loved: Bool var replay: Bool var heartedByUser: Bool @Relationship var author: Author? init(id: String, soundId: String, soundAppleId: String, soundType: String, type: String, authorId: String, text: String, createdAt: Date, actionsCount: Int, chainsCount: Int, ratings: Int, loved: Bool, replay: Bool, heartedByUser: Bool, author: Author) { self.id = id self.soundId = soundId self.soundAppleId = soundAppleId self.soundType = soundType self.type = type self.authorId = authorId self.text = text self.createdAt = createdAt self.actionsCount = actionsCount self.chainsCount = chainsCount self.rating = ratings self.loved = loved self.replay = replay self.heartedByUser = heartedByUser self.author = author } private enum CodingKeys: String, CodingKey { case id case soundId = "sound_id" case soundAppleId = "sound_apple_id" case soundType = "sound_type" case type case authorId = "author_id" case text case createdAt = "created_at" case actionsCount = "actions_count" case chainsCount = "chains_count" case rating case loved case replay case heartedByUser case author } required init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) id = try container.decode(String.self, forKey: .id) soundId = try container.decode(String.self, forKey: .soundId) soundAppleId = try container.decode(String.self, forKey: .soundAppleId) soundType = try container.decode(String.self, forKey: .soundType) type = try container.decode(String.self, forKey: .type) authorId = try container.decode(String.self, forKey: .authorId) text = try container.decode(String.self, forKey: .text) let dateString = try container.decode(String.self, forKey: .createdAt) let formatter = DateFormatter() formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'" if let date = formatter.date(from: dateString) { createdAt = date } else { throw DecodingError.dataCorruptedError(forKey: .createdAt, in: container, debugDescription: "Date string does not match format expected by formatter.") } actionsCount = try container.decode(Int.self, forKey: .actionsCount) chainsCount = try container.decode(Int.self, forKey: .chainsCount) rating = try container.decode(Int.self, forKey: .rating) loved = try container.decode(Bool.self, forKey: .loved) replay = try container.decode(Bool.self, forKey: .replay) heartedByUser = try container.decode(Bool.self, forKey: .heartedByUser) author = try container.decode(Author.self, forKey: .author) } func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(id, forKey: .id) try container.encode(soundId, forKey: .soundId) try container.encode(soundAppleId, forKey: .soundAppleId) try container.encode(soundType, forKey: .soundType) try container.encode(type, forKey: .type) try container.encode(authorId, forKey: .authorId) try container.encode(text, forKey: .text) let formatter = DateFormatter() formatter.dateFormat = "yyyy-MM-dd" let dateString = formatter.string(from: createdAt) try container.encode(dateString, forKey: .createdAt) try container.encode(actionsCount, forKey: .actionsCount) try container.encode(chainsCount, forKey: .chainsCount) try container.encode(rating, forKey: .rating) try container.encode(loved, forKey: .loved) try container.encode(replay, forKey: .replay) try container.encode(heartedByUser, forKey: .heartedByUser) try container.encode(author, forKey: .author) } } @Model final class Author: Codable { var id: String var image: URL var username: String var bio: String? init(id: String, image: URL, username: String, bio: String?) { self.id = id self.image = image self.username = username self.bio = bio } private enum CodingKeys: String, CodingKey { case id case image case username case bio } required init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) id = try container.decode(String.self, forKey: .id) image = try container.decode(URL.self, forKey: .image) username = try container.decode(String.self, forKey: .username) bio = try container.decodeIfPresent(String.self, forKey: .bio) } func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(id, forKey: .id) try container.encode(image, forKey: .image) try container.encode(username, forKey: .username) try container.encodeIfPresent(bio, forKey: .bio) } } In my ItemView when I try to access something inside author, Swift preview crashes. Thread 0 Crashed:: Dispatch queue: com.apple.main-thread 0 SwiftData 0x1cb459e90 0x1cb3d3000 + 552592 1 SwiftData 0x1cb45ba7c 0x1cb3d3000 + 559740 2 SwiftData 0x1cb45e5f8 0x1cb3d3000 + 570872 3 SwiftData 0x1cb4190e4 0x1cb3d3000 + 286948 4 audition 0x100b436e0 Item.author.getter + 320 (@__swiftmacro_8audition4ItemC6author18_PersistedPropertyfMa_.swift:9) 5 ContentView.1.preview-thunk.dylib 0x105f23a20 closure #1 in closure #1 in closure #1 in closure #1 in ItemCard.__preview__body.getter + 820 (ContentView.swift:89) 6 SwiftUI 0x1cba41308 0x1cb47b000 + 6054664 7 ContentView.1.preview-thunk.dylib 0x105f22ee4 closure #1 in closure #1 in closure #1 in ItemCard.__preview__body.getter + 472 (ContentView.swift:84) 8 SwiftUI 0x1cc2e6c40 0x1cb47b000 + 15121472 9 ContentView.1.preview-thunk.dylib 0x105f228b8 closure #1 in closure #1 in ItemCard.__preview__body.getter + 388 (ContentView.swift:83) ...
3
1
668
Apr ’24
Getting a 401 error when trying to connect to Apple Music API
export default function MyComponent() { const { isLoading, error, data } = useQuery( "songs", async () => { const response = await fetch( "https://api.music.apple.com/v1/catalog/us/search?term=beach+bunny" ); console.log(response.headers); // log the response headers const data = await response.json(); return data.results; }, { headers: { Authorization: `Bearer ${"thisiswheremytokenis"}`, }, } ); if (isLoading) { return <div>Loading...</div>; } if (error) { return <div>Error fetching data: {error.message}</div>; } return ( <div> <h1>Songs</h1> <ul> {data.map((song) => ( <li key={song.id}>{song.attributes.name}</li> ))} </ul> </div> ); } It's just a simple react component thats using react-query and I cannot for the life of me figure out why it's not working. I know my Token works because when I verify it with the curl command in the docs it gives me code 200.
0
0
472
Mar ’23