SwiftUI - context menu background colour - background

Context menu background not updated
I am trying to update background colour.
Background colour is updated for view, but it is not getting updated for the context menu
Context menu shows previous colour which was set.
can someone help me with this.
thanks in advance
this is the code i used
import SwiftUI
struct ContextMenu: View {
/*List of items =*/
#State var bgColor = Color.gray
var body: some View {
HStack {
Rectangle().frame(width: 120, height: 120).opacity(0.01).border(Color.black, width: 1).contextMenu{
VStack {
Button("Orange",action: {
self.bgColor = Color.orange
})
Button("Green",action: {
self.bgColor = Color.green
})
Button("Red",action: {
self.bgColor = Color.red
})
}
}
}.frame(width:UIScreen.main.bounds.width, height: 200).background(bgColor)
}
}

Context menu caches content and reuse it all the time. Here is possible solution to force update it.
Tested with Xcode 11.4 / iOS 13.4
HStack {
Rectangle().fill(bgColor) // << use same color
.frame(width: 120, height: 120)
.border(Color.black, width: 1)
.contextMenu{
VStack {
Button("Orange",action: {
self.bgColor = Color.orange
})
Button("Green",action: {
self.bgColor = Color.green
})
Button("Red",action: {
self.bgColor = Color.red
})
}
}.id(UUID()) // << force recreate context menu
}.frame(width:UIScreen.main.bounds.width, height: 200).background(bgColor)

Related

ScrollView SwiftUI

I'am developing an app with swift ui. It's a single View App. But I have a problem with the scrollView
I have tried to delete the scrollview but it didn't change anything. I always have this big white problem on the top of my view.
https://imgur.com/a/BiWy0fe
import SwiftUI
struct Redactor {
var id: Int
let name, imageURL, since, bio: String
}
struct Article {
var id: Int
let name, image, content, redactor: String
}
struct MainScreenView: View {
let redactors:[Redactor] = [...]
let articles:[Article] = [...]
var body: some View {
NavigationView {
ScrollView(.vertical, showsIndicators: true) {
VStack(alignment: .leading) {
ScrollView(.horizontal, showsIndicators: false) {
HStack(alignment: .top, spacing: 20) {
ForEach(redactors, id: \.id) { Redactor in
NavigationLink(destination: MainRedactorView(redactor: Redactor)) {
RedactorView(redactor: Redactor)
}
}
}.padding(.leading, 15)
}.padding(.top, 10)
VStack(alignment: .leading) {
Text("Les Dernières nouvelles :")
.font(.title)
.fontWeight(.bold)
.padding(.leading, 15)
.padding(.top, 20)
ForEach(articles, id: \.id) { Article in
ArticleView(article: Article)
}
}
}
}
.navigationBarTitle(Text("The News Place"))
}
}
}
struct RedactorView: View {
let redactor : Redactor
var body : some View {
VStack {
Image(redactor.imageURL)
.resizable()
.frame(width: 150, height: 150)
.aspectRatio(contentMode: .fill)
.cornerRadius(75)
Text(redactor.name)
.font(.subheadline)
.fontWeight(.bold)
}
}
}
struct ArticleView: View {
var article: Article
var body: some View {
VStack (alignment: .leading) {
Image(article.image)
.renderingMode(.original)
.resizable()
.cornerRadius(10)
.aspectRatio(contentMode: .fit)
Text(article.name)
.font(.title)
.fontWeight(.bold)
Text("Le Rondeau Mag'")
.font(.subheadline)
.foregroundColor(.red)
Text(article.content)
.font(.body)
.lineLimit(5)
.foregroundColor(.secondary)
}.padding()
}
}
I would like to delete this white blank on the top of my scroll view. Thank you
Did you try deleting the VStack inside ScrollView? ScrollView already defines an implicit stack.
A good way to know which element causes the blank space is applying .border(Color.red) to different views.
Hope this helps.
Is the NavigationView that's creating the white space on top.
Try with
.navigationBarTitle(Text("The News Place"), displayMode: .inline)
and see if the white space disappears.

Equal widths of subviews with SwiftUI

I'm trying to build a simple watchOS UI with SwiftUI with two pieces of information side-by-side above a button.
I'd like each side (represented as a VStack within an HStack) to take up half of the available width (so it's an even 50/50 split within the yellow parent view) divided where the | character is centered on the button in the example below.
I want the Short and Longer!!! text to each be centered within each side's 50%.
I started with this code, to get the elements in place and show the bounds of some of the different stacks:
var body: some View {
VStack {
HStack {
VStack {
Text("Short").font(.body)
}
.background(Color.green)
VStack {
Text("Longer!!!").font(.body)
}
.background(Color.blue)
}
.frame(minWidth: 0, maxWidth: .infinity)
.background(Color.yellow)
Button (action: doSomething) {
Text("|")
}
}
}
Which gave me this result:
Then, when it comes to making each side-by-side VStack 50% of the available width, I'm stuck. I thought it should work to add .relativeWidth(0.5) to each VStack, which should, as I understand it, make each VStack half the width of its parent view (the HStack, with the yellow background):
var body: some View {
VStack {
HStack {
VStack {
Text("Short").font(.body)
}
.relativeWidth(0.5)
.background(Color.green)
VStack {
Text("Longer!!!").font(.body)
}
.relativeWidth(0.5)
.background(Color.blue)
}
.frame(minWidth: 0, maxWidth: .infinity)
.background(Color.yellow)
Button (action: doSomething) {
Text("|")
}
}
}
But this is the result I get:
How can I get the behavior I want with SwiftUI?
Update: After reviewing the SwiftUI documentation more, I see the example here that sets a frame and then defines a relative width in comparison to that frame, so maybe I'm not supposed to use relativeWidth in this way?
I'm a step closer to what I want with the following code:
var body: some View {
VStack {
HStack {
VStack {
Text("Short").font(.body)
}
.frame(minWidth: 0, maxWidth: .infinity)
.background(Color.green)
VStack {
Text("Longer!!!").font(.body)
}
.frame(minWidth: 0, maxWidth: .infinity)
.background(Color.blue)
}
.frame(minWidth: 0, maxWidth: .infinity)
.background(Color.yellow)
Button (action: doSomething) {
Text("|")
}
}
}
which produces this result:
Now, I am trying to figure out what's creating that extra space in the middle between the two VStacks. So far, experimenting with getting rid of padding and ignoring safe areas does not seem to affect it.
I'm still confused about when and how relativeWidth is supposed to be used, but I was able to achieve want I wanted without using it. (EDIT 18 July 2019: According to the iOS 13 Beta 4 release notes, relativeWidth is now deprecated)
In the last update to my question I had some extra spacing between the two sides, and realized that was the default spacing coming in on the HStack and I was able to remove that by setting its spacing to 0. Here's the final code and result:
var body: some View {
VStack {
HStack(spacing: 0) {
VStack {
Text("Short").font(.body)
}
.frame(minWidth: 0, maxWidth: .infinity)
.background(Color.green)
VStack {
Text("Longer!!!").font(.body)
}
.frame(minWidth: 0, maxWidth: .infinity)
.background(Color.blue)
}
.frame(minWidth: 0, maxWidth: .infinity)
.background(Color.yellow)
Button (action: doSomething) {
Text("|")
}
}
}
And here is the result:
Here's how to create an EqualWidthHStack for watchOS 9, iOS 16, tvOS 16 & macOS 13
Here's the usage:
struct ContentView: View {
private let strings = ["Hello,", "very very very big", "world!"]
var body: some View {
EqualWidthHStack {
ForEach(strings, id: \.self) { string in
ZStack {
RoundedRectangle(cornerRadius: 10, style: .continuous)
.opacity(0.2)
Text(string)
.padding(10)
}
}
}
}
}
First create a struct that conforms to Layout.
struct EqualWidthHStack: Layout {
...
}
It will come with two default methods, here's how you can implement them.
Size that Fits:
func sizeThatFits(proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) -> CGSize {
let maxSize = maxSize(subviews: subviews)
let spacing = spacing(subviews: subviews)
let totalSpacing = spacing.reduce(0.0, +)
return CGSize(width: maxSize.width * CGFloat(subviews.count) + totalSpacing,
height: maxSize.height)
}
Place Subviews:
func placeSubviews(in bounds: CGRect, proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) {
let maxSize = maxSize(subviews: subviews)
let spacing = spacing(subviews: subviews)
let sizeProposal = ProposedViewSize(width: maxSize.width,
height: maxSize.height)
var x = bounds.minX + maxSize.width / 2
for index in subviews.indices {
subviews[index].place(at: CGPoint(x: x, y: bounds.midY),
anchor: .center,
proposal: sizeProposal)
x += maxSize.width + spacing[index]
}
}
You will need the following two helper methods.
Max Size:
private func maxSize(subviews: Subviews) -> CGSize {
let subviewSizes = subviews.map { $0.sizeThatFits(.unspecified) }
let maxSize: CGSize = subviewSizes.reduce(.zero, { result, size in
CGSize(width: max(result.width, size.width),
height: max(result.height, size.height))
})
return maxSize
}
Spacing:
private func spacing(subviews: Subviews) -> [CGFloat] {
subviews.indices.map { index in
guard index < subviews.count - 1 else { return 0.0 }
return subviews[index].spacing.distance(to: subviews[index + 1].spacing,
along: .horizontal)
}
}
Here's Apples WWDC22 video on how to make it:
Compose custom layouts with SwiftUI
You have set background of HStack to yellow color and HStack has some default inter child views spacing. By adding spacing: 0 in HStack will solve the problem see the updated code below.
var body: some View {
VStack {
HStack(spacing: 0) { // Set spacing here
VStack {
Text("Short").font(.body)
}
.frame(minWidth: 0, maxWidth: .infinity)
.background(Color.green)
VStack {
Text("Longer!!!").font(.body)
}
.frame(minWidth: 0, maxWidth: .infinity)
.background(Color.blue)
}
.frame(minWidth: 0, maxWidth: .infinity)
.background(Color.yellow)
Button (action: doSomething) {
Text("|")
}
}
}

(How) can i access relative position of qml element to main window

I have a qml element and want to show a (own) tooltip element as a new window right above this element. for this i need the absolute screen position to place the new window (AFAIK).
i got as far that the regular approach is to use "mapToItem" to get the relative position, but i cannot get to the "main window" - because the element in question is located within a "Loader" (which in this case is again located in another Loader).
So my question is: Is it possible to access the mainWindow from inside the dynamically loaded component, or is there maybe another easier way to anchor a new (tooltip) window right above an element ?
EDIT
mapToGlobal would probably work too, but i have to use qt 5.6.
i finally got it to work by setting the main window as a context property in c++:
this->qmlEngine->rootContext()->setContextProperty("mainWindow", this->root);
and in qml i can then access the main window position (on screen) and add the relative position the item has to the shown window like that:
tooltipWindow.setX(mainWindow.x +item1.mapToItem(item2,0,0).x )
The Window item has contentItem especially for that
[read-only] contentItem : Item
The invisible root item of the scene.
So you can refer to Window.contentItem as if it was Window:
import QtQuick 2.7
import QtQuick.Window 2.2
Window {
id: mainWindow
visible: true
width: 600
height: 300
Component {
id: testElement
Rectangle {
id: rect
width: 100
height: 100
color: "orange"
border { width: 1; color: "#999" }
MouseArea {
anchors.fill: parent
hoverEnabled: true
onEntered: tooltip.show(true);
onExited: tooltip.show(false);
onPositionChanged: tooltip.setPosition(mapToItem(mainWindow.contentItem,mouse.x, mouse.y));
}
}
}
Item {
x: 40
y: 50
Item {
x: 80
y: 60
Loader {
sourceComponent: testElement
}
}
}
Rectangle {
id: tooltip
visible: false
width: 100
height: 20
color: "lightgreen"
border { width: 1; color: "#999" }
Text {
anchors.centerIn: parent
text: "I'm here"
}
function show(isShow) {
tooltip.visible = isShow;
}
function setPosition(point) {
tooltip.x = point.x - tooltip.width / 2;
tooltip.y = point.y - tooltip.height;
}
}
}
As for me I would reparent tooltip Item to hovered item itself at MouseArea.onEntered and so you can avoid position recalculation etc.:
onEntered: tooltip.show(true, rect);
onExited: tooltip.show(false);
onPositionChanged: tooltip.setPosition(mouse.x, mouse.y);
...
function show(isShow, obj) {
obj = (typeof obj !== 'undefined' ? obj : null);
if(obj !== null) {
tooltip.parent = obj;
}
tooltip.visible = isShow;
}
function setPosition(x, y) {
tooltip.x = x - tooltip.width / 2;
tooltip.y = y - tooltip.height;
}

Using a slider under another mouse area QML / QT

I am designing an UI interface in Qt where one of the pages can swipe between views. I have defined a SwipeArea region in the whole screen (which is a MouseArea I defined in another QML file), to be able to swipe between pages, with the property propagateComposedEvents set to true so that I can swipe between pages, and still being able to click on all buttons.
The problem comes when using a Slider. Apparently it can't get these mouse events, and I can't interact with it because when I click on the screen it starts the swiping handler. Any idea of how can I solve this?
This is where I define the SwipeArea.qml code:
import QtQuick 2.0
MouseArea {
property point origin
property bool ready: false
signal move(int x, int y)
signal swipe(string direction)
propagateComposedEvents: true
onPressed: {
drag.axis = Drag.XAndYAxis
origin = Qt.point(mouse.x, mouse.y)
}
onPositionChanged: {
switch (drag.axis) {
case Drag.XAndYAxis:
if (Math.abs(mouse.x - origin.x) > 16) {
drag.axis = Drag.XAxis
}
else if (Math.abs(mouse.y - origin.y) > 16) {
drag.axis = Drag.YAxis
}
break
case Drag.XAxis:
move(mouse.x - origin.x, 0)
break
case Drag.YAxis:
move(0, mouse.y - origin.y)
break
}
}
onReleased: {
switch (drag.axis) {
case Drag.XAndYAxis:
canceled(mouse)
break
case Drag.XAxis:
swipe(mouse.x - origin.x < 0 ? "left" : "right")
break
case Drag.YAxis:
swipe(mouse.y - origin.y < 0 ? "up" : "down")
break
}
}
}
And here is the main.qml where I use it:
Item {
id: content
width: root.width * itemDataLength
height:parent.height
property double k: (content.width - root.width) / (img.width - root.width)
Repeater {
id:repeater
model: itemDataLength
width:parent.width
height:parent.height
Loader{
id:loader
width:parent.width / 2
x: root.width * index
height:parent.height
source:loaderItems[index]
}
}
}
SwipeArea {
id: mouse
anchors.fill: parent
onMove: {
content.x = (-root.width * currentIndex) + x
}
onSwipe: {
switch (direction) {
case "left":
if (currentIndex === itemDataLength - 1) {
currentIndexChanged()
}
else {
currentIndex++
}
break
case "right":
if (currentIndex === 0) {
currentIndexChanged()
}
else {
currentIndex--
}
break
}
}
onCanceled: {
currentIndexChanged()
}
}
Maybe important to add, my slider code:
Slider {
id: slider
anchors.centerIn: parent
updateValueWhileDragging:true
activeFocusOnPress:true
style: SliderStyle {
id:sliderStyle
//Slider handler
handle: Rectangle {
id:handler
width: 22
height: 44
radius:2
antialiasing: true
Image{
source:"icons/slidercenter.png"
anchors.centerIn: parent
}
}
//Slider Groove
groove: Item {
id:slidergroove
implicitHeight: 50
implicitWidth: temperaturePage.width -50
}
}
}
I appreciate any help or opinion, and thank you in advance!

Create custom picker control

See screenshot of custom picker:
I know there isn't a way to style the picker control in Titanium. This picker only needs to work on iOS (iPad only). I was thinking I could hack the TableView control to use instead of the picker to achieve the style desired. Is that reasonable? How would I snap the TableViewRows so one is always in the center like seen in typical picker controls?
that's a tough one i would say you just only need views and labels and the swipe event (you can recognize if some one swipe up and down ) and just changes the labels. you can do this with a callback function i hope this code will help you (we have created a custom picker with this code)
using alloy
XML
<View id="numOfIntrusionsPickerContainer" class="compositeWrapperPicker noMargins" >
<View id="numOfIntrusionsButtonContainer" class="horizontalWrapper"></View>
CSS
".compositeWrapperPicker" : {
height: Ti.UI.SIZE,
layout: "composite",
width:Ti.UI.SIZE
},
".horizontalWrapper" : {
width: Ti.UI.SIZE,
height: Ti.UI.SIZE,
layout: "horizontal"
},
JS
// INSTANTIATION
var args = arguments[0] || {};
var widthValue = 120;
var pickerEnabled = true;
if(args.width != null){
widthValue = args.width;
}
if(args.isEnabled != null){
pickerEnabled = args.isEnabled;
}
// STYLING
// ADDITIONS
// pressed arg can be: true,false,null. false & null are equal
// true = 'yes' is Pressed at creation, false/null = 'no' is pressed
var btn_main = Ti.UI.createButton({
top: 5,
bottom: 0,
left:0,
right:5,
height: 45,
enabled: pickerEnabled,
width: widthValue,
borderRadius: 5,
backgroundImage: "/images/bttn_gray_flex.png",
backgroundSelectedImage : "/images/bttn_gray_press_flex.png",
backgroundFocusedImage : "/images/bttn_gray_press_flex.png"
});
var picker_divider = Ti.UI.createImageView({
right: 43,
bottom:2,
touchEnable:false,
focusable:false,
image: "/images/Divider_picker.png"
});
var picker_arrows = Ti.UI.createImageView({
right: 16,
top: 17,
touchEnabled: false,
focusable: false,
image: "/images/bttn_selector_arrow.png"
});
$.pickerContainer.add(btn_main);
$.pickerContainer.add(picker_divider);
$.pickerContainer.add(picker_arrows);
// CODE
// LISTENERS
if(args.callBack != null){
btn_main.addEventListener("click",function(_event){
args.callBack(_event);
});
}