How to use ForEach with ScrollView in SwiftUI
Issue #517
Use ScrollView -> VStack -> ForEach -> Content
1 | struct SearchScreen: View { |
Issue #517
Use ScrollView -> VStack -> ForEach -> Content
1 | struct SearchScreen: View { |
Issue #516
Suppose we have an array of SearchObject
, and user can enter search query into text
property.
1 | class SearchObject: ObservableObject { |
Although SearchObject
is class, when we use ForEach
, the changes to passed object won’t be reflected in our array and there is no reload trigger, we need to point to object in array directly, like
1 | self.$searchObjects[index].text |
1 | struct SearchScreen: View { |
Issue #515
Use enumerated
and id: \.element.name
1 | struct CountriesView: View { |
Issue #514
1 | ssh-keygen -t rsa -C "onmyway133@gmail.com" -f "id_rsa_github" |
1 | vim ~/.ssh/config |
1 | git config --global user.email "onmyway133@gmail.com" |
Updated at 2020-10-04 06:52:07
Issue #513
A publisher that emits before the object has changed
Use workaround DispatchQueue
to wait another run loop to access newValue
1 | .onReceive(store.objectWillChange, perform: { |
Issue #512
1 | git clone https://github.com/zsh-users/zsh-autosuggestions ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-autosuggestions |
Issue #511
1 | struct CountriesView: View { |
Issue #510
Use Dictionary(grouping:by:)
1 | func groups(countries: [Country]) -> [Group] { |
Issue #508
We need to use frame(minWidth: 0, maxWidth: .infinity, alignment: .leading)
. Note that order is important, and padding
should be first, and background
after frame
to apply color to the entire frame
1 | struct BooksScreen: View { |
Issue #507
View
extends to the bottom, but not to the notch. We need to add .edgesIgnoringSafeArea(.top)
to our TabView
to tell TabView
to extend all the way to the top.
Note that if we use edgesIgnoringSafeArea(.all)
then TabView
‘s bar will be dragged very down and broken.
1 | struct MainScreen: View { |
Issue #506
When a function expects AnyPublisher<[Book], Error>
but in mock, we have Just
1 | func getBooks() -> AnyPublisher<[Book], Error> { |
There will be a mismatch, hence compile error
Cannot convert return expression of type ‘AnyPublisher<[Book], Just
The reason is because Just produces Never
, not Error
. The workaround is to introduce Error
1 | enum AppError: Error { |
1 | func getBooks() -> AnyPublisher<[Book], Error> { |
Updated at 2020-11-07 20:30:01
Issue #505
Make sure all String
are passed into Text
, not Optional<String>
1 | VStack { |
Issue #504
https://twitter.com/NeoNacho/status/1181245484867801088?s=20
There’s no way to have platform specific sources or targets today, so you’ll have to take a different approach. I would recommend wrapping all OS specific files in #if os and just having one target. For tests, you could do something similar, one test target, but conditional tests
Every files are in Sources
folder, so we can use platform and version checks. For example Omnia is a Swift Package Manager that supports iOS, tvOS, watchOS, macOS and Catalyst.
For macOS only code, need to check for AppKit and Catalyst
https://github.com/onmyway133/Omnia/blob/master/Sources/macOS/ClickedCollectionView.swift
1 | #if canImport(AppKit) && !targetEnvironment(macCatalyst) |
For SwiftUI feature, need to check for iOS 13 and macOS 10.15
https://github.com/onmyway133/Omnia/blob/master/Sources/SwiftUI/Utils/ImageLoader.swift
1 | 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *) (iOS |
Issue #502
Mutation
is used to mutate state synchronously. Action
is like intent, either from app or from user action. Action
maps to Mutation
in form of Publisher
to work with async action, similar to redux-observable
AnyReducer
is a type erasure that takes the reduce
function
1 | import Combine |
To use, conform to all the protocols. Also make typelias AppStore
in order to easy specify type in SwiftUI View
1 | import SwiftUI |
Use in SwiftUI
1 | struct RootScreen: View { |
Issue #501
1 | platform :ios, '13.0' |
gRPC-C++-gRPCCertificates-Cpp
The problem was related to the difference between Firebase/Core and FirebaseCore. The first is a subspec of the Firebase pod that depends on FirebaseAnalytics. The second is only the FirebaseCore pod. Only the latter should be used for macOS.
Issue #499
It turns on all the features that are necessary to build your library in such a way that it can be distributed
What does this error actually mean? Well, when the Swift compiler goes to import a module, it looks for a file called the Compiled Module for that library. If it finds one of these files, it reads off the manifest of public APIs that you can call into, and lets you use them. Now, this Compiled Module Format is a binary format that basically contains internal compiler data structures.
And since they’re just internal data structures, they’re subject to change with every version of the Swift Compiler. So what this means is that if one person tries to import a module using one version of Swift, and that module was created by another version of Swift, their compiler can’t understand it, and they won’t be able to use it.
Well, in order to solve this version lock, Xcode 11 introduces a new format for Swift Modules, called Swift Module Interfaces. And just like the Compiled Module Format, they list out all the public APIs of a module, but in a textual form that behaves more like source code. And since they behave like source code, then future versions of the Swift Compiler will be able to import module interfaces created with older versions. And when you enable Build Libraries for Distribution, you’re telling the compiler to generate one of these stable interfaces whenever it builds your framework
Issue #499
It turns on all the features that are necessary to build your library in such a way that it can be distributed
What does this error actually mean? Well, when the Swift compiler goes to import a module, it looks for a file called the Compiled Module for that library. If it finds one of these files, it reads off the manifest of public APIs that you can call into, and lets you use them. Now, this Compiled Module Format is a binary format that basically contains internal compiler data structures.
And since they’re just internal data structures, they’re subject to change with every version of the Swift Compiler. So what this means is that if one person tries to import a module using one version of Swift, and that module was created by another version of Swift, their compiler can’t understand it, and they won’t be able to use it.
Well, in order to solve this version lock, Xcode 11 introduces a new format for Swift Modules, called Swift Module Interfaces. And just like the Compiled Module Format, they list out all the public APIs of a module, but in a textual form that behaves more like source code. And since they behave like source code, then future versions of the Swift Compiler will be able to import module interfaces created with older versions. And when you enable Build Libraries for Distribution, you’re telling the compiler to generate one of these stable interfaces whenever it builds your framework
Issue #497
Synthetic properties generated by Kotlin Android Extensions plugin needs a view
for Fragment/Activity
to be set before hand.
In your case, for Fragment
, you need to use view.btn_K
in onViewCreated
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
super.onCreateView(inflater, container, savedInstanceState)
val view = inflater.inflate(R.layout.fragment_card_selector, container, false)
view.btn_K.setOnClickListener{} // access with `view`
return view
}
Or better, you should only access synthetic properties in onViewCreated
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
super.onCreateView(inflater, container, savedInstanceState)
return inflater.inflate(R.layout.fragment_card_selector, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
btn_K.setOnClickListener{} // access without `view`
}
Please notice that savedInstanceState
parameter should be nullable Bundle?
, and also check Importing synthetic properties
It is convenient to import all widget properties for a specific layout
in one go:
import kotlinx.android.synthetic.main.<layout>.*
Thus if the layout filename is activity_main.xml, we’d import
kotlinx.android.synthetic.main.activity_main.*.
If we want to call the synthetic properties on View, we should also
importkotlinx.android.synthetic.main.activity_main.view.*.
Original answer https://stackoverflow.com/questions/34541650/nullpointerexception-when-trying-to-access-views-in-a-kotlin-fragment/51674381#51674381
Issue #496
Read this Restoring Purchased Products to understand the purposes between the 2.
From iOS 7, every app downloaded from the store has a receipt (for downloading/buying the app) at appStoreReceiptURL
. When users purchases something via In App Purchase, the content at appStoreReceiptURL
is updated with purchases information. Most of the cases, you just need to refresh the receipt (at appStoreReceiptURL
) so that you know which transactions users have made.
Users restore transactions to maintain access to content they’ve already purchased. For example, when they upgrade to a new phone, they don’t lose all of the items they purchased on the old phone. Include some mechanism in your app to let the user restore their purchases, such as a Restore Purchases button. Restoring purchases prompts for the user’s App Store credentials, which interrupts the flow of your app: because of this, don’t automatically restore purchases, especially not every time your app is launched.
In most cases, all your app needs to do is refresh its receipt and deliver the products in its receipt. The refreshed receipt contains a record of the user’s purchases in this app, on this device or any other device. However, some apps need to take an alternate approach for one of the following reasons:
If you use Apple-hosted content, restoring completed transactions gives your app the transaction objects it uses to download the content.
If you need to support versions of iOS earlier than iOS 7, where the app receipt isn’t available, restore completed transactions instead.
Refreshing the receipt asks the App Store for the latest copy of the receipt. Refreshing a receipt does not create any new transactions.
Restoring completed transactions creates a new transaction for every completed transaction the user made, essentially replaying history for your transaction queue observer.
More about receipt, from WWDC 2017, What’s new in StoreKit session https://developer.apple.com/videos/play/wwdc2017/303/
You can also watch WWDC 2017, session Advanced StoreKit for more detail https://developer.apple.com/videos/play/wwdc2017/305/
Original answer https://stackoverflow.com/questions/45615106/when-to-refresh-a-receipt-vs-restore-purchases-in-ios/52162283#52162283
Issue #494
Remove max-width
from source/css/style.styl
1 | .outer |
Change font-size
of code block from source/css/_partial/highlight.styl
1 | $code-block |
Change font-size
of article-header
from source/css/_partial/article.styl
1 | .article-header |
Change font-size
of text from source/css/_partial/article.styl
1 | .article-entry |
Issue #493
Declare in Podfile
1 | pod 'Firebase/Core' |
Use RemoteConfigHandler
to encapsulate logic. We introduce Key
with CaseIterable
and defaultValue
of type NSNumber
to manage default values.
1 | import Firebase |
Issue #492
Suppose we have a base Localizable.strings
1 | "open" = "Open"; |
After sending that file for translations, we get translated versions.
1 | "open" = "Åpen"; |
Searching and copy pasting these to our Localizable.strings is tedious and time consuming. We can write a script to apply that.
Remember that we need to be aware of smart and dump quotes
1 | .replace(/\"/g, '') |
1 | const fs = require('fs') |
Issue #491
Issue #490
Vision Edge is part of MLKit, but for custom images training
https://console.firebase.google.com/u/0/project/avengers-ad2ce/ml/
Model Avengers_dataset_2019114133437 is training and may take several hours. You will receive an email once training is complete.
Issue #489
https://console.cloud.google.com/storage
Issue #488
The dollar is not a prefix, it seems to be a generated code for property wrapper, and each kind of property wrapper can determine which value it return via this dollar sign
State
and ObservedObject
are popular property wrappers in SwiftUI
Read State
A persistent value of a given type, through which a view reads and monitors the value.
If we have a simple State, we can access its 3 forms
1 | @State private var image: UIImage |
and here is what
1 | image // UIImage |
Also, with State, we can access Binding
via projectedValue
1 | _image.projectedValue // Binding<UIImage> |
Read ObservedObject
For a simple ObservableObject
, we can see its 3 forms
1 | class ViewModel: ObservableObject { |
1 | @ObservedObject var viewModel: ViewModel = ViewModel() |
1 | viewModel // ViewModel |
If we view the source code of ObservableObject
, we can see its Wrapper
which uses dynamicMemberLookup
to provide Binding
1 | @propertyWrapper public struct ObservedObject<ObjectType> : DynamicProperty where ObjectType : ObservableObject { |
If we have a struct with @State
in a SwiftUI view, we can access its property as Binding. This derived state mechanism is done via Dynamic keypath member lookup feature of Swift 5.1
1 | struct ViewModel { |
Take a look at the interface of Binding
1 | /// A value and a means to mutate it. |
So if use $viewModel.image
we can access its property as Binding
1 | viewModel.image // UIImage? |
So now we know how to get Binding
from State
and ObservableObject
, and the mysterious dollar sign. These are both convenient but confusing at first, but if we use it more, it will make more sense and hopefully we can learn to do the same for our own property wrappers
Issue #487
In case we have to modify state when another state is known, we can encapsulate all those states in ObservableObject
and use onReceive
to check the state we want to act on.
See code Avengers
If we were to modify state from within body
function call, we will get warnings
Modifying state during view update, this will cause undefined behavior.
This is similar to the warning when we change state inside render
in React
For example, when we get an image, we want to do some logic based on that image and modify result state. Here we use var objectWillChange = ObservableObjectPublisher()
to notify state change, and because onReceive
requires Publisher
, we use let imagePublisher = PassthroughSubject<UIImage, Never>()
Note that we use $
prefix from a variable to form Binding
1 | import SwiftUI |
See ObservableObject
By default an ObservableObject synthesizes an objectWillChange publisher that emits the changed value before any of its @Published properties changes.
1 | class Contact: ObservableObject { |
We should use @Published
1 | class ViewModel: ObservableObject { |
Note that we should not use objectWillChange
as
By default an ObservableObject synthesizes an objectWillChange publisher that emits the changed value before any of its @Published properties changes.
1 | .onReceive(viewModel.$image, perform: { image in |
We need to manually notify using objectWillChange
!! Maybe this is a SwiftUI bug
1 | private func detect(image: UIImage) { |
If we remove the declaration of var objectWillChange = ObservableObjectPublisher()
, then it works automatically
Learn more about the history of objectWillChange
https://twitter.com/luka_bernardi/status/1155944329363349504?lang=no
In Beta 5 ObjectBinding is now defined in Combine as ObservableObject (the property wrapper is now @ObservedObject). There is also a new property wrapper @Published where we automatically synthesize the objectWillChange publisher and call it on willSet.
It’ll objectWillChange.send() in the property willSet it’s defined on.
It just removes the boilerplate that you had to write before but otherwise behaves the same.
If we were to use @State
instead of @ObservedObject
, it still compiles, but after we pick an image, which should change the image
property of our viewModel
, the view is not reloaded.
1 | struct MainView: View { |
Note that we can’t use @Published
inside struct
‘wrappedValue’ is unavailable: @Published is only available on properties of classes
@State
is for internal usage within a view, and should use struct and primitive data structure. SwiftUI keeps @State
property in a separate memory place to preserve it during many reload cycles.
@Observabled
is meant for sharing reference objects across views
To to use @State
we should use struct, and to use onReceive
we should introduce another Publisher
like imagePublisher
1 | struct ViewModel { |
The dollar sign for State
to access nested properties, like $viewModel.image
is called derived Binding, and is achieved via Keypath member lookup feature of Swift 5.1.
Take a look at projectedValue: Binding<Value>
from State
and subscript<Subject>(dynamicMember keyPath
from Binding
1 | 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *) (iOS |
1 | /// A value and a means to mutate it. |
Issue #486
1 | import SwiftUI |
Issue #485
The easiest way to show image picker in iOS is to use UIImagePickerController
, and we can bridge that to SwiftUI via UIViewControllerRepresentable
We conform to UIViewControllerRepresentable
and make a Coordinator, which is the recommended way to manage the bridging with UIViewController
.
There’s some built in environment property we can use, one of those is presentationMode
where we can call dismiss
to dismiss the modal.
My first attempt looks like below
1 | import SwiftUI |
We need to be aware of the types of these property wrappers
Where we declare environment, presentationMode
is of type Binding<PresentationMode>
1 | @Environment(\.presentationMode) private var presentationMode |
Given a Binding declaration, for example @Binding var image: UIImage?
, image
is of type UIImage?
but $image
is Binding<UIImage?>
1 | public func makeCoordinator() -> ImagePicker.Coordinator { |
When we want to assign to variables in init
, we use _image
to use mutable Binding<UIImage?>
because self.$image
gives us immutable Binding<UIImage?>
1 | class Coordinator: NSObject, UINavigationControllerDelegate { |
To show modal, we use sheet
and use a state @State var showImagePicker: Bool = false
to control its presentation
1 | Button(action: { |
If we run the above code, it will crash because of we access environment value presentationMode
in makeCoordinator
and this is outside body
Fatal error: Reading Environment<Binding
> outside View.body
1 | public func makeCoordinator() -> ImagePicker.Coordinator { |
So instead of passing environment presentationMode
, we can pass closure, just like in React where we pass functions to child component.
So ImagePicker
can just accept a closure called onDone
, and the component that uses it can do the dismissal.
1 | Button(action: { |
Unfortunately, although the onDone
gets called, the modal is not dismissed.
Maybe there are betters way, but we can use Binding to replace usage of Environment.
We can do that by accepting Binding
and change the isPresented
state
1 | import SwiftUI |
How to use it
1 | Button(action: { |
So that we can call parent.presentationMode.wrappedValue.dismiss()
Issue #484
Use SwiftMonkey which adds random UITests gestures
Add to UITests target
1 | target 'MyAppUITests' do |
Failed to determine hittability of Button: Unable to fetch parameterized attribute XC_kAXXCParameterizedAttributeConvertHostedViewPositionFromContext, remote interface does not have this capability.
This happens when using SwiftMonkey and somewhere in our code uses isHittable
, so best to avoid that by having isolated monkey test only
1 | import XCTest |
Another workaround is possibly use addDefaultXCTestPublicActions
other than addDefaultUIAutomationActions
Assertion Failure: MonkeyXCTest.swift:33: Failed to get matching snapshots: Timed out while evaluating UI query.
This seems related to SwiftMonkey trying to snapshot. Workaround is to remove
1 | monkey.addXCTestTapAlertAction(interval: 100, application: app) |