where to place a function in SwiftUI - function

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)
}

Related

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

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)
}

SwiftUI: Do I need to remove UIHostingController from the controller chain when I remove the controlled view from the view hierarchy?

I am trying to migrate UIKit Views in my app to SwiftUI. One of the central elements in my app is a UICollectionView. I am embedding the SwiftUI views using a UIHostingController - so far so good.
I am wondering, since my cells are reusable, what happens to the UIHostingController when the cell is recycled?
Do I need to take it out of the controller chain?
If I need to, what is the best way to do so? (storing the UIHostingController in the cell?)
Eg. a header view looks like this so far:
class HeaderViewCell: UICollectionReusableView {
var layoutAttributes:GroupHeaderViewLayoutAttributes = GroupHeaderViewLayoutAttributes()
public func attachContent(model:GroupHeaderModel, controller:UIViewController){
let view = GroupHeaderView(model: model, layoutAttributes: self.layoutAttributes)
let hostingController = UIHostingController(rootView: view)
if let contentView = hostingController.view {
controller.addChild(hostingController)
self.addSubviewAndConstrains(contentView)
}
}
override func prepareForReuse() {
self.subviews.forEach { $0.removeFromSuperview() }
layoutAttributes = GroupHeaderViewLayoutAttributes()
}
override func apply(_ layoutAttributes: UICollectionViewLayoutAttributes) {
if let attributes = layoutAttributes as? TileViewLayout.HeaderViewAttributes {
self.layoutAttributes.topBarHeight = attributes.topBarHeight
self.layoutAttributes.indicatorWidth = attributes.indicatorWidth
}
}
}

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")
}
}

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".

Xcode 11 Beta 4 TabbedView Binding Problem

Environment: Version 11.0 beta 4 (11M374r)
I'm trying to get a grip on the evolving List() syntax.
The following is a simple list of UUIDs:
via the pre-Beta 4 Code:
Here's my attempted remedy and the compiler result.
I've created an additional error after fixing the previous warning:
I tried replacing '#State' with '#Binding'; which didn't work.
Here's the complete source code:
import SwiftUI
enum TabIdentifier {
case list
case another
}
struct TabView: View {
private var uuids: [String] = {
let ids: [String] = Array(0...5).map { _ in
UUID().uuidString
}
return ids
}()
#State private var selectedTab: TabIdentifier = .list
var body: some View {
TabbedView(selection: $selectedTab) {
// ------------------------------------------------------------
// Tab #1
NavigationView {
List(uuids, id: \.id) { uuid in
Text(uuid)
}.navigationBarTitle(Text("List of UUIDs"))
}.tabItem {
Text("List") // ...Tab #1 Label
}
.tag(TabIdentifier.list) // ...Tab #1 tag
// ------------------------------------------------------------
// Tab #2
Text("Hello Ric!")
.tabItem {
Text("Another view") // ...Tab #2 Label
}
.tag(TabIdentifier.another) // ... Tab #2 Label
}
}
}
What's the remedy?
This is a misleading error. I'm pretty sure your issue is just a typo. It should be List(uuids, id: \.self), not List(uuids, id: \.id). Based on your code, uuids is just an array of strings, and String doesn't have a property id.