Giter Site home page Giter Site logo

notes's People

Contributors

gshaw avatar

Stargazers

 avatar

Watchers

 avatar  avatar

notes's Issues

Spellcheck code with codespell

Install with Homebrew

brew install codespell

Example how to skip specific folders and files ignoring lines that found in the file .codespellignorelines. The ignore line file is codespell's way of silencing false positives that can creep up.

codespell --enable-colors --exclude-file .codespellignorelines  --skip deps,priv,erl_crash.dump

I discovered this tool when I noticed in the Rails CI lint workflow.

Source:

How to make view night vision compatible

While working on Land Nav I stumbled across a view modifier called luminanceToAlpha which when paired with .background(.green) caused the map to have a cool night vision compatible look.

Screenshot 2023-05-06 at 10 05 59 PM Screenshot 2023-05-06 at 10 52 22 PM

Unfortunately because I also want to switch that mode off and that luminanceToAlpha isn't compatible with a ternary operator it will cause the expensive Mapbox view to be recreated each time that changes. This caused a 200MB memory leak which I wasn't able to determine why.

The result is that while it looks cool and I was hoping to go this direction I reverted the changed and stuck with the Dark Mapbox style so as to avoid the performance hit.

Not having conditional view modifiers in SwiftUI is a huge problem that feels easy to fix but the fix is almost certainly broken. See the reference for details..

Reference: https://www.objc.io/blog/2021/08/24/conditional-view-modifiers/

Crash in ListView when using sections

Today I encountered a difficult to understand crash when one ListView was pushing to another View (in this case another ListView but I don't that is related). In the detail view an action was performed which would cause a Section to be removed from the Parent view on next render. SwiftUI really did not like this and crashed are at @main with an unhelpful stack trace.

Only through trial and error and vague recollection of encountering this before did I fix this by ensuring no sections are added or removed by child views of a ListView.

Changing a viewโ€™s layout in response to size classes

import SwiftUI

struct SizeClassView: View {
    @Environment(\.horizontalSizeClass) var hor: UserInterfaceSizeClass?
    @Environment(\.verticalSizeClass) var ver: UserInterfaceSizeClass?
  
    var body: some View {
        VStack(alignment: .center, spacing: 10) {
            Text("horizontal size class \(hor == .regular ? "regular" : "compact")")
            Text("vertical size class \(ver == .regular ? "regular" : "compact")")
          
             if hor == .regular {
                Text("You can see me, if there is enough space.")
            }
        }
    }
}
  • All iPhones in portrait have compact width and regular height.
  • Most iPhones in landscape have compact width and compact height.
  • Large iPhones (Plus-sized and Max devices) in landscape have regular width and compact height.
  • All iPads in both orientations have regular width and regular height when full screen.

References:

How to get conservation status of a species using WikiData

  1. Use the REST API to get the Wikipedia article for the species using the scientific name. E.g., https://en.wikipedia.org/api/rest_v1/page/summary/Branta_canadensis
  2. Parse the JSON response for the wikibase_item value. This will be a string starting with Q, e.g., Q26733.
  3. Request the WikData using the REST API for the specific item.
  4. Parse the JSON response looking for the "statement" of interest. The IUCN conservation status property code is P141.
  5. Look at the contents of that statement. It will be an array but there should only be 1 element. Look at the value dictionary for the content key. This will be another Q string followed by a number. E.g., Q211005. You can look up details for any property using the WikiData, .e.g, https://www.wikidata.org/wiki/Q211005

SwiftUI List bugs when moving rows into different sections.

I can't seem to figure out what SwiftUI needs to make animating rows into different sections work.

I'm trying to build a simple view that allows "pinning" items in a list.

  • When an item is pinned it appears in a different section from the other items at the top of the list.
  • Pinned items are not visible in the default items section
  • Pinning an item should animate the row moving into the pinned section.
  • The pinned section should not be visible until at least one item is pinned (this causes issues with the swipe to delete)

I've built a simple app that attempts to implement this but it fails in various ways.

  1. Using .onTapGesture to toggle pin state animates the list well in all cases. Unfortunately this isn't the UX I want to use.
  2. Using .swipeActions causes glitches when the item is the first to be pinned. Once the pinned section is in the view tree pinning other rows works well. Unpinning works well until it will hide the section again.
  3. Using .contextMenu works when it is the first item to be pinned but glitches when pinning additional items (oppose of .swipeActions WTF?). This can be hacked by putting an artificial delay of around 500-700ms before toggling the state.

๐ŸŽ‰ Hello, World!

Problem: Current method of using notes is unstructured and hard to follow and would be difficult to share.

Solution: Use GitHub Issues to organize notes.

The idea was inspired by onmyway133 aka Khoa.

Enable other apps to share data with my app

Apps need to provide a Share Extension if they want to appear on the Activity Sheet when a user shares content from that app.

E.g., when sharing a location from the Maps app you will see the Lyft app available. This lets you share a location in Maps as a destination in Lyft.

It is a 2 step process.

  1. You need to create a Share Extension to indicate that you can accept the shared content.
  2. You need to setup Universal Links so that the Share Extension can open the main app with the shared content.

References:

AudioServicesPlayAlertSound(kSystemSoundID_Vibrate) doesn't work in silent mode

If you want a strong vibration the original vibrate sound effect works well using

AudioServicesPlayAlertSound(kSystemSoundID_Vibrate)

But it won't play if the device is silent mode.

The new AVAudioPlayer APIs allow you to specify the app category so you can explicitly state that you are app is using audio in a playback category to play audio while the device is in silent mode. This is great except that there is no option to use the old kSystemSoundID_Vibrate effect.

Bug with sheet and Menu on same View

Environment: Xcode 14.2 Swift iOS 16.2 iPhone 12 mini. Must run on device, simulator works as expected.

Steps to reproduce

  1. Have a button that when tapped shows a sheet.
  2. Have a menu that when opened doesn't hide the button that shows the sheet
  3. Tap to open the menu
  4. Tap to open the sheet

Expected

The menu to close and the sheet to open.

Actual

The menu closes as expected but the sheet does not open even though the state is changed by the button. If the menu is not open this does not happen.


import SwiftUI

@main
struct SheetMenuBugApp: App {
    @State private var showSheet = false

    var body: some Scene {
        WindowGroup {
            VStack {
                Button("Open Sheet") { showSheet = true }
                Text("showSheet: \(showSheet ? "T" : "F")")
                Spacer()
                Text("Open Sheet doesn't work if you show the menu and tap Open Sheet.")
                Spacer()
                Menu(
                    content: { Text("Empty Menu") },
                    label: { Text("Show Menu") }
                )
            }
            .padding()
            .sheet(isPresented: $showSheet) { Text("Sheet Contents") }
        }
    }
}

SheetMenuBug.zip


RPReplay_Final1679947295.MP4

Images on circles of a consistent size

I'm trying to make a ListView have an attractive top level menu of different folders represented by an icon and text.

Screenshot 2023-02-27 at 2 32 22 PM

Because each image is slightly different sizes, the eye is wide and the trash icon is tall this requires drawing all the images on each row and hiding all but the visible one in a ZStack to get the layout correct.

enum LayersViewKind: CaseIterable {
    case all
    case visible
    case recents
    case deleted

    var menuLabelTitle: String {
        switch self {
        case .all: return "All Layers"
        case .visible: return "Visible"
        case .recents: return "Recents"
        case .deleted: return "Recently Deleted"
        }
    }

    var color: Color {
        switch self {
        case .all: return .blue
        case .visible: return .green
        case .recents: return .orange
        case .deleted: return .gray
        }
    }

    var imageName: String {
        switch self {
        case .all: return "square.fill.on.square.fill"
        case .visible: return "eye.fill"
        case .recents: return "clock.fill"
        case .deleted: return "trash.fill"
        }
    }
}

struct HomeMenuLabel: View {
    let kind: LayersViewKind

    var body: some View {
        HStack {
            // Stack all images for each row to insure icon and text is centered.  If you know of a better way
            ZStack {
                ForEach(LayersViewKind.allCases, id: \.self) { currentKind in
                    Image(systemName: currentKind.imageName)
                        .imageScale(.medium)
                        .padding(MagicValue.mediumPadAmount)
                        .foregroundColor(.white)
                        .background(kind.color)
                        .clipShape(Circle())
                        .opacity(kind.imageName == currentKind.imageName ? 1 : 0)
                        .accessibilityHidden(kind.imageName != currentKind.imageName)
                }
            }
            Text(kind.menuLabelTitle)
        }
    }
}

There has to be a better way to do this but I can't think of what it would be. Maybe GeometryReader but then would that be any less complicated?

Capture development emails with Mailpit

My preferred way to capture and view emails being sent by a Rails app in development is to use Mailpit. It runs a local SMTP server at port 1025 and a barebones email client at port 8025. I used to use MailHog but development has stalled. Mailpit has the same ease of use while supporting newer features and looking nicer.

The mailpit utility can be installed with brew:

brew install mailpit
brew services start mailpit

The development smtp settings are configured in config/environments/development.rb:

config.action_mailer.delivery_method = :smtp
config.action_mailer.smtp_settings = { address: "localhost", port: 1025 }

All outgoing email from the development server will be captured and viewable in
both html and text form at localhost:8025.

Source: https://github.com/jbranchaud/til/blob/master/rails/capture-development-emails-with-mailhog.md

MapAnnotation with SwiftUI gives "Publishing changes from within view updates is not allowed" warning

I've got a minimal Swift UI example that reproduces an odd warning that is happening by many people that I can't explain.

Using MapMarker removes the problem but when using MapAnnotation as soon as the map is panned the app produces reams of Publishing changes from within view updates is not allowed, this will cause undefined behavior. warnings in the output log.

Apple Developer Forums discussion doesn't have any solutions.
https://developer.apple.com/forums/thread/718697

import MapKit
import SwiftUI

@main
struct TestApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

struct Observation: Identifiable {
    let id: String
    let coordinate: CLLocationCoordinate2D
}

struct ContentView: View {
    @State var region = MKCoordinateRegion(
        center: CLLocationCoordinate2D(latitude: 49.3, longitude: -123.2),
        latitudinalMeters: 20_000,
        longitudinalMeters: 20_000
    )

    let observations: [Observation] = [
        .init(id: "1", coordinate: CLLocationCoordinate2D(latitude: 49.3, longitude: -123.2))
    ]

    var body: some View {
        Map(
            coordinateRegion: $region,
            annotationItems: observations,
            annotationContent: { observation in
                // MapMarker(coordinate: observation.coordinate)
                MapAnnotation(coordinate: observation.coordinate) {
                    // Circle().fill(.red).frame(width: 10, height: 10)
                    EmptyView()
                }
            }
        )
    }
}
Screenshot 2023-06-18 at 3 54 59 PM

Restoring NavigationStack State from SceneStorage

I was running into some annoying bugs with my SwiftUI app around restoring NavigationStack path from SceneStorage. I tried following Paul's [1] and Majid's [2] articles but was still running into problems.

To solve the problem I created a test app that loosely follows the app I was building. It includes a couple of questionable navigation ideas around changing the active tab from a tab view and needing to save two shallow navigation paths.

During testing with both ideas I was noticing that when the views were getting stored the navigation path would be restored from SceneStorage and then immediately updated with an empty array. This was puzzling as I couldn't understand why that array was getting assigned []. Using the debugger provided no insight.

I eventually thought it might be related to threading and guarded the initial assignment of the restored thread in a @MainActor Task and that solved the issue.

The example app includes a pattern where SceneStorage is encapsulated at the top of the app view using a MainScene. All the logic around scene state can saved on that view but is made available via a SceneState object that is injected into the environment. Using this pattern views deep in the app are able to manipulate the view state of the app without having to pass bindings through all the views.

I'm not convinced this is the best way to accomplish this but I haven't found anything better. If anybody reads this please leave a comment if you've encountered the problem and/or have any suggestions.

import SwiftUI

final class NavigationStore<Route: Hashable> where Route: Codable {
    static func save(path: [Route]) -> Data? {
        do {
            return try JSONEncoder().encode(path)
        } catch {
            print("NavigationStore: save error: \(error)")
            return nil
        }
    }

    static func restore(data: Data?) -> [Route] {
        guard let data else {
            return []
        }
        do {
            let representation = try JSONDecoder().decode([Route].self, from: data)
            return [Route](representation)
        } catch {
            print("NavigationStore: restore error: \(error)")
            return []
        }
    }
}

struct Layer: Identifiable {
    let id: UUID
    let name: String
}

struct Item: Identifiable {
    let id: UUID
    let name: String
}

class AppData: NSObject, ObservableObject {
    var layers = [
        Layer(id: UUID(uuidString: "1b927f0c-6d91-49ce-807a-c05dd1fbf2be")!, name: "One Bravo Niner"),
        Layer(id: UUID(uuidString: "27801b7f-f6cf-4add-98c9-71f581555996")!, name: "Two Seven Eight"),
        Layer(id: UUID(uuidString: "3fec91ad-1889-40da-a630-cccf30a3db2a")!, name: "Three Foxtrot Echo"),
    ]

    var items = [
        Item(id: UUID(uuidString: "ab927f0c-6d91-49ce-807a-c05dd1fbf2be")!, name: "Alpha Beta"),
        Item(id: UUID(uuidString: "b7801b7f-f6cf-4add-98c9-71f581555996")!, name: "Bravo Seven"),
    ]

    func findItem(itemID: UUID) -> Item? {
        items.first { $0.id == itemID }
    }

    func findLayer(layerID: UUID) -> Layer? {
        layers.first { $0.id == layerID }
    }
}

@main
struct AppMain: App {
    @StateObject private var appData: AppData

    var body: some Scene {
        WindowGroup {
            MainScene()
                .environmentObject(appData)
        }
    }

    init() {
        let appData = AppData()
        _appData = StateObject(wrappedValue: appData)
    }
}

enum MainTab: String {
    case location
    case compass
    case map
    case items
    case settings
}

enum LayersViewKind: String, CaseIterable, Codable {
    case all
    case visible
    case recents
    case deleted

    var menuLabelTitle: String {
        switch self {
        case .all: return "All Maps"
        case .visible: return "Visible"
        case .recents: return "Recents"
        case .deleted: return "Recently Deleted"
        }
    }

    var color: Color {
        switch self {
        case .all: return .blue
        case .visible: return .green
        case .recents: return .orange
        case .deleted: return .gray
        }
    }

    var imageName: String {
        switch self {
        case .all: return "square.fill.on.square.fill"
        case .visible: return "eye.fill"
        case .recents: return "clock.fill"
        case .deleted: return "trash.fill"
        }
    }
}

class SceneState: ObservableObject {
    @Published var activeTab = MainTab.map
    @Published var mapTabPath: [MapTabRoute] = []
    @Published var itemsTabPath: [ItemsTabRoute] = []

    var activeLayerID: UUID? {
        switch mapTabPath.last {
        case let .map(layerID):
            return layerID
        default:
            return nil
        }
    }
}

struct MainScene: View {
    @SceneStorage("MainScene.activeTab") var activeTab = MainTab.map
    @SceneStorage("MainScene.mapTabPathData") var mapTabPathData: Data?
    @SceneStorage("MainScene.itemsTabPathData") var itemsTabPathData: Data?

    @StateObject private var sceneState = SceneState()

    var body: some View {
        MainView()
            .onAppear {
                Task { @MainActor in
                    sceneState.activeTab = activeTab
                    sceneState.mapTabPath = NavigationStore<MapTabRoute>.restore(data: mapTabPathData)
                    sceneState.itemsTabPath = NavigationStore<ItemsTabRoute>.restore(data: itemsTabPathData)
                }
            }
            .onChange(of: sceneState.activeTab) { _ in
                activeTab = sceneState.activeTab
            }
            .onChange(of: sceneState.mapTabPath) { _ in
                mapTabPathData = NavigationStore<MapTabRoute>.save(path: sceneState.mapTabPath)
            }
            .onChange(of: sceneState.itemsTabPath) { _ in
                itemsTabPathData = NavigationStore<ItemsTabRoute>.save(path: sceneState.itemsTabPath)
            }
            .environmentObject(sceneState)
    }
}

struct MainView: View {
    @EnvironmentObject var sceneState: SceneState

    var body: some View {
        TabView(selection: $sceneState.activeTab) {
            Text("Location Tab")
                .tag(MainTab.location)
                .tabItem { Label("Location", systemImage: "location") }

            CompassTab()
                .tag(MainTab.compass)
                .tabItem { Label("Compass", systemImage: "arrow.up.circle") }

            MapTab()
                .tag(MainTab.map)
                .tabItem { Label("Map", systemImage: "map") }

            if let activeLayerID = sceneState.activeLayerID {
                ItemsTab(layerID: activeLayerID)
                    .tag(MainTab.items)
                    .tabItem { Label(activeLayerID.uuidString.prefix(3), systemImage: "folder") }
            }

            Text("Settings Tab")
                .tag(MainTab.settings)
                .tabItem { Label("Settings", systemImage: "gear") }
        }
    }
}

struct CompassTab: View {
    @EnvironmentObject var sceneState: SceneState

    var body: some View {
        VStack {
            Text("Compass Tab")
            Button("Show Map") {
                sceneState.activeTab = .map
            }
            .buttonStyle(.borderedProminent)
        }
    }
}

struct TopLevelFoldersView: View {
    var body: some View {
        List {
            ForEach(LayersViewKind.allCases, id: \.self) { kind in
                NavigationLink(value: MapTabRoute.layers(kind)) {
                    Label(kind.menuLabelTitle, systemImage: kind.imageName)
                        .foregroundColor(kind.color)
                }
            }
        }
        .navigationTitle("Folders")
    }
}

struct LayersView: View {
    let kind: LayersViewKind

    @EnvironmentObject var appData: AppData

    var body: some View {
        List {
            ForEach(appData.layers) { layer in
                NavigationLink(value: MapTabRoute.map(layer.id)) {
                    Text(layer.name)
                }
            }
        }
        .navigationTitle(kind.menuLabelTitle)
    }
}

struct MapView: View {
    let layerID: UUID

    @EnvironmentObject var appData: AppData

    var body: some View {
        if let layer = appData.findLayer(layerID: layerID) {
            MapContentView(layer: layer)
        } else {
            Text("Unknown Layer: id:\(layerID)")
        }
    }
}

struct MapContentView: View {
    let layer: Layer

    var body: some View {
        VStack {
            Text(layer.name).font(.title)
            Text(layer.id.uuidString.prefix(8)).monospaced()
        }
        .navigationTitle(layer.name)
        .navigationBarTitleDisplayMode(.inline)
    }
}

enum MapTabRoute: Hashable, Codable {
    case layers(LayersViewKind)
    case map(UUID)
}

enum ItemsTabRoute: Hashable, Codable {
    case item(UUID)
}

struct MapTab: View {
    @EnvironmentObject var sceneState: SceneState

    @State private var readyToShow = false

    var body: some View {
        NavigationStack(path: $sceneState.mapTabPath) {
            Group {
                TopLevelFoldersView()
            }
            .navigationTitle("Folders")
            .navigationDestination(for: MapTabRoute.self) { route in
                switch route {
                case let .layers(kind):
                    LayersView(kind: kind)
                case let .map(layerID):
                    MapView(layerID: layerID)
                }
            }
        }
    }
}

struct ItemView: View {
    let itemID: UUID
    @EnvironmentObject var appData: AppData

    var body: some View {
        if let item = appData.findItem(itemID: itemID) {
            ItemContentView(item: item)
        } else {
            Text("Unknown Item: id:\(itemID)")
        }
    }
}

struct ItemContentView: View {
    @EnvironmentObject var sceneState: SceneState

    let item: Item

    var body: some View {
        VStack {
            Text(item.name).font(.title)
            Text(item.id.uuidString.prefix(8)).monospaced()
            Button("Show on Map") {
                sceneState.activeTab = .map
            }
            .buttonStyle(.borderedProminent)
        }
        .navigationTitle(item.name)
        .navigationBarTitleDisplayMode(.inline)
    }
}

struct ItemsView: View {
    let layerID: UUID

    @EnvironmentObject var appData: AppData

    var body: some View {
        if let layer = appData.findLayer(layerID: layerID) {
            ItemsContentView(layer: layer, items: appData.items)
        } else {
            Text("Unknown layerID: \(layerID)")
        }
    }
}

struct ItemsContentView: View {
    let layer: Layer
    let items: [Item]

    var body: some View {
        List {
            ForEach(items) { item in
                NavigationLink(value: ItemsTabRoute.item(item.id)) {
                    Text(item.name)
                }
            }
        }
        .navigationTitle(layer.name)
        .navigationBarTitleDisplayMode(.inline)
    }
}

struct ItemsTab: View {
    let layerID: UUID

    @EnvironmentObject var sceneState: SceneState

    var body: some View {
        NavigationStack(path: $sceneState.itemsTabPath) {
            ItemsView(
                layerID: layerID
            )
            .navigationDestination(for: ItemsTabRoute.self) { route in
                switch route {
                case let .item(itemID):
                    ItemView(itemID: itemID)
                }
            }
        }
    }
}

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.