I'm using SwiftUI 2.0, and I need a way to add a header to an OutlineGroup or DisclosureGroup, similar to headers in List sections. Is there a way to do this?
You can use separate Sections to create the headers (and, optionally, footers).
import SwiftUI
struct Node : Identifiable {
let id = UUID()
let name: String
let children: [Node]?
}
struct SectionedOutlineView: View {
var body: some View {
List(selection: $selection) {
Section(header: Label("Nodes 1", systemImage: "sparkle")) {
OutlineGroup(nodes1, children: \.children) { node in
Label(node.name, systemImage: "shield")
}
}
Section(header: Label("Nodes 2", systemImage: "rosette")) {
OutlineGroup(nodes2, children: \.children) { node in
Label(node.name, systemImage: "folder")
}
}
}
.listStyle(SidebarListStyle())
}
#State var nodes1 = [
Node(name: "Layer 1", children: [
Node(name: "Layer 1-1", children: [
Node(name: "Layer 1-1-1", children: nil),
Node(name: "Layer 1-1-2", children: nil),
]),
Node(name: "Layer 1-2", children: [
Node(name: "Layer 1-2-1", children: nil),
Node(name: "Layer 1-2-2", children: nil),
Node(name: "Layer 1-2-3", children: nil),
]),
Node(name: "Layer 1-3", children: nil),
]),
Node(name: "Layer 2", children: [
Node(name: "Layer 2-1", children: [
]),
Node(name: "Layer 2-2", children: [
]),
Node(name: "Layer 2-3", children: [
]),
])
]
#State var nodes2 = [
Node(name: "Layer 1", children: [
Node(name: "Layer 1-1", children: [
Node(name: "Layer 1-1-1", children: nil),
Node(name: "Layer 1-1-2", children: nil),
]),
Node(name: "Layer 1-2", children: [
Node(name: "Layer 1-2-1", children: nil),
Node(name: "Layer 1-2-2", children: nil),
Node(name: "Layer 1-2-3", children: nil),
]),
Node(name: "Layer 1-3", children: [
]),
]),
Node(name: "Layer 2", children: [
Node(name: "Layer 2-1", children: [
]),
Node(name: "Layer 2-2", children: [
Node(name: "Layer 2-2-1", children: nil),
Node(name: "Layer 2-2-2", children: nil),
Node(name: "Layer 2-2-3", children: nil),
]),
Node(name: "Layer 2-3", children: [
]),
])
]
#State var selection = Set<Node.ID>()
}
import SwiftUI
struct NodeOutlineGroup<Node>: View where Node: Hashable, Node: Identifiable, Node: CustomStringConvertible{
let node: Node
let childKeyPath: KeyPath<Node, [Node]?>
let headerKeyPath: KeyPath<Node, String?>?
#State var isExpanded: Bool = true
var body: some View {
if node[keyPath: childKeyPath] != nil {
DisclosureGroup(
isExpanded: $isExpanded,
content: {
if isExpanded {
ForEach(node[keyPath: childKeyPath]!) { childNode in
NodeOutlineGroup(node: childNode,
childKeyPath: childKeyPath,
headerKeyPath: headerKeyPath,
isExpanded: isExpanded)
}
}
},
label: {
section
})
} else {
section
}
}
var section: some View {
VStack(alignment: .leading) {
if let kp = headerKeyPath, let header = node[keyPath: kp] {
Section(header:
HStack {
Text(header)
Spacer()
}
.background(Color.secondary.opacity(0.3))
) {
Text(node.description)
}
} else {
Text(node.description)
}
}
}
}
struct ContentView: View {
var body: some View {
List {
NodeOutlineGroup(node: data, childKeyPath: \.children, headerKeyPath: \.header, isExpanded: true)
}
.listStyle(InsetGroupedListStyle())
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
// Sample data
struct FileItem: Hashable, Identifiable, CustomStringConvertible {
var id: Self { self }
var name: String
var header: String?
var children: [FileItem]? = nil
var description: String {
switch children {
case nil:
return "π \(name)"
case .some(let children):
return children.isEmpty ? "π \(name)" : "π \(name)"
}
}
}
let data =
FileItem(name: "users", children:
[FileItem(name: "user1234", children:
[FileItem(name: "Photos", header: "Header 1", children:
[FileItem(name: "photo001.jpg", header: "Header 2"),
FileItem(name: "photo002.jpg")]),
FileItem(name: "Movies", children:
[FileItem(name: "movie001.mp4")]),
FileItem(name: "Documents", children: [])
]),
FileItem(name: "newuser", children:
[FileItem(name: "Documents", children: [])
])
])
Related
class TrackingItem : ObservableObject, Identifiable, Codable {
var id = UUID()
var trackingNumber: String?
}
struct ToMeTabResponse : Codable {
var shipments: [TrackingItem] = []
}
struct ObjectData : Codable {
var groups: [ToMeTabResponse] = []
}
struct BaseViewModel {
var objectList: [ObjectData] = []
var ignoreIds: [String] = ["AB","BC" ]
This function filter is not returning the expected result. If trackingNumber is in the ignoreIds then that object should not in objectList.
func filter
objectList = objectList.filter { list in
list.groups.contains(where: {
$0.shipments.contains(where: {
!ignoreIds.contains($0.trackingNumber ?? "")
})
})
}
}
If your goal is to filter out any ObjectData instance that has any group with any shipment with an ignored id then you can use the following:
objectList = objectList.filter { data in
data.groups.allSatisfy({
$0.shipments.allSatisfy({
!ignoreIds.contains($0.trackingNumber ?? "")
})
})
}
The use of allSatisfy ensures that all entries meet the desired criteria. So if a single TrackingItem within all of the shipments of all of the groups of a given ObjectData is to be ignored then that entire ObjectData will be filtered out of results.
If your goal is to create a new copy of the data with just matching TrackingItem instances removed (as well as empty shipments and groups) then that is far more complicated. Below is code that will achieve that goal.
filter(by:) methods have been added to and to make this far more manageable.
The following can be run in a Swift Playground.
struct TrackingItem : Codable {
var id = UUID()
var trackingNumber: String?
}
struct ToMeTabResponse : Codable {
var shipments: [TrackingItem] = []
// Create a copy of self while filtering out matching shipments
func filter(by ids: [String]) -> Self {
return ToMeTabResponse(shipments: shipments.filter { !ids.contains($0.trackingNumber ?? "") })
}
}
struct ObjectData : Codable {
var groups: [ToMeTabResponse] = []
// Create a copy of self filtering out empty shipments
func filter(by ids: [String]) -> Self {
return ObjectData(groups: groups.map { $0.filter(by: ids) }.filter { !$0.shipments.isEmpty })
}
}
struct BaseViewModel {
var objectList: [ObjectData] = []
var ignoreIds: [String] = ["AB","BC" ]
// Update objectList by filtering out matching TrackingItems and removing ObjectData instances with empty groups
mutating func filter() {
objectList = objectList.map { $0.filter(by: ignoreIds) }.filter { !$0.groups.isEmpty }
}
}
var x = BaseViewModel()
x.objectList = [
ObjectData(groups: [
ToMeTabResponse(shipments: [
TrackingItem(trackingNumber: "AB"),
TrackingItem(trackingNumber: "XY"),
]),
ToMeTabResponse(shipments: [
TrackingItem(trackingNumber: "GF"),
TrackingItem(trackingNumber: "HY"),
]),
]),
ObjectData(groups: [
ToMeTabResponse(shipments: [
TrackingItem(trackingNumber: "QW"),
TrackingItem(trackingNumber: "NN"),
]),
ToMeTabResponse(shipments: [
TrackingItem(trackingNumber: "TT"),
TrackingItem(trackingNumber: "OO"),
]),
]),
ObjectData(groups: [
ToMeTabResponse(shipments: [
TrackingItem(trackingNumber: "HG"),
TrackingItem(trackingNumber: "JU"),
]),
ToMeTabResponse(shipments: [
TrackingItem(trackingNumber: "AB"),
TrackingItem(trackingNumber: "BC"),
]),
]),
ObjectData(groups: [
ToMeTabResponse(shipments: [
TrackingItem(trackingNumber: "BC"),
TrackingItem(trackingNumber: "AB"),
]),
ToMeTabResponse(shipments: [
TrackingItem(trackingNumber: "AB"),
TrackingItem(trackingNumber: "BC"),
]),
]),
]
x.filter()
print(x.objectList)
I want depending on which button is pressed to update the index number. Also when I print it, I want it to show me that it is well updated.
struct idontcare { //Named it this way cause I got mad
#State var index: Int = 0
let buttons: [String] = [ "Primary", "Blue", "Green", "Red", "Yellow", "Orange","Gray", "Purple", "Cyan", "Pink", "Teal"]
let buttonColor: [Color] = [Color.primary, Color.blue, Color.green, Color.red, Color.yellow, Color.orange, Color.gray, Color.purple, Color.cyan, Color.pink, Color.teal]
func showMenu() -> some View{
return Menu("Update Color: "){
ForEach(buttons, id: \.self) { button in
Button(action: {
self.index = 5 //DOES NOT WORK!!
print(self.index) //ALWAYS PRINTS 0!
}) {
Label(button, systemImage: "paintbrush.pointed")
}
}
}
}
}
You code is missing the body property.
The struct "Idontcare" has to conform to View, it might got lost while pasting to your question.
However, if I add both to your code, everything works fine for me.
struct Idontcare: View { //Has to conform to View
#State var index: Int = 0
let buttons: [String] = [ "Primary", "Blue", "Green", "Red", "Yellow", "Orange","Gray", "Purple", "Cyan", "Pink", "Teal"]
let buttonColor: [Color] = [Color.primary, Color.blue, Color.green, Color.red, Color.yellow, Color.orange, Color.gray, Color.purple, Color.cyan, Color.pink, Color.teal]
var body: some View { //was missing in your Code
showMenu()
}
func showMenu() -> some View{
return Menu("Update Color: "){
ForEach(buttons, id: \.self) { button in
Button(action: {
self.index = 5 //Works now
print(self.index)
}) {
Label(button, systemImage: "paintbrush.pointed")
}
}
}
}
}
This should work just like what you wanted. I made a Test view, you can follow the same logic.
import SwiftUI
struct Test: View {
#State var index: Int = 0
let buttons: [String] = [ "Primary", "Blue", "Green", "Red", "Yellow", "Orange","Gray", "Purple", "Cyan", "Pink", "Teal"]
let buttonColor: [Color] = [Color.primary, Color.blue, Color.green, Color.red, Color.yellow, Color.orange, Color.gray, Color.purple, Color.cyan, Color.pink, Color.teal]
var body: some View {
ForEach(0..<buttons.count, id: \.self) { index in
Button(action: {
print(index)
}) {
Label(buttons[index], systemImage: "paintbrush.pointed")
}
}
}
}
You can try the below code. it will help you to get selected button index.
struct idontcare : View {
#State var index: Int = 0
let buttons: [String] = ["Primary", "Blue", "Green", "Red", "Yellow", "Orange","Gray", "Purple", "Cyan", "Pink", "Teal"]
let buttonColor: [Color] = [Color.primary, Color.blue, Color.green, Color.red, Color.yellow, Color.orange, Color.gray, Color.purple, Color.gray, Color.pink, Color.black]
var body: some View {
showMenu()
}
func showMenu() -> some View{
return Menu("Update Color: ") {
ForEach(0..<buttons.count, id: \.self) { index in
Button(action: {
self.index = index
debugPrint("Color Selected = \(buttons[index])")
debugPrint("Selected Color Index = \(self.index)")
}) {
Label(buttons[index], systemImage: "paintbrush.pointed")
}
}
}
}
}
I wrote a basic calculator app with flutter and installed it on my iphone with Xcode.
I builded the app and trusted the developer in the iPhone settings. But when I want to open the app it crashes after about 2 seconds of my background screen in blurry. I don't know what is wrong the app runs on the simulator. I don't know if the code is to bad written or if I missed some configuration steps. Has somebody experience with that...
Thank you for your help.
Device Log/ Crash:
{
"crashReporterKey" : "771e2f68485128ac7de5b4f2d3a557289be32a15",
"kernel" : "Darwin Kernel Version 20.0.0: Fri Aug 28 23:07:00 PDT 2020; root:xnu-7195.0.46~9\/RELEASE_ARM64_T8020",
"product" : "iPhone11,2",
"incident" : "E2C76AF9-8284-47F9-962E-FE6DB1D9AD19",
"date" : "2020-09-28 21:24:44.54 +0200",
"build" : "iPhone OS 14.0.1 (18A393)",
"timeDelta" : 6,
"memoryStatus" : {
"compressorSize" : 29314,
"compressions" : 1810481,
"decompressions" : 1202975,
"zoneMapCap" : 1454407680,
"largestZone" : "APFS_4K_OBJS",
"largestZoneSize" : 35880960,
"pageSize" : 16384,
"uncompressed" : 79755,
"zoneMapSize" : 162873344,
"memoryPages" : {
"active" : 75993,
"throttled" : 0,
Flutter Code
import 'package:flutter/material.dart';
void main() {
runApp(Calculator());
}
class Calculator extends StatefulWidget {
#override
_CalculatorState createState() => _CalculatorState();
}
class _CalculatorState extends State<Calculator> {
String _num1 = "";
String _operator = "";
String _num2 = "";
String _show = "";
bool _set_show_to_none = false;
bool _isNum2 = false;
void press_0_button_num1() {
setState(() {
_num1 += "0";
_show += "0";
});
}
void press_1_button_num1() {
setState(() {
_num1 += "1";
_show += "1";
});
}
void press_2_button_num1() {
setState(() {
_num1 += "2";
_show += "2";
});
}
void press_3_button_num1() {
setState(() {
_num1 += "3";
_show += "3";
});
}
void press_4_button_num1() {
setState(() {
_num1 += "4";
_show += "4";
});
}
void press_5_button_num1() {
setState(() {
_num1 += "5";
_show += "5";
});
}
void press_6_button_num1() {
setState(() {
_num1 += "6";
_show += "6";
});
}
void press_7_button_num1() {
setState(() {
_num1 += "7";
_show += "7";
});
}
void press_8_button_num1() {
setState(() {
_num1 += "8";
_show += "8";
});
}
void press_9_button_num1() {
setState(() {
_num1 += "9";
_show += "9";
});
}
void press_0_button_num2() {
setState(() {
if (_set_show_to_none){
_show = "";}
_num2 += "0";
_show += "0";
_set_show_to_none = false;
});
}
void press_1_button_num2() {
setState(() {
if (_set_show_to_none){
_show = "";}
_num2 += "1";
_show += "1";
_set_show_to_none = false;
});
}
void press_2_button_num2() {
setState(() {
if (_set_show_to_none){
_show = "";}
_num2 += "2";
_show += "2";
_set_show_to_none = false;
});
}
void press_3_button_num2() {
setState(() {
if (_set_show_to_none){
_show = "";}
_num2 += "3";
_show += "3";
_set_show_to_none = false;
});
}
void press_4_button_num2() {
setState(() {
if (_set_show_to_none){
_show = "";}
_num2 += "4";
_show += "4";
_set_show_to_none = false;
});
}
void press_5_button_num2() {
setState(() {
if (_set_show_to_none){
_show = "";}
_num2 += "5";
_show += "5";
_set_show_to_none = false;
});
}
void press_6_button_num2() {
setState(() {
if (_set_show_to_none){
_show = "";}
_num2 += "6";
_show += "6";
_set_show_to_none = false;
});
}
void press_7_button_num2() {
setState(() {
if (_set_show_to_none){
_show = "";}
_num2 += "7";
_show += "7";
_set_show_to_none = false;
});
}
void press_8_button_num2() {
setState(() {
if (_set_show_to_none){
_show = "";}
_num2 += "8";
_show += "8";
_set_show_to_none = false;
});
}
void press_9_button_num2() {
setState(() {
if (_set_show_to_none){
_show = "";}
_num2 += "9";
_show += "9";
_set_show_to_none = false;
});
}
void press_plus_button() {
setState(() {
_operator = "+";
_show = "+";
_set_show_to_none = true;
_isNum2 = true;
});
}
void press_minus_button() {
setState(() {
_operator = "-";
_show = "-";
_set_show_to_none = true;
_isNum2 = true;
});
}
void press_multiple_button() {
setState(() {
_operator = "*";
_show = "*";
_set_show_to_none = true;
_isNum2 = true;
});
}
void press_divided_button() {
setState(() {
_operator = "/";
_show = "/";
_set_show_to_none = true;
_isNum2 = true;
});
}
void press_ac_button() {
setState(() {
_num1 = "";
_operator = "";
_num2 = "";
_show = "";
_set_show_to_none = false;
_isNum2 = false;
});
}
void calculate() {
setState(() {
int num1int = int.tryParse(_num1);
int num2int = int.tryParse(_num2);
double num1do = num1int.toDouble();
double num2do = num2int.toDouble();
double result = 0;
if (_operator == "+") {
result = num1do + num2do;
}
else if (_operator == "-") {
result = num1do - num2do;
}
else if (_operator == "*") {
result = num1do * num2do;
}
else if (_operator == "/") {
result = num1do / num2do;
}
RegExp regex = RegExp(r"([.]*0)(?!.*\d)");
String result_output = result.toString().replaceAll(RegExp(r"([.]*0)(?!.*\d)"), "");
_show = result_output;
});
}
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Column(
children: <Widget>[
SizedBox(
height: 98,
),
Expanded(
child: Align(
alignment: Alignment.centerRight,
child: Text(_show,
textAlign: TextAlign.right,
style: TextStyle(fontSize: 130)),
),
),
Row(
children: [
Container(
padding: const EdgeInsets.all(10.0),
child: SizedBox(
height: 70,
width: 150,
child: FloatingActionButton.extended(
elevation: 0.2,
onPressed: () {
press_ac_button();
},
label: Text("AC"),
isExtended: true,
),
),
),
],
),
SizedBox(
height: 20,
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
SizedBox(
height: 100,
width: 90,
child: FloatingActionButton(
elevation: 0.2,
onPressed: () {
if (_isNum2) {
press_1_button_num2();
} else {
press_1_button_num1();
}
},
child: Text("1"),
),
),
SizedBox(
height: 100,
width: 90,
child: FloatingActionButton(
elevation: 0.2,
onPressed: () {
if (_isNum2) {
press_2_button_num2();
} else {
press_2_button_num1();
}
},
child: Text("2"),
),
),
SizedBox(
height: 100,
width: 90,
child: FloatingActionButton(
elevation: 0.2,
onPressed: () {
if (_isNum2) {
press_3_button_num2();
} else {
press_3_button_num1();
}
},
child: Text("3"),
),
),
SizedBox(
height: 100,
width: 90,
child: FloatingActionButton(
elevation: 0.2,
onPressed: () {
press_plus_button();
},
child: Text("+"),
),
),
],
),
SizedBox(
height: 20,
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
SizedBox(
height: 100,
width: 90,
child: FloatingActionButton(
elevation: 0.2,
onPressed: () {
if (_isNum2) {
press_4_button_num2();
} else {
press_4_button_num1();
}
},
child: Text("4"),
),
),
SizedBox(
height: 100,
width: 90,
child: FloatingActionButton(
elevation: 0.2,
onPressed: () {
if (_isNum2) {
press_5_button_num2();
} else {
press_5_button_num2();
}
},
child: Text("5"),
),
),
SizedBox(
height: 100,
width: 90,
child: FloatingActionButton(
elevation: 0.2,
onPressed: () {
if (_isNum2) {
press_6_button_num2();
} else {
press_6_button_num1();
}
},
child: Text("6"),
),
),
SizedBox(
height: 100,
width: 90,
child: FloatingActionButton(
elevation: 0.2,
onPressed: () {
press_minus_button();
},
child: Text("-"),
),
),
],
),
SizedBox(
height: 20,
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
SizedBox(
height: 100,
width: 90,
child: FloatingActionButton(
elevation: 0.2,
onPressed: () {
if (_isNum2) {
press_7_button_num2();
} else {
press_7_button_num1();
}
},
child: Text("7"),
),
),
SizedBox(
height: 100,
width: 90,
child: FloatingActionButton(
elevation: 0.2,
onPressed: () {
if (_isNum2) {
press_8_button_num2();
} else {
press_8_button_num1();
}
},
child: Text("8"),
),
),
SizedBox(
height: 100,
width: 90,
child: FloatingActionButton(
elevation: 0.2,
onPressed: () {
if (_isNum2) {
press_9_button_num2();
} else {
press_9_button_num1();
}
},
child: Text("9"),
),
),
SizedBox(
height: 100,
width: 90,
child: FloatingActionButton(
elevation: 0.2,
onPressed: () {
press_multiple_button();
},
child: Text("*"),
),
),
],
),
SizedBox(
height: 3,
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
SizedBox(
height: 90,
width: 90,
child: FloatingActionButton(
elevation: 0.2,
onPressed: () {
if (_isNum2) {
press_0_button_num2();
} else {
press_0_button_num1();
}
},
child: Text("0"),
isExtended: true,
),
),
SizedBox(
height: 75,
width: 180,//290
child: FloatingActionButton.extended(
elevation: 0.2,
onPressed: () {
calculate();
},
label: Text("="),
isExtended: true,
),
),
SizedBox(
height: 100,
width: 90,
child: FloatingActionButton(
elevation: 0.2,
onPressed: () {
press_divided_button();
},
child: Text("/"),
),
)
],
),
SizedBox(height: 47.5),
],
),
),
);
}
}
I have two errors in Xcode:
Go into xcode and change your Bundle Identifier to something like this using your own website name.
com.mywebsitename.calculator
With List or OutlineGroup in SwiftUI how to make some (or all) of their branches expanded by default when creating the view. This seems to be possible with DisclosureGroup with a binding.
This could be useful for restoring state or customizing the view for presentation.
Reusable version of OutlineGroup, where expandability is under control.
import SwiftUI
struct NodeOutlineGroup<Node>: View where Node: Hashable, Node: Identifiable, Node: CustomStringConvertible{
let node: Node
let childKeyPath: KeyPath<Node, [Node]?>
#State var isExpanded: Bool = true
var body: some View {
if node[keyPath: childKeyPath] != nil {
DisclosureGroup(
isExpanded: $isExpanded,
content: {
if isExpanded {
ForEach(node[keyPath: childKeyPath]!) { childNode in
NodeOutlineGroup(node: childNode, childKeyPath: childKeyPath, isExpanded: isExpanded)
}
}
},
label: { Text(node.description) })
} else {
Text(node.description)
}
}
}
struct ContentView: View {
var body: some View {
List {
NodeOutlineGroup(node: data, childKeyPath: \.children, isExpanded: true)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
struct FileItem: Hashable, Identifiable, CustomStringConvertible {
var id: Self { self }
var name: String
var children: [FileItem]? = nil
var description: String {
switch children {
case nil:
return "π \(name)"
case .some(let children):
return children.isEmpty ? "π \(name)" : "π \(name)"
}
}
}
let data =
FileItem(name: "users", children:
[FileItem(name: "user1234", children:
[FileItem(name: "Photos", children:
[FileItem(name: "photo001.jpg"),
FileItem(name: "photo002.jpg")]),
FileItem(name: "Movies", children:
[FileItem(name: "movie001.mp4")]),
FileItem(name: "Documents", children: [])
]),
FileItem(name: "newuser", children:
[FileItem(name: "Documents", children: [])
])
])
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Very nice, Paul B., thank you.
But I would like to have more control about the displayed rows. So I have expanded your solution a little bit:
struct NodeOutlineGroup<Node, Content>: View where Node: Hashable, Node: Identifiable, Node: CustomStringConvertible, Content: View {
let node: Node
let childKeyPath: KeyPath<Node, [Node]?>
#State var isExpanded: Bool = true
let content: (Node) -> Content
var body: some View {
if node[keyPath: childKeyPath] != nil {
DisclosureGroup(
isExpanded: $isExpanded,
content: {
if isExpanded {
ForEach(node[keyPath: childKeyPath]!) { childNode in
NodeOutlineGroup(node: childNode, childKeyPath: childKeyPath, isExpanded: isExpanded, content: content)
}
}
},
label: { content(node) })
} else {
content(node)
}
}
}
with usage:
struct ContentView: View {
var body: some View {
List {
NodeOutlineGroup(node: data, childKeyPath: \.children, isExpanded: true) { node in
Text(node.description)
}
}
}
}
I was searching for this as well and I believe OutlineGroup doesn't support this. Instead I've moved to DisclosureGroup, which OutlineGroup uses for it's implementation, and directly supports an expansion boolean binding as well as allowing nesting:
struct ToggleStates {
var oneIsOn: Bool = false
var twoIsOn: Bool = true
}
#State private var toggleStates = ToggleStates()
#State private var topExpanded: Bool = true
var body: some View {
DisclosureGroup("Items", isExpanded: $topExpanded) {
Toggle("Toggle 1", isOn: $toggleStates.oneIsOn)
Toggle("Toggle 2", isOn: $toggleStates.twoIsOn)
DisclosureGroup("Sub-items") {
Text("Sub-item 1")
}
}
}
Example from https://developer.apple.com/documentation/swiftui/disclosuregroup
This is nice, but doesnβt allow for selection. Instead, the subtree under each first level item is composited as a single cell, which makes individual selection impossible.
I am developing iphone app using xcode 11 beta 5.
but I found the error like "Value of type '[Course]' has no member 'identified'"
This is my source code.
struct ContentView: View {
var body: some View {
NavigationView{
List(
[
Course.init(name: "sample1", imageUrl: "no image"),
Course.init(name: "sample2", imageUrl: "no image")
].identified(by: \.name)
){
Text($0.name)
}.navigationBarTitle(Text("Courses"))
}
}
}
how can i solve this error?
Here is fixed variant
struct ContentView: View {
var body: some View {
NavigationView{
List(
[
Course(name: "sample1", imageUrl: "no image"),
Course(name: "sample2", imageUrl: "no image")
], id: \.name // << here !!
){
Text($0.name)
}.navigationBarTitle(Text("Courses"))
}
}
}