I am using Xcode 14.3.1 to develop and I have this unit test:
@testable import bimb_authenticate_ios
import XCTest
import RxSwift
import RxBlocking
final class BindingApiClientTests: XCTestCase {
private var disposeBag = DisposeBag()
override func setUpWithError() throws {
// Put setup code here. This method is called before the invocation of each test method in the class.
disposeBag = DisposeBag()
}
override func tearDownWithError() throws {
// Put teardown code here. This method is called after the invocation of each test method in the class.
}
func testPostInternetBankingCredentials_WhenSuccess() throws {
XCTAssertEqual(1, 1)
// let expectedParams = getExpectedIBCredsParams()
// let expectedResponseModel = getExpectedIBCredentialsResponseModel()
// let expectedData = try expectedResponseModel.jsonData() // Encodable to Data from JSON string
// let expectedOutput = Single.just(expectedData)
// let apiDataSource = MockDataSource(postOutputEvent: expectedOutput)
// let sut = BindingApiClient(apiDataSource: apiDataSource)
//
// let expectation = expectation(description: "Expect Success!")
// var actualResponseModel: IBCredentialsResponseModel? = try sut
// .postInternetBankingCredentials(with: expectedParams)
// .debug()
// .toBlocking()
// .first()
//
//// sut.postInternetBankingCredentials(with: expectedParams)
//// .subscribe(onSuccess: {
//// actualResponseModel = $0
//// expectation.fulfill()
//// })
//// .disposed(by: disposeBag)
////
//// wait(for: [expectation], timeout: 0.1)
// XCTAssertEqual(expectedResponseModel,
// actualResponseModel)
// XCTAssertEqual(expectedParams.toJSONString(),
// apiDataSource.postParamsInput?.toJSONString())
// XCTAssertTrue(apiDataSource.isPostCalled)
}
}`
But it never run even though it only had one simple line, XCTAssertEqual(1, 1). It just started and will be processing ad infinitum. There is no log whatsoever except for the logs for successful compilations of pods and some swiftlint warnings. Can anybody help me?
Thanks.
Post
Replies
Boosts
Views
Activity
Hi,
I have a WKWebview to open the client's website. In the website, there is a "Cancel" button that will open a popup for confirmation. In Safari or other web clients, it works and it will open a popup. But in my iOS app, it didn't. When I ask the web FE developer, he said that he is using jQuery modal dialog for the popup. I tried using this, but doesn't work:
class WebsiteViewController: UIViewController {
var urlRequest: URLRequest?
@IBOutlet weak private var ibWebView: WKWebView!
override func viewDidLoad() {
super.viewDidLoad()
ibWebView.configuration.websiteDataStore = .nonPersistent()
ibWebView.configuration.preferences.javaScriptEnabled = true
ibWebView.configuration.preferences.javaScriptCanOpenWindowsAutomatically = true
ibWebView.navigationDelegate = self
ibWebView.uiDelegate = self
loadInternetBanking()
}
private func loadInternetBanking() {
guard let urlRequest = urlRequest else {
return
}
ibWebView.load(urlRequest)
}
}
extension InternetBankingViewController: WKUIDelegate {
func webView(_ webView: WKWebView, createWebViewWith configuration: WKWebViewConfiguration, for navigationAction: WKNavigationAction, windowFeatures: WKWindowFeatures) -> WKWebView? {
print(":::::::: createWebViewWith!!!!") //- this doesn't get called
let popupWebView = WKWebView(frame: view.bounds, configuration: configuration)
popupWebView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
return popupWebView
}
func webViewDidClose(_ webView: WKWebView) {
}
func webView(_ webView: WKWebView, runJavaScriptAlertPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo) async {
print(":::::::: runJavaScriptAlertPanelWithMessage!!!!") //- this doesn't get called
}
func webView(_ webView: WKWebView, runJavaScriptConfirmPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo) async -> Bool {
print(":::::::: runJavaScriptConfirmPanelWithMessage!!!!") //- this doesn't get called
return true
}
}
The createWebViewWith, runJavaScriptAlertPanelWithMessage, and runJavaScriptConfirmPanelWithMessage callbacks are not being called. Can you help me with this?
Thank you.
I am trying to wrap os_log for logging in my iOS app like so:
import Foundation
import OSLog
enum LogCategory: String, CaseIterable {
case viewCycle
case tracking
case api
}
struct Log {
private static let logs = {
return LogCategory.allCases
.reduce(into: [LogCategory: OSLog]()) { dict, category in
dict[category] = OSLog(subsystem: Bundle.main.bundleIdentifier ?? "BIMB", category: category.rawValue)
}
}()
static func debug(category: LogCategory, message: StaticString, _ args: CVarArg...) {
logImpl(category: category, message: message, type: .debug, args)
}
static func info(category: LogCategory, message: StaticString, _ args: CVarArg...) {
logImpl(category: category, message: message, type: .info, args)
}
static func notice(category: LogCategory, message: StaticString, _ args: CVarArg...) {
logImpl(category: category, message: message, type: .default, args)
}
static func warning(category: LogCategory, message: StaticString, _ args: CVarArg...) {
logImpl(category: category, message: message, type: .default, args)
}
static func error(category: LogCategory, message: StaticString, _ args: CVarArg...) {
logImpl(category: category, message: message, type: .error, args)
}
static func critical(category: LogCategory, message: StaticString, _ args: CVarArg...) {
logImpl(category: category, message: message, type: .fault, args)
}
private static func logImpl(category: LogCategory, message: StaticString, type: OSLogType, _ args: CVarArg...) {
guard let log = logs[category] else {
return
}
os_log(message, log: log, type: type, args)
}
}
The problem is if I did this:
Log.debug(category: .tracking,
message: "Device ID: %s.",
UIDevice.current.identifierForVendor?.uuidString ?? "unknown")
it always crashed with this error:
2023-12-13 12:33:35.173798+0700 bimb-authenticate-ios[62740:928633]
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[Swift.__SwiftDeferredNSArray
UTF8String]: unrecognized selector sent to instance 0x600000dcbbc0'
But if I just do it with os_log like this:
os_log("Device ID: %s.",
log: OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "tracking"),
type: .debug,
UIDevice.current.identifierForVendor?.uuidString ?? "unknown")
it worked fine. Also if I changed %@ in my wrapper instead, it didn't crash, but the idfv is shown inside a pair of brackets like this:
Device ID: (
"C0F906C8-CD73-44F6-86A1-A587248680D3"
).`
But with os_log it is shown normally like this: Device ID: C0F906C8-CD73-44F6-86A1-A587248680D3.
Can you tell me what's wrong here? And how do I fix this?
Thanks.
NOTE: This is using os_log since the minimum version is iOS 11. I don't know why people advising me with using Logger instead.
I have this ToastView that will animate by expanding the height from the top of the screen. All the components are created programmatically. This is the code:
final class ToastView: UIView {
private static let instance = createContainer()
private let labelHeightOffset: CGFloat = 32
private let messageLabel: UILabel = {
let label = UILabel()
label.textAlignment = .center
label.numberOfLines = 0
return label
}()
private var heightConstraint: NSLayoutConstraint!
static func show(message: String) {
let keyWindow = UIWindow.getKeyWindow() //extension to get the kley window
show(on: keyWindow, message: message)
}
private static func show(on parentView: UIView? = nil, message: String) {
guard let parentView = parentView ?? UIViewController.getTopViewController()?.view,
instance.superview == nil,
let toast = toast else {
return
}
parentView.addSubview(instance, method: .fill)
toast.messageLabel.text = message
toast.messageLabel.textColor = .red
toast.messageLabel.textAlignment = .center
toast.messageLabel.sizeToFit()
toast.show(for: 3)
}
private func show(for duration: TimeInterval = 0) {
isHidden = false
layoutIfNeeded()
UIView.animate(withDuration: 0.4, animations: {
let labelLineHeight = self.messageLabel.getRect(maxLine: 3).size.height //getRect is an extension func to get the rect based on the content of the label
let lineSpaces = CGFloat((self.messageLabel.getLineNumber() - 1) * 2) //getLineNumber is an extension func to get the total number of lines based on the content
//Get the height by the content of the label
self.heightConstraint.constant = self.labelLineHeight +
self.labelHeightOffset +
lineSpaces
self.setNeedsUpdateConstraints()
self.superview?.layoutIfNeeded()
}, completion: { _ in
self.hide(delay: duration)
})
}
private func hide(delay: TimeInterval = 0) {
UIView.animate(withDuration: 0.4, delay: delay, animations: {
self.heightConstraint.constant = 0
self.setNeedsUpdateConstraints()
self.superview?.layoutIfNeeded()
}, completion: { _ in
self.isHidden = true
self.messageLabel.text = nil
ToastView.instance.removeFromSuperview()
})
}
}
private extension ToastView {
static var toast: ToastView? {
return instance.subviews[0] as? ToastView
}
static func createContainer() -> UIView {
let container = UIView(frame: .zero)
container.backgroundColor = .clear
let toast = ToastView(frame: .zero)
toast.backgroundColor = .white
toast.addCorner(radius: 8)
container.addSubview(toast)
toast.layoutMessageLabel()
toast.layoutToast()
return container
}
func layoutMessageLabel() {
addSubview(messageLabel)
messageLabel.center = center
messageLabel.translatesAutoresizingMaskIntoConstraints = false
let constraints = [
centerYAnchor.constraint(equalTo: messageLabel.centerYAnchor),
leftAnchor.constraint(equalTo: messageLabel.leftAnchor, constant: 16),
rightAnchor.constraint(equalTo: messageLabel.rightAnchor, constant: 16)
]
NSLayoutConstraint.activate(constraints)
messageLabel.setNeedsUpdateConstraints()
layoutIfNeeded()
}
func layoutToast() {
translatesAutoresizingMaskIntoConstraints = false
let topConstants: CGFloat = UIWindow.getKeyWindow()?.safeAreaInsets.top ?? 0 + 16
let topConstraint = topAnchor.constraint(equalTo: superview!.topAnchor,
constant: topConstants)
heightConstraint = heightAnchor.constraint(equalToConstant: 0)
let constraints = [
topConstraint,
heightConstraint!,
leftAnchor.constraint(equalTo: superview!.leftAnchor, constant: 16),
superview!.rightAnchor.constraint(equalTo: rightAnchor, constant: 16)
]
NSLayoutConstraint.activate(constraints)
setNeedsUpdateConstraints()
superview!.layoutIfNeeded()
}
}
The code is working fine EXCEPT for the first time it appears. It always animates from the bottom of the screen and rising above ot the top. But if you'll have a look at the code, I only animte the heightConstraint's constant value. Why is this happening? Can you help me fix this? thanks.
Hi, I've got this html text:
"<style>* {font-size: 12pt !important;color: #000000 !important;font-family: Montserrat-Regular !important;}</style>Perform the following steps:<br><u>Option 1:</u><br><p>1) Upon receiving a push notification alert, tap on the push notification to launch BIMB Authenticator</p><p>2) Verify the transaction details and choose \"Approve\"</p><p>3) Complete</p><br><u>Option 2:</u><br><ol><p>1) If you didn’t receive push notification, you may launch BIMB Authenticator</p><p>2) Verify the transaction details and choose \"Approve\"</p><p>3) Complete</p>"
And I'm trying to show this HTML text properly in a UILabel. This is my codes:
String extension to map to NSAttributedString:
extension String {
func attributedStringFromHTML() -> NSAttributedString? {
guard let data = "\(self)"
.data(using: .utf8, allowLossyConversion: false) else {
Log.error(category: .transaction, message: "Unable to decode data from html string: %@", self)
return nil
}
let options: [NSAttributedString.DocumentReadingOptionKey: Any] = [
.documentType: NSAttributedString.DocumentType.html,
.characterEncoding: String.Encoding.utf8.rawValue
]
if let attributedString = try? NSAttributedString(data: data, options: options, documentAttributes: nil) {
return attributedString
} else {
Log.error(category: .transaction,
message: "Unable to create attributed string from html string: %@",
self)
return nil
}
}
}
And this is the result:
Can you tell me how to fix this? Thanks.
Hi,
I'm trying to create some targets of my project with the exact source code, Info.plist, build settings, build phases, extensions, copied files, tests, etc like this:
I've also created some schemes so we can toggle the targets by picking the scheme like this:
I've set all of these targets with the same value instead of getting it from their $TARGET, $PRODUCT_BUNDLE_ID, etc:
Info plist file = bimb-authenticate-ios/Info.plist
Project Module Name = bimb_authenticate_ios
Bundle Display name = BIMBAuthenticator
I've added the targets in the Podfile like so:
Podfile
But when I tried building it, I;ve got these "Multiple commands" errors:
Can you tell me what I did wrong here? Thank you.
Hi,
Just a quick one. I am working with a client who doesn't share his team's credentials like certificates, mobile provisioning, etc. He even refused to add me as one of the developer in his Apple Dev account. So, I am creating a new scheme for me that will use my own personal team and app ID to build it. While the main app's original scheme is basically unusable since I don't have the credentials to build it. The client still needs it for his CI/CD though.
Now, the app has a Notification Service extension that will share UserDefaults via App Group. When I try to create a container with the same group ID as his, it always failed. It seems like we can't use it because it has already been taken by the clent. How do I fix this so I can just change the scheme to switch between the client's and mine?
Thanks.
Hi,
I'm experiencing a bug that only occur in iPhone X/XS that run iOS 16.2. I couldn't tap the push notification when the app is already active. I need to trigger a feature when the push notification is tapped. When the app is on the background, it worked. But when the app is already active / on the foreground, it didn't. I tried this on an iPhone 8 Plus running iOS 16.2.1 and iPhone 14 and iPhone XR running iOS 17.1.2, but this bug didn't occur (ie. the tap was working and I can call didReceiveResponse callback). Why does this happen? Can anybody help me?
Thanks.
I am trying to do the tests form the terminal and xcpretty the output. Here is the command:
xcodebuild -workspace bimb-authenticate-ios.xcworkspace -scheme bimb-authenticate-ios-afx -configuration Debug -destination 'platform=iOS Simulator,name=iPhone 14 Pro,OS=16.4' -enableCodeCoverage YES -derivedDataPath Build/ clean build test | xcpretty -c "--report" "html" "--output" "../Project_report.html"
However, the tests are never run. The simulator is not even opened. Here's the last items of the xcbuild logs:
... *compiling, linting and linking logs*
2024-03-27 21:42:37.151 xcodebuild[84540:2237180] [MT] IDETestOperationsObserverDebug: 264.553 elapsed -- Testing started completed.
2024-03-27 21:42:37.152 xcodebuild[84540:2237180] [MT] IDETestOperationsObserverDebug: 0.001 sec, +0.001 sec -- start
2024-03-27 21:42:37.152 xcodebuild[84540:2237180] [MT] IDETestOperationsObserverDebug: 264.554 sec, +264.553 sec -- end
▸ Test Succeeded
Can anybody help me? Thanks.
Hi,
I'm trying to create a new target duplicated from the main target (cdx_ios) called cdx-ios-dev02. I also made a new scheme called cdx-ios-dev02 It can be built just fine however when I run it, it crashed and it throws this exception:
*** Terminating app due to uncaught exception 'NSInvalidUnarchiveOperationException', reason: '*** -[NSKeyedUnarchiver decodeObjectForKey:]: cannot decode object of class (cdx_ios.AuthObject) for key (root) because no class named "cdx_ios.AuthObject" was found; the class needs to be defined in source code or linked in from a library (ensure the class is part of the correct target). If the class was renamed, use setClassName:forClass: to add a class translation mapping to NSKeyedUnarchiver'
This is the class:
class AuthObject: NSObject, NSCoding {
var accessT1: String = ""
var t1Type: String = "bearer"
var refreshT1: String = ""
var expiresIn: Int = 0
var scope: String = ""
var jti: String = ""
init(accessT1: String = "",
t1Type: String = "bearer",
refreshT1: String = "",
expiresIn: Int = 0,
scope: String = "",
jti: String = "") {
self.accessT1 = accessT1
self.t1Type = t1Type
self.refreshT1 = refreshT1
self.expiresIn = expiresIn
self.scope = scope
self.jti = jti
}
convenience init(dic: [String: Any]) {
self.init()
mapping(dic)
}
required convenience init(coder aDecoder: NSCoder) {
let t1 = aDecoder.decodeObject(forKey: "accessT1") as? String ?? ""
let t1Type = aDecoder.decodeObject(forKey: "t1Type") as? String ?? ""
let refreshT1 = aDecoder.decodeObject(forKey: "refreshT1") as? String ?? ""
let expiresIn = aDecoder.decodeInteger(forKey: "expiresIn")
let scope = aDecoder.decodeObject(forKey: "scope") as? String ?? ""
let jti = aDecoder.decodeObject(forKey: "jti") as? String ?? ""
self.init(
accessT1: t1,
t1Type: t1Type,
refreshT1: refreshT1,
expiresIn: expiresIn,
scope: scope,
jti: jti
)
}
func mapping(_ dic: [String: Any]) {
accessT1 = ParseUtil.dictionaryValue(dic, "access_token", "")
t1Type = ParseUtil.dictionaryValue(dic, "token_type", "bearer")
refreshT1 = ParseUtil.dictionaryValue(dic, "refresh_token", "")
expiresIn = ParseUtil.dictionaryValue(dic, "expires_in", 0)
scope = ParseUtil.dictionaryValue(dic, "scope", "")
jti = ParseUtil.dictionaryValue(dic, "jti", "")
}
func encode(with nsCoder: NSCoder) {
nsCoder.encode(accessT1, forKey: "accessT1")
nsCoder.encode(t1Type, forKey: "t1Type")
nsCoder.encode(refreshT1, forKey: "refreshT1")
nsCoder.encode(expiresIn, forKey: "expiresIn")
nsCoder.encode(scope, forKey: "scope")
nsCoder.encode(jti, forKey: "jti")
}
}
It worked fine on the original target, cdx-ios. Can anybody help me? Thank you.
So I have 2 schemes: prod and staging. And both have their own Debug and Release configuration settings (one of which is the bundle ID, eg. prod-Release: com.mycomp.myApp, prod-Debug: com.myComp.myApp.debug, staging-Release: com.myComp.myApp.staging.debug, staging-Debug: com.myComp.myApp.staging.debug).
Now, there is a framework that is bound to each bundle ID. The framework is not from a So I need 4 instances of the same framework for each bundle ID. I tried separating the frameworks into folders and just link them whenever I need to run the appropriate scheme. Basically like so:
\{PROJECT_ROOT}\Frameworks\MyFramework\prod-Debug\MyFramework.xcframework
\{PROJECT_ROOT}\Frameworks\MyFramework\prod-Release\MyFramework.xcframework
\{PROJECT_ROOT}\Frameworks\MyFramework\staging-Debug\MyFramework.xcframework
\{PROJECT_ROOT}\Frameworks\MyFramework\staging-Release\MyFramework.xcframework
I've added a Run script to create the link before Compile Sources step like so:
cd "${SRCROOT}/zDefend"
if [ "${CONFIGURATION}" = "prod-Debug" ]; then
rm -rf ./MyFramework.xcframework
ln -s ./prod-Debug/MyFramework.xcframework MyFramework.xcframework
elif [ "${CONFIGURATION}" = "prod-Release" ]; then
rm -rf ./MyFramework.xcframework
ln -s ./prod-Release/MyFramework.xcframework/ MyFramework.xcframework
elif [ "${CONFIGURATION}" = "staging-Debug" ]; then
rm -rf ./MyFramework.xcframework
ln -s ./staging-Debug/MyFramework.xcframework MyFramework.xcframework
elif [ "${CONFIGURATION}" = "staging-Release" ]; then
rm -rf ./MyFramework.xcframework
ln -s ./staging-Release/MyFramework.xcframework/ MyFramework.xcframework
fi
I only have 1 target, myApp. But in the Build Settings, I set it so that every configurations will create 4 different binaries with 4 different bundle ID like I've mentioned above. I've added the framework by its link and set it to "Embed and Sign".
The framework itself is not from Cocoapods, Carthage or SwiftPM. We just need to compile it ourselves into 4 and bind them with their own bundle ID in the code. Then we manually added them into the project. It will throw error if the app's bundle ID is not the same with the bundle ID bound to the framework.
This can be compiled just fine. But when in created 2 or more binaries in the simulator, it crashes. So this is possibly a linking issue. How do I solve this?
Thank you.