Is it possible to use different colors for focused and selected state on uitabbaritems at tvOS? - uitabbar

In our tvOS app we have a customised tabbar. now additionally we want to change the background color of the focused/selected item. When I do by tabBarAppearance.selectionIndicatorTintColor = .purple, it changes focused and selected states to purple(while without that line code we do have different colors or at least different opacities).
no custom color focused:
no custom color selected
custom color focused
custom color selected
Is it possible to use different colors for focused and selected (like I did for the item text, as you can see on the screen shots)?

I fixed that by setting the the standardAppearance of the tab bar each time the focus changes in my UITabBarController. The relevant code looks like this (the initial setup of appearance is only posted for completeness):
// setting up standard appearance for the first time
private func setupTabbarAppearance() {
let tabBarAppearance = UITabBarAppearance()
//...
tabBarAppearance.selectionIndicatorTintColor = .focusedBackgroundColor // focused items
//...
let itemAppearance = UITabBarItemAppearance()
//...
itemAppearance.normal.titleTextAttributes[.foregroundColor] = .normalTextColor // used for focused AND non-focused items,
// when the whole tabbar is focused
//...
itemAppearance.selected.titleTextAttributes[.foregroundColor] = .selectedTextColor // used for the selected item,
// wen tabbar is not focused
// ...
tabBarAppearance.inlineLayoutAppearance = itemAppearance
tabBar.standardAppearance = tabBarAppearance
tabBar.setNeedsLayout()
}
private func setTabBarIndicatorColor(tabBarFocused: Bool) {
let currentAppearance = tabBar.standardAppearance
// here is where the color is set
currentAppearance.selectionIndicatorTintColor = tabBarFocused ? .focusedBackgroundColor : .selectedBackgroundColor
tabBar.standardAppearance = currentAppearance
}
// change appearance each time, when focus changes in tabbar controller
override func didUpdateFocus(in context: UIFocusUpdateContext, with coordinator: UIFocusAnimationCoordinator) {
if isTabbarInHierarchy(view: context.nextFocusedView) {
setTabBarIndicatorColor(tabBarFocused: true)
} else {
setTabBarIndicatorColor(tabBarFocused: false)
}
super.didUpdateFocus(in: context, with: coordinator)
}
private func isTabbarInHierarchy(view: UIView?) -> Bool {
guard let view = view else {return false}
if view == tabBar {
return true
}
return isTabbarInHierarchy(view: view.superview)
}

Related

where to place a function in SwiftUI

I am trying to write a function that puts a rectangle on the screen in a pre-existing HStack. This is the code without the function (you can see that there is some code repetition used put a few rectangles in the HStack):
struct ContentView: View {
#State var backgroundHeight = 60.0
#State var backgroundWidth = 60.0
#State var backgroundCorners = 10.0
#State var highlightHeight = 8.0
#State var highlightWidth = 8.0
#State var highlightCorners = 3.0
var body: some View {
Color.blue
.frame(width:backgroundWidth, height:backgroundHeight)
.cornerRadius(backgroundCorners)
.overlay(alignment:.center){
HStack(spacing: 2){
Rectangle()
.foregroundColor(.yellow)
.frame(width:highlightWidth, height:highlightHeight)
.cornerRadius(highlightCorners)
Rectangle()
.foregroundColor(.cyan)
.frame(width:highlightWidth, height:highlightHeight)
.cornerRadius(highlightCorners)
Rectangle()
.foregroundColor(.red)
.frame(width:highlightWidth, height:highlightHeight)
.cornerRadius(highlightCorners)
Rectangle()
.foregroundColor(.white)
.frame(width:highlightWidth, height:highlightHeight)
.cornerRadius(highlightCorners)
}
}
}
}
This text places a small rectangle on the screen with some smaller rectangles overlayed.
I then tried using the following function to streamline the code (and then calling the function in the HStack):
func quickHighlight {
Rectangle()
.foregroundColor(.yellow)
.frame(width: highlightWidth, height: highlightHeight)
.cornerRadius(highlightCorners)
}
I tried putting a variety of permutations and putting it in different parts both in and out of the code. Although the function seems to generate error messages depending on where it is placed such as 'Cannot infer contextual base...' to 'Closure containing a declaration cannot be used with result builder'. The puzzling thing is the very basic function I used as a contextual basis for this learning exercise seemed to indicate this should work (although I am sure there is something overlooked).
FYI my goal was to try a case statement with the function where the function receives an integer and then iterates through a few options to assign a colour to the rectangle.
Any help greatly appreciated.
The standard way is to make a subview. In SwiftUI small views increases performance because it tightens invalidation, i.e. it only needs to recompute the body funcs where the lets/vars have actually changed. Don't use a func that takes params to return a View because that breaks SwiftUI's change detection. A view modifier is an interesting way to make it even more reusable, I'll demonstrate both ways below:
Subview way:
struct HighlightedRectangle: View {
let color: Color
let highlightWidth, highlightHeight, highlightCorners: Float
// body only called if any of the lets are different from last time this View was init by the parent view's body.
var body: some View {
Rectangle()
.foregroundColor(color)
.frame(width: highlightWidth, height: highlightHeight)
.cornerRadius(highlightCorners)
}
}
Then use it in the parent view as follows
let colors = [.yellow, .cyan, .red, .white]
...
ForEach(colors, id: \.self) { color in {
HighlightedRectangle(color: color, highlightWidth: highlightWidth, highlightHeight: highlightHeight, highlightCorners: highlightCorners)
}
View modifier way:
struct Highlighted: ViewModifier {
let color: Color
let highlightWidth, highlightHeight, highlightCorners: Float
// body only called if any of the lets are different from last time this ViewModifier was init by the parent view's body.
func body(content: Content) -> some View {
content
.foregroundColor(color)
.frame(width: highlightWidth, height: highlightHeight)
.cornerRadius(highlightCorners)
}
}
// this just makes the syntax for using the modifier simpler.
extension View {
func highlighted(color: Color, highlightWidth: Float, highlightHeight: Float, highlightCorners: Float) -> some View {
modifier(Highlighted(color: color, highlightWidth: highlightWidth, highlightHeight: highlightHeight, highlightCorners: highlightCorners))
}
}
Then use it in the parent view as follows
let colors = [.yellow, .cyan, .red, .white]
...
ForEach(colors, id: \.self) { color in {
Rectangle()
.highlighted(color: color, highlightWidth: highlightWidth, highlightHeight: highlightHeight, highlightCorners: highlightCorners)
}

How to set limitation or just notice hint for function's argument

how to set argument limitation like below?
// 1.
func ChooseColor(color string:"black|white" ) {
fmt.Println(color)
}
ChooseColor("white") // console "white"
ChooseColor("yellow") // console panic
If you feel like the noob can't understand the above solution, then alternately see below
// 2.
/**
* arg: "black|white"
*
*/
func ChooseColor(color string) {
fmt.Println(color)
}
ChooseColor( ) // IDE can notice "color: black || white"
please help me (T.T)
You can create your type Color and have constants for this type. Something like this
type Color string
const (
ColorBlack Color = "black"
ColorWhite Color = "white"
)
func ChooseColor(color Color) {
fmt.Println(color)
}
The "Go" way to do this is with godoc:
// ChooseColor selects a color. Valid colors are: black, white.
func ChooseColor(color string) {
if color != "black" && color != "white" {
/* handle the error condition */
}
/* ... */
}
This will appear in most IDEs.
This is the solution to 1.. The function panics if given argument is not expected one.
func ChooseColor(color string) {
switch color {
case "white", "black":
fmt.Println(color)
default:
panic(color)
}
}
And 2. is likely answered by Bakurits. This lets the IDE to catch what type of data will be passed to that function.
type Color bool
const (
ColorBlack Color = true
ColorWhite Color = false
)
func ChooseColor(color Color) {
if color {
fmt.Println("black")
} else {
fmt.Println("white")
}
}

Multi-selection with different color for each selected object

I am trying to build a feature to select multiple objects with a different colour to differentiate their status.
Is this possible to have multi-selection in a different colour?
You can use some sort of mapping to do this elegantly. If you have an object that describes a dbId and a status you can map from status to color automaticly. You can use enums for more type safety but this is ommited for this example.:
typescript:
interface ItemStatus{
dbId: number,
status: string
}
let statusColors: { [key: string]: string; } = {};
statusColors['INITIATED'] = "#0000FF";
statusColors['COMPLETED'] = "#00FF00";
statusColors['FAILED'] = "#FF0000";
let items: ItemStatus[] = [{ dbId: 1, status: 'INITIATED' }, { dbId: 2, status: 'COMPLETED' }]
// add color to items
setColor(items)
// select colored items
this.viewer.select(items.map(x => x.dbId));
function setColor(items: ItemStatus[]) : void{
items.forEach(item => {
// setThemingColor requires we a THREE.Vector4 so we have to provide it
// using our hex color we generate a THREE.Color object and assign the 4th value of THREE.Vector4 manually to 1
let color = new THREE.Color(statusColors[item.status]);
this.viewer.setThemingColor(item.dbId, new THREE.Vector4(color.r, color.g, color.b, 1))
})
}
You can just pass the different color for different dbid in
viewer.setThemingColor();
Ref: https://forge.autodesk.com/en/docs/viewer/v7/reference/Viewing/Viewer3D/#setthemingcolor-dbid-color-model-recursive
Related helpful blogs:
https://forge.autodesk.com/blog/happy-easter-setthemingcolor-model-material
https://forge.autodesk.com/blog/revisiting-viewers-theming-coloring-selective-cancelling-deferred-rendering-and-recursive

UIViewController lifecycle broken iOS13, makeKeyAndVisible() seems not to operate?

I have a custom UIStoryboardSegue that works as desired in iOS12.*.
One of the destination view controller is a UITabbarController: for each tab, I have a controller embedded in a navigation controller.
Unfortunately, for iOS13.*, this does not work well: the view controller lifecycle is broken, and no call the viewXXXAppear() nor the willTransition() methods are no longer issued.
It looks like makeKeyAndVisible() has no effect?!
See at the bottom how the screen UI is puzzled below without viewWillAppear() being called.
An horrible temporary workaround
I had to pull my hairs but, I have found a fix which I make public (I had to add a navigation controller on the fly).
This messes the vc hierarchy: do you have a better solution?
public class AladdinReplaceRootViewControllerSegue: UIStoryboardSegue {
override public func perform() {
guard let window = UIApplication.shared.delegate?.window as? UIWindow,
let sourceView = source.view,
let destinationView = destination.view else {
super.perform()
return
}
let screenWidth = UIScreen.main.bounds.size.width
let screenHeight = UIScreen.main.bounds.size.height
destinationView.frame = CGRect(x: 0, y: 0, width: screenWidth, height: screenHeight)
window.insertSubview(destinationView, aboveSubview: sourceView)
// **My fix**
if #available(iOS 13,*) {
// I introduced an invisible navigation controller starting in iOS13 otherwise, my controller attached to the tabbar thru a navigation, dont work correctly, no viewXAppearis called.
let navigationController = UINavigationController.init(rootViewController: self.destination)
navigationController.isNavigationBarHidden = true
window.rootViewController = navigationController
}
else {
window.rootViewController = self.destination
}
window.makeKeyAndVisible()
}
}
I found a solution thanks to Unbalanced calls to begin/end appearance transitions with custom segue
What happens here is that the creation and attaching of the destination view controller happens twice, and the first one happens too soon.
So what you need to do is:
public class AladdinReplaceRootViewControllerSegue: UIStoryboardSegue {
override public func perform() {
guard let window = UIApplication.shared.delegate?.window as? UIWindow,
let sourceView = source.view,
let destinationView = destination.view else {
super.perform()
return
}
let screenWidth = UIScreen.main.bounds.size.width
let screenHeight = UIScreen.main.bounds.size.height
let mock = createMockView(view: desination.view)
window.insertSubview(mock, aboveSubview: sourceView)
//DO SOME ANIMATION HERE< MIGHT NEED TO DO mock.alpha = 0
//after the animation is done:
window.rootViewController = self.destination
mock.removeFromSuperview()
}
func createMockView(view: UIView) -> UIImageView {
UIGraphicsBeginImageContextWithOptions(view.frame.size, true, UIScreen.main.scale)
view.drawHierarchy(in: view.bounds, afterScreenUpdates: true)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return UIImageView(image: image)
}
}
I had a similar problem on iOS 13 when performing a custom storyboard segue that replaces the rootViewController. The original code looked like this:
#interface CustomSegue : UIStoryboardSegue
#end
#implementation CustomSegue
- (void)perform {
AppDelegate* appDelegate = (AppDelegate*)[[UIApplication sharedApplication] delegate];
UIViewController *destination = (UIViewController *) self.destinationViewController;
[destination.view removeFromSuperview];
[appDelegate.window addSubview:destination.view];
appDelegate.window.rootViewController = destination;
}
#end
Removing the line [appDelegate.window addSubview:destination]; fixed the problem to me. Apparanently, it was unnecessary to add the new VC's view as a subview to the window. It did the job correctly even after removing that line, and it also fixed the error message "unbalanced calls to begin/end appearance transitions".

SWIFT - helper class callback

In order to manage/shorten my code a little, I decided to try to create a helper class to manage all alerts.
The idea is that the helper class will create the alert. When the user presses the button, it will then send a callback to inform the code on the viewController which button is pressed. Then based on that I can fire different bits of code.
Currently the code I have manages to pop up the alert correctly, but I cannot get it to send the callback when the button is pressed.
here is my alert manager I created so far(still basic for simplicity):
var alertMgr: alertManager = alertManager()
class alertManager: NSObject {
func showAlert(titleText:String?, confirmText:String, cancelText:String, msgText:String, showCancel:Bool, showConfirm:Bool, style:UIAlertControllerStyle, viewController:UIViewController) -> Int? {
let alertController = UIAlertController(title: titleText, message: msgText, preferredStyle: style)
var myInt:Int!
let actionLeft = UIAlertAction(title:cancelText, style: .Cancel) { action in
println("0") //works when user presses cancel button
myInt = 0
}
let actionRight = UIAlertAction(title:confirmText, style: .Default) { action in
println("1") //works when user presses confirm button
myInt = 1
}
alertController.addAction(actionLeft)
alertController.addAction(actionRight)
viewController.presentViewController(alertController, animated: true, completion: nil)
return myInt //does not return when button is pressed???
}
}
In my code when I want to show an alert I use the following code:
let titleTxt:String = "title txt goes here."
let confirmTxt:String = "confirm"
let cancelTxt:String = "cancel"
let msgTxt:String = "some msg goes here."
let showCancel:Bool = true
let showConfirm:Bool = true
let style:UIAlertControllerStyle = .ActionSheet
let alert = alertMgr.showAlert(titleTxt, confirmText: confirmTxt, cancelText: cancelTxt, msgText: msgTxt, showCancel: showCancel, showConfirm: showConfirm, style:style, viewController: self)
switch alert as Int! {
case nil:
println("nil value")
case 0:
println("cancel pressed") //never gets fired. when button is pressed
case 1:
println("confirm pressed") //never gets fired. when button is pressed
default:
break
}
The main problem is I cannot seem to get the call back to send the "myInt" value on button press. I have searched through pretty much all questions on here with [UIAlertController] and have not been able to find any hints that would push me in the right direction.
Any help would be great.
showAlert returns the value of myInt before any of the given blocks gets a chance to be executed, the solution is to pass complete action-blocks as parameters of showAlert. In order to shorten the signature you can prepare a special type with text, block and flag values and make the function to process arrays of the objects of this type.