Post

Replies

Boosts

Views

Activity

Move and delete in ForEach without List
Here is my code simplified. struct TileView: View { @Binding var editMode: EditMode var columns = [GridItem(.adaptive(minimum: UIScreen.main.bounds.width / 4))] var body: some View { LazyVGrid(columns: columns, spacing: 16) { ForEach(0 ..< 5, id: \.self) { num in ZStack(alignment: .topLeading) { GroupBox(label: Text("Label")) { Text("Content") } .contextMenu { Button { } label: { Label("Delete", systemImage: "trash") } } // this is the work around for the delete action if editMode == .active { Button { } label: { Label("Delete", systemImage: "minus.circle.fill") .imageScale(.large) .foregroundColor(Color(.systemRed)) .backgroundColor(Color(.systemFill) .clipShape(Circle()) .labelStyle(IconOnlyLabelStyle()) .offset(x: -10, y: -10) } } } .padding(.horizontal, 4) } } } .padding(.horizontal) } } Is there any way I can add an .onMove or .onDelete modifier to the ForEach and have those actions work when editing, or something else similar (maybe implementing a List to make it work)? If not, is there a workaround for the move action (since I already have a delete action workaround)? Thanks in advance for any solutions.
1
0
1.8k
Aug ’20
pressesBegan() in UIViewController connected to SwiftUI view
I have a TypingController which when certain key are pressed will send out notifications. A SwiftUI view will receive those notifications and do something with them. I am trying to figure out how to add this view controller to SceneDelegate.swift to maybe set this as the window.rootViewController but I am unable to work out how. Any solutions to this would be appreciated. Here's my code: extension Notification.Name { static let enter = Notification.Name("enter") static let remove = Notification.Name("remove") static let submit = Notification.Name("submit") } class TypingController: UIViewController { override func pressesBegan(_ presses: Set<UIPress>, with event: UIPressesEvent?) { guard let key = presses.first?.key else { return } switch key.keyCode { case .keyboardDeleteOrBackspace: NotificationCenter.default.post(name: .remove, object: nil) case .keyboardReturn: NotificationCenter.default.post(name: .submit, object: nil) default: guard let characters = key.characters else { return } if let number = Int(characters) { NotificationCenter.default.post(name: .enter, object: number) } else { super.pressesBegan(presses, with: event) } } } }
1
0
1.2k
Aug ’20
Pass in selection variable from Picker to SKScene
I have a simple SwiftUI view with a Picker: struct ContentView: View { @State private var selection = 0 @State private var showingGame = false var body: some View { VStack { Picker("Choose option", selection: $selection) { ForEach(0 ..< 10, id: \.self) { Text("Option \($0)") } } Button("Play") { showingGame = true } .fullScreenCover(isPresented: $showingGame) { GeometryReader { geometry in SpriteView(scene: scene(size: geometry.size, preferredFramesPerSecond: 120) .frame(width: geometry.size.width, height: geometry.size.height) } .ignoresSafeArea() } } } func scene(size: CGSize) -> SKScene { let scene = GameScene(size: size, selection: selection) scene.size = size scene.anchorPoint = CGPoint(x: 0.5, y: 0.5) scene.scaleMode = .aspectFill return scene } } and a SKScene with an custom init(): class GameScene: SKScene { var size: CGSize var selection: Int let player: SKSpriteNode ... init(size: CGSize, selection: Int) { self.size = size self.selection = selection player = SKSpriteNode(imageNamed: "player\(selection)") super.init(size: size) } ... } I would like to know how to pass in the current value of selection, after the picker has changed it, to the GameScene instead of the initially set value being passed in all the time.
0
0
613
Sep ’20
Load and save image from document directory
I have added two methods to an extension of URL so I can load and save and images. extension URL { func loadImage(_ image: inout UIImage) { if let loaded = UIImage(contentsOfFile: self.path) { image = loaded } } func saveImage(_ image: UIImage) { if let data = image.jpegData(compressionQuality: 1.0) { try? data.write(to: self) } } } I use this extension in a view: @State private var image = UIImage(systemName: "xmark")! private var url: URL { let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask) return paths[0].appendingPathComponent("image.jpg") } var body: some View { Image(uiImage: image) .onAppear { url.load(&image) } .onTapGesture { url.save(image) } } However this isn't working. The image isn't loaded from the document directory because its probably isn't being saved. Is there anyway to rewrite this extension or another alternative to loading and saving a UIImage? Or is this just a bug with Xcode 12/iOS 14?
3
1
7.6k
Sep ’20
App crashes when setting variable to nil
I have been experiencing a problem that I can't seem to figure out how it's happening or how to fix it. When then view below appears, the app crashes and shows this message: Thread 1: EXC_BREAKPOINT (code=1, subcode=0x1a2120fac) If I remove the line that sets color to nil the view appears without any crashes. ... @Binding var color: Color? var body: some View { NavigationView { ... } .onAppear { switch background { case .color: color = nil // error not occurring when this line removed ... } } } I have looked up the error and worked out its about nil and optionals, but I don't know why setting the variable to nil doesn't work. Has anyone got any ideas as to why this is happening and how to fix it? Thanks is advance.
18
0
2.2k
Oct ’20
Conform struct with UIImage to Codable
I have a data model with UIImage, Color and TextAlignment in. I think I have managed to conform Color and TextAlignment to Codable. I am unsure of how to conform UIImage to Codable. I have an attempt below which crashes when the app loads, probably because there is something wrong with line 20 because backgroundImage is initially nil. struct Model: Codable { private enum CodingKeys: CodingKey { case image } var backgroundColor: Color var textColor: Color var textAlignment: TextAlignment var image: UIImage? init(backgroundColor: Color = Color(.systemFill), textColor: Color = Color(.label), textAlignment: TextAlignment = .leading, image: UIImage? = nil) { self.backgroundColor = backgroundColor self.textColor = textColor self.textAlignment = textAlignment self.image = image } init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) let data = try container.decode(Data.self, forKey: .image) try self.init(from: decoder) self.image = UIImage(data: data) } func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) if let image = self.image { let data = image.jpegData(compressionQuality: 1.0) try container.encode(data, forKey: .image) } } } Can anyone correct or point me in the right direction as how to fix this? If is anything wrong with the Codable conformations below, please let me know. Any help would be really appreciated. Color and TextAlignment Codable below: extension Color: Codable { private enum CodingKeys: CodingKey { case color } public init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) self = try container.decode(Color.self, forKey: .color) try self.init(from: decoder) } public func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(self, forKey: .color) } } extension TextAlignment: Codable { private enum CodingKeys: CodingKey { case center case leading case trailing } public init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) if let decoded = try? container.decode(String.self, forKey: .center), decoded == "center" { self = .center } if let decoded = try? container.decode(String.self, forKey: .leading), decoded == "leading" { self = .leading } if let decoded = try? container.decode(String.self, forKey: .trailing), decoded == "trailing" { self = .trailing } try self.init(from: decoder) } public func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) switch self { case .center: try container.encode("center", forKey: .center) case .leading: try container.encode("leading", forKey: .leading) case .trailing: try container.encode("trailing", forKey: .trailing) } } }
5
0
1.4k
Oct ’20
Returning redacted(reason:) or unredacted() from method causes mismatching types error
The error I am getting is: Result values in '? :' expression have mismatching types 'some View' (result of 'Self.redacted(reason:)') and 'some View' (result of 'Self.unredacted()') But aren't some View and some View the same thing. This method is part of a view: func redacted(when active: Bool) -> some View { active ? redacted(reason: .placeholder) : unredacted() } and then used on the view in ContentView like this: TestView() .redacted(when: isActive) Even if I use an if statement this error still occurs: Function declares an opaque return type, but the return statements in its body do not have matching underlying types How can I return either of two modifiers that both return some View from a method that returns some View?
1
0
768
Oct ’20
Error when previewing on device
When the Xcode Previews app opens on the device, this is the error message in Xcode: RemoteHumanReadableError: Failed to update preview. Error encountered when sending 'display' message to agent. I tried cleaning the build folder and quitting then reopening Xcode to no avail. Using Xcode 12.0.1 previewing on iPhone 11.
18
0
8.4k
Oct ’20
Change navigation bar back button title
Is there any way to change this without using a toolbar item, and instead changing it something like this: .onAppear { UINavigationBar().backItem?.title = "Title" // also tried this UINavigationController().navigationBar.backItem?.title = "Title" } which I can’t seem to get working yet.
1
0
1.2k
Oct ’20
No cancel button in UIFontPickerViewController
I have made a font picker but the problem is that I can’t seem to figure out why there is no navigation bar with a cancel button and search bar. Normally there would be but with this there isn’t (there is a fontPickerViewControllerDidCancel method but no cancel button). Has anyone else had this problem, and is there a way to resolve this? struct FontPicker: UIViewControllerRepresentable { class Coordinator: NSObject, UIFontPickerViewControllerDelegate { private let parent: FontPicker init(_ parent: FontPicker) { self.parent = parent } func fontPickerViewControllerDidPickFont(_ viewController: UIFontPickerViewController) { parent.presentationMode.wrappedValue.dismiss() guard let descriptor = viewController.selectedFontDescriptor else { return } let font = UIFont(descriptor: descriptor, size: 17) parent.fontName = font.fontName } } @Environment(\.presentationMode) private var presentationMode @Binding var fontName: String func makeUIViewController(context: Context) -> some UIViewController { let configuration = UIFontPickerViewController.Configuration() configuration.includeFaces = true let picker = UIFontPickerViewController(configuration: configuration) picker.delegate = context.coordinator return picker } func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) {} func makeCoordinator() -> Coordinator { Coordinator(self) } } // used in a View like this Button("Choose font") { showingFontPicker = true } .sheet(isPresented: $showingFontPicker) { FontPicker(fontName: $newFontName) .ignoresSafeArea() }
3
0
1k
Oct ’20
matchedGeometryEffect in LazyVGrid
I have a LazyVGrid of items which when tapped I want to showing details of that item. When the item is tapped the item should animate to the detail view with matchedGeometryEffect. However this doesn’t work. Even with the withAnimation there is no animation. How can this code work with matchedGeometryEffect? Any help will be appreciated. I have replaced the items in the grid with numbers to make it simpler. struct ContentView: View { @Namespace private var animation @State private var showingDetail = false @State private var selection = -1 var detail: some View { VStack { Spacer() Image(systemName: "\(selection).square") .resizable() .scaledToFit() .matchedGeometryEffect(id: selection, in: animation) Spacer() Text("Number \(selection)") .font(.headline) .padding(.bottom) } } var grid: some View { ScrollView { LazyVGrid(columns: [GridItem(.adaptive(minimum: 100, maximum: 100))]) { ForEach(0 ..< 20) { num in Image(systemName: "\(num).square") .resizable() .scaledToFit() .frame(width: 100, height: 100) .matchedGeometryEffect(id: num, in: animation) .onTapGesture { selection = num withAnimation { showingDetail = true } } } } } } var body: some View { NavigationView { Group { if showingDetail { detail .toolbar { Button { withAnimation { showingDetail = false } } label: { Image(systemName: "xmark.circle.fill") .imageScale(.large) .foregroundColor(.secondary) } } } else { grid } } .navigationTitle(showingDetail ? "" : "Numbers") .navigationBarTitleDisplayMode(showingDetail ? .inline : .automatic) } } }
0
0
970
Dec ’20
Search bar not updating text in UI
I have implemented a custom NavigationView with a search bar in SwiftUI. The SearchResultsContent doesn’t update properly though. I can’t seem to figure out why. I’ve done all the UISearchResultsUpdating related things, but that doesn’t fix the problem. struct SearchBarNavigationView<SearchResultsContent: View, Content: View>: UIViewControllerRepresentable { class Coordinator: NSObject, UISearchResultsUpdating { private let parent: SearchBarNavigationView init(_ parent: SearchBarNavigationView) { self.parent = parent } func updateSearchResults(for searchController: UISearchController) { guard let searchText = searchController.searchBar.text else { return } DispatchQueue.main.async { self.parent.text = searchText } } } @Binding private var text: String private let searchResultsContent: SearchResultsContent private let content: Content init(text: Binding<String>, @ViewBuilder searchResultsContent: () -> SearchResultsContent, @ViewBuilder content: () -> Content) { _text = text self.searchResultsContent = searchResultsContent() self.content = content() } func makeUIViewController(context: Context) -> UINavigationController { let rootViewController = UIHostingController(rootView: content) let navigationController = UINavigationController(rootViewController: rootViewController) navigationController.navigationBar.prefersLargeTitles = true let searchResultsController = UIHostingController(rootView: searchResultsContent) let searchController = UISearchController(searchResultsController: searchResultsController) searchController.searchResultsUpdater = context.coordinator searchController.obscuresBackgroundDuringPresentation = false searchController.searchBar.autocapitalizationType = .none navigationController.navigationBar.topItem?.searchController = searchController return navigationController } func updateUIViewController(_ uiViewController: UINavigationController, context: Context) { if let searchResultsController = uiViewController.navigationBar.topItem?.searchController?.searchResultsController as? UIHostingController<SearchResultsContent> { searchResultsController.rootView = searchResultsContent searchResultsController.view.setNeedsDisplay() } if let rootViewController = uiViewController.topViewController as? UIHostingController<Content> { rootViewController.rootView = content rootViewController.view.setNeedsDisplay() } uiViewController.navigationBar.topItem?.searchController?.searchBar.text = text } func makeCoordinator() -> Coordinator { Coordinator(self) } } Implementation in View SearchBarNavigationView(text: $searchText) { Text(searchText) // doesn’t change whilst typing } content: { List(0 ..< 20) { Text("Row \($0)") } } .ignoresSafeArea() .onChange(of: searchText) { value in print(value) // works correctly }
2
0
1.4k
Dec ’20
Show default detail screen in UINavigationController
How can I show a default view when there's no selection like in a normal NavigationView but with a UINavigationController? I've tried this } content: { NavigationView { Text("Hello, World!") .navigationTitle("Title") Text("No selection") } .navigationBarHidden(true) } but then the search bar doesn't show. struct SearchBarNavigationView<SearchResultsContent: View, Content: View>: UIViewControllerRepresentable { class Coordinator: NSObject, UISearchResultsUpdating { private let parent: SearchBarNavigationView init(_ parent: SearchBarNavigationView) { self.parent = parent } func updateSearchResults(for searchController: UISearchController) { guard let searchText = searchController.searchBar.text else { return } DispatchQueue.main.async { self.parent.text = searchText.trimmingCharacters(in: .whitespaces) } } } @Binding private var text: String private let searchResultsContent: SearchResultsContent private let content: Content init(text: Binding<String>, @ViewBuilder searchResultsContent: () -> SearchResultsContent, @ViewBuilder content: () -> Content) { _text = text self.searchResultsContent = searchResultsContent() self.content = content() } func makeUIViewController(context: Context) -> UINavigationController { let rootViewController = UIHostingController(rootView: content) let navigationController = UINavigationController(rootViewController: rootViewController) navigationController.navigationBar.prefersLargeTitles = true let searchResultsController = UIHostingController(rootView: searchResultsContent) let searchController = UISearchController(searchResultsController: searchResultsController) searchController.searchResultsUpdater = context.coordinator searchController.obscuresBackgroundDuringPresentation = false searchController.searchBar.autocapitalizationType = .none navigationController.navigationBar.topItem?.searchController = searchController return navigationController } func updateUIViewController(_ uiViewController: UINavigationController, context: Context) { if let searchResultsController = uiViewController.navigationBar.topItem?.searchController?.searchResultsController as? UIHostingController<SearchResultsContent> { searchResultsController.rootView = searchResultsContent searchResultsController.view.setNeedsDisplay() } if let rootViewController = uiViewController.topViewController as? UIHostingController<Content> { rootViewController.rootView = content rootViewController.view.setNeedsDisplay() } uiViewController.navigationBar.topItem?.searchController?.searchBar.text = text } func makeCoordinator() -> Coordinator { Coordinator(self) } } SearchBarNavigationView(text: $searchText) { Text(searchText) } content: { Text("Hello, World!") .navigationTitle("Title") Text("No selection") // make this show when no selection } .ignoresSafeArea()
1
0
707
Dec ’20